- 投稿日:2020-01-24T20:53:23+09:00
Oculus Quest ハンドトラッキングの実装
◆ はじめに
今回はOculus Questのハンドトラッキング機能の実装の説明です。
Unityプロジェクトの作成、ビルド方法などは
【UnityでOculus Quest向けのアプリを作る】APPをビルドして、動作確認する
【UnityでOculus Quest向けのアプリを作る】物を掴む為には
参照してください。◆ 開発環境
macOS Mojave バージョン 10.14.6
Unity 2018.4.12f1
Android SDKハンドトラッキング機能を使用する為、
Oculusのバージョンは(Ver12)にアップデートする必要があります。◆ 手順
- 準備すべき物
- ハンドトラッキング作成
- 手を表示するためには
- 補充説明
- 動作確認
準備すべき物
まず、メニューバーの「Window」→「Asset Store」
Oculus Integrationを検索し、(最新バージョンを)Importします。
Download が終わるとImportボタン出るので、Importボタンをチェックします。ハンドトラッキング作成
ここでは、前回作成した「前回のリンク」SCENEを使います。
前回「Hierarchy」に残した「LocalAvatarWithGrab」を削除し、
先程、Importした中にある「OVRCameraRig」を「Hierarchy」に入れます。
追加した「OVRCameraRig」を選択し、「Inspector」にある、
「Hand Tracking Support」を「Hands Only」に変更すれば。
準備完了です。
手を表示するためには
先程追加した「OVRCameraRig」>「TrackingSpace」下階層の
「LeftHandAnchor」と「RightHandAnchor」中に「OVRHandPrefab」を追加する必要があります。「OVRHandPrefab」は「Oculus Integration」の中にあります。
ドラッグして追加する。
「RightHandAnchor」の中にある「OVRHandPrefab」を選択し、
「OVR Skeleton」→「Skeleton Type」を「Hand Right」設定します。「OVR Mesh」→「Mesh Type」も同じく「Hand Right」に設定します。
(初期値は「Hand Left」なので、左手は設定する必要がないです。)
補充説明
手のスケルトンを表示したい場合
「OVR Skeleton Renderer」にチェックを入れればOKです。
(今回は分かりやすいように、右手にチェックを入れます。Materialsの削除も忘れに)
手カスタマイズしたい場合は「Skinned Mesh Renderer」で調整可能です。
左手を調整しました。
最後に
「OVR Skeleton」中の「Enable Physics Capsules」にチェックを入れると、手に当たり判定が追加出来ます。
今回は右手に追加します。
動作確認
- 投稿日:2020-01-24T18:54:58+09:00
UnityでマルチスレッドなHttpサーバー
Unity と Webアプリ間で データ連携したかったので、Unity で Httpサーバーを作ってみました。後々、Unityで簡易な WebAPI を作りたいので、再利用しやすい方法をまとめています。
やりたいこと
・Unityで Httpサーバーを作成
・利用するライブラリは System.Net.HttpListener
・マルチスレッド処理で描画負荷の影響を抑える
・ Get / Post リクエストを処理
・再利用したいので、サーバー処理とリクエスト処理のコンポーネントを分離
・UnityEvent を使って、インスペクタでイベントを管理
・通信テストは、Postman で行うSystem.Net.HttpListenerとは
HTTP 要求に応答する単純な HTTP プロトコルリスナーを作成できます。
.NET 標準クラスなので、Unityでも標準で使うことができます。
MS-DOC HttpListener クラス概要マルチスレッド処理の準備
Unity は描画負荷が高いので、安定させるために System.Net.HttpListener を別スレッドで実行します。
ただし、リクエスト内容によってはメインスレッドで描画を行う必要があります。Unity ではスレッドをまたいだ関数の実行はできないので、UnityMainThreadDispatcher を使って、メインスレッドのアクションを呼び出せるようにします。
下記からダウンロードし、Assetsに配置します。
UnityMainThreadDispatcher - GitHubHTTPサーバーのコンポーネントを作る
以下のようなスクリプトを書きます。
HTTPサーバーのコード
UnityHttpListener.csusing System; using System.IO; using System.Net; using System.Net.Http; using System.Text; using System.Threading; using UnityEngine; using UnityEngine.Events; public class UnityHttpListener : MonoBehaviour { private HttpListener listener; private Thread listenerThread; public string domain = "localhost"; public int port = 8080; [System.Serializable] public class OnGetRequestEvent : UnityEvent<HttpListenerContext> { } public OnGetRequestEvent OnGetRequest; [System.Serializable] public class OnPostGetRequestEvent : UnityEvent<HttpListenerContext> { } public OnPostGetRequestEvent OnPostRequest; void Start() { listener = new HttpListener(); listener.Prefixes.Add("http://" + domain + ":" + port + "/"); listener.AuthenticationSchemes = AuthenticationSchemes.Anonymous; listener.Start(); listenerThread = new Thread(startListener); listenerThread.Start(); Debug.Log("Server Started"); } private void OnDestroy() { listener.Stop(); listenerThread.Join(); } private void startListener() { while (listener.IsListening) { var result = listener.BeginGetContext(ListenerCallback, listener); result.AsyncWaitHandle.WaitOne(); } } private void ListenerCallback(IAsyncResult result) { if (!listener.IsListening) return; HttpListenerContext context = listener.EndGetContext(result); Debug.Log("Method: " + context.Request.HttpMethod); Debug.Log("LocalUrl: " + context.Request.Url.LocalPath); try { if (ProcessGetRequest(context)) return; if (ProcessPostRequest(context)) return; } catch (Exception e) { ReturnInternalError(context.Response, e); } } private bool CanAccept(HttpMethod expected, string requested) { return string.Equals(expected.Method, requested, StringComparison.CurrentCultureIgnoreCase); } private bool ProcessGetRequest(HttpListenerContext context) { if (!CanAccept(HttpMethod.Get, context.Request.HttpMethod) || context.Request.IsWebSocketRequest) return false; //メインスレッドでGetリクエストイベントを呼び出し UnityMainThreadDispatcher.Instance().Enqueue(() => OnGetRequest.Invoke(context)); return true; } private bool ProcessPostRequest(HttpListenerContext context) { if (!CanAccept(HttpMethod.Post, context.Request.HttpMethod)) return false; //メインスレッドでPostリクエストイベントを呼び出し UnityMainThreadDispatcher.Instance().Enqueue(() => OnPostRequest.Invoke(context)); return true; } private void ReturnInternalError(HttpListenerResponse response, Exception cause) { Console.Error.WriteLine(cause); response.StatusCode = (int) HttpStatusCode.InternalServerError; response.ContentType = "text/plain"; try { using(var writer = new StreamWriter(response.OutputStream, Encoding.UTF8)) writer.Write(cause.ToString()); response.Close(); } catch (Exception e) { Console.Error.WriteLine(e); response.Abort(); } } }Get / Post のリクエストがあれば、UnityMainThreadDispatcher を使って、メインスレッドでそれぞれのUnityEventを呼び出しています。
リクエスト処理のコード
RequestHandler.csusing System; using System.Net; using UnityEngine; public class RequestHandler : MonoBehaviour { private MyData data; private void Start() { data = new MyData(); } public void OnGetRequest(HttpListenerContext context) { var request = context.Request; var response = context.Response; response.StatusCode = (int) HttpStatusCode.OK; response.ContentType = "application/json"; string message = ""; if (request.QueryString.AllKeys.Length > 0) { foreach (var key in request.QueryString.AllKeys) { object value = request.QueryString.GetValues(key) [0]; Debug.Log("key: " + key + " , value: " + value); switch (key) { case "GetData": message = JsonUtility.ToJson(data); break; case "SetData": data.success = Convert.ToBoolean(value); message = JsonUtility.ToJson(data); break; } } } // message の内容をバイト配列に変換してレスポンスを返す var bytes = System.Text.Encoding.UTF8.GetBytes(message); response.Close(bytes, false); } public void OnPostRequest(HttpListenerContext context) { var request = context.Request; var response = context.Response; response.StatusCode = (int) HttpStatusCode.OK; response.ContentType = "application/json"; string message = ""; if (request.QueryString.AllKeys.Length > 0) { foreach (var key in request.QueryString.AllKeys) { object value = request.QueryString.GetValues(key) [0]; Debug.Log("key: " + key + " , value: " + value); switch (key) { case "GetData": message = JsonUtility.ToJson(data); break; case "SetData": data.success = Convert.ToBoolean(value); message = JsonUtility.ToJson(data); break; } } } // message の内容をバイト配列に変換してレスポンスを返す var bytes = System.Text.Encoding.UTF8.GetBytes(message); response.Close(bytes, false); } } [System.Serializable] class MyData { public bool success = false; }ここでは、UnityEvent を受けて、リクエストに対応したレスポンスを返しています。
- リクエストキー が
GetData
の場合: Unity 側のデータを返信- リクエストキー が
SetData
の場合: Unity 側のデータをリクエスト値に変更 -> 変更後のデータを返信レスポンスの ContentType はひとまず Json にしました。
データを保持する MyData クラスを用意して、JsonUtilityでJsonに変換しています。
通信テストができればいいので、 Get / Post どちらも同じ内容です。コンポーネントをアタッチする
空の GameObject を作り、準備していた UnityMainThreadDispatcher と 先ほど作った UnityHttpListener、RequestHandlerをアタッチします。
次にイベントをアタッチしていきます。
インスペクタのOnGetRequest
とOnPostRequest
下部の+
ボタンからイベントを追加し、それぞれにRequest Handler (Script)
コンポーネントを貼り付けます。
プルダウンメニューから
OnGetRequest
とOnPostRequest
それぞれの呼び出す関数を設定します。
通信テストのために、Unity を再生しておきます。
通信テストしてみる
Postman を使って、通信テストをします。
Postman Downloadインストールの手順や使い方はこちら
Postmanを使ったAPIテストのやり方 - IT業務で使えるプログラミングテクニックPostman で Get テスト
メソッドを
Get
にして、http://localhost:8080/?GetData=
を送ってみます。
Unity で作った MyData が Json で返ってきてました。
次は、
http://localhost:8080/?SetData=true
を送ってみます。
ちゃんと
success
がtrue
に変わりました。メソッドを Post にすれば、Postのテストが出来ます。
まとめ
Unityだけで Post/Get リクエストを処理出来るのは、とても楽ですね。プロトタイプでは充分使えます。
今回はローカルでテストしましたが、ngrok やlocaltunnel を使って外部公開すれば、Unity でも 簡易な WebAPI が作れそうです。
参考にしたサイト
UnityでHTTPリクエストを処理してみる -Qiita
C#でHTTPSサーバ(Ver. HttpListener) -Qiita
- 投稿日:2020-01-24T18:54:58+09:00
UnityでマルチスレッドなHTTPサーバー
Unity と Webアプリ間で データ連携したかったので、Unity で HTTPサーバーを作ってみました。後々、Unityで簡易な WebAPI を作りたいので、再利用しやすい方法をまとめています。
やりたいこと
・Unityで HTTPサーバーを作成
・利用するライブラリは System.Net.HttpListener
・マルチスレッド処理で描画負荷の影響を抑える
・ Get / Post リクエストを処理
・再利用したいので、サーバー処理とリクエスト処理のコンポーネントを分離
・UnityEvent を使って、インスペクタでイベントを管理
・通信テストは、Postman で行うSystem.Net.HttpListenerとは
HTTP 要求に応答する単純な HTTP プロトコルリスナーを作成できます。
.NET 標準クラスなので、Unityでも標準で使うことができます。
MS-DOC HttpListener クラス概要マルチスレッド処理の準備
Unity は描画負荷が高いので、安定させるために System.Net.HttpListener を別スレッドで実行します。
ただし、リクエスト内容によってはメインスレッドで描画を行う必要があります。Unity ではスレッドをまたいだ関数の実行はできないので、UnityMainThreadDispatcher を使って、メインスレッドのアクションを呼び出せるようにします。
下記からダウンロードし、Assetsに配置します。
UnityMainThreadDispatcher - GitHubHTTPサーバーのコンポーネントを作る
以下のようなスクリプトを書きます。
HTTPサーバーのコード
UnityHttpListener.csusing System; using System.IO; using System.Net; using System.Net.Http; using System.Text; using System.Threading; using UnityEngine; using UnityEngine.Events; public class UnityHttpListener : MonoBehaviour { private HttpListener listener; private Thread listenerThread; public string domain = "localhost"; public int port = 8080; [System.Serializable] public class OnGetRequestEvent : UnityEvent<HttpListenerContext> { } public OnGetRequestEvent OnGetRequest; [System.Serializable] public class OnPostGetRequestEvent : UnityEvent<HttpListenerContext> { } public OnPostGetRequestEvent OnPostRequest; void Start() { listener = new HttpListener(); listener.Prefixes.Add("http://" + domain + ":" + port + "/"); listener.AuthenticationSchemes = AuthenticationSchemes.Anonymous; listener.Start(); listenerThread = new Thread(startListener); listenerThread.Start(); Debug.Log("Server Started"); } private void OnDestroy() { listener.Stop(); listenerThread.Join(); } private void startListener() { while (listener.IsListening) { var result = listener.BeginGetContext(ListenerCallback, listener); result.AsyncWaitHandle.WaitOne(); } } private void ListenerCallback(IAsyncResult result) { if (!listener.IsListening) return; HttpListenerContext context = listener.EndGetContext(result); Debug.Log("Method: " + context.Request.HttpMethod); Debug.Log("LocalUrl: " + context.Request.Url.LocalPath); try { if (ProcessGetRequest(context)) return; if (ProcessPostRequest(context)) return; } catch (Exception e) { ReturnInternalError(context.Response, e); } } private bool CanAccept(HttpMethod expected, string requested) { return string.Equals(expected.Method, requested, StringComparison.CurrentCultureIgnoreCase); } private bool ProcessGetRequest(HttpListenerContext context) { if (!CanAccept(HttpMethod.Get, context.Request.HttpMethod) || context.Request.IsWebSocketRequest) return false; //メインスレッドでGetリクエストイベントを呼び出し UnityMainThreadDispatcher.Instance().Enqueue(() => OnGetRequest.Invoke(context)); return true; } private bool ProcessPostRequest(HttpListenerContext context) { if (!CanAccept(HttpMethod.Post, context.Request.HttpMethod)) return false; //メインスレッドでPostリクエストイベントを呼び出し UnityMainThreadDispatcher.Instance().Enqueue(() => OnPostRequest.Invoke(context)); return true; } private void ReturnInternalError(HttpListenerResponse response, Exception cause) { Console.Error.WriteLine(cause); response.StatusCode = (int) HttpStatusCode.InternalServerError; response.ContentType = "text/plain"; try { using(var writer = new StreamWriter(response.OutputStream, Encoding.UTF8)) writer.Write(cause.ToString()); response.Close(); } catch (Exception e) { Console.Error.WriteLine(e); response.Abort(); } } }Get / Post のリクエストがあれば、UnityMainThreadDispatcher を使って、メインスレッドでそれぞれのUnityEventを呼び出しています。
リクエスト処理のコード
RequestHandler.csusing System; using System.Net; using UnityEngine; public class RequestHandler : MonoBehaviour { private MyData data; private void Start() { data = new MyData(); } public void OnGetRequest(HttpListenerContext context) { var request = context.Request; var response = context.Response; response.StatusCode = (int) HttpStatusCode.OK; response.ContentType = "application/json"; string message = ""; if (request.QueryString.AllKeys.Length > 0) { foreach (var key in request.QueryString.AllKeys) { object value = request.QueryString.GetValues(key) [0]; Debug.Log("key: " + key + " , value: " + value); switch (key) { case "GetData": message = JsonUtility.ToJson(data); break; case "SetData": data.success = Convert.ToBoolean(value); message = JsonUtility.ToJson(data); break; } } } // message の内容をバイト配列に変換してレスポンスを返す var bytes = System.Text.Encoding.UTF8.GetBytes(message); response.Close(bytes, false); } public void OnPostRequest(HttpListenerContext context) { var request = context.Request; var response = context.Response; response.StatusCode = (int) HttpStatusCode.OK; response.ContentType = "application/json"; string message = ""; if (request.QueryString.AllKeys.Length > 0) { foreach (var key in request.QueryString.AllKeys) { object value = request.QueryString.GetValues(key) [0]; Debug.Log("key: " + key + " , value: " + value); switch (key) { case "GetData": message = JsonUtility.ToJson(data); break; case "SetData": data.success = Convert.ToBoolean(value); message = JsonUtility.ToJson(data); break; } } } // message の内容をバイト配列に変換してレスポンスを返す var bytes = System.Text.Encoding.UTF8.GetBytes(message); response.Close(bytes, false); } } [System.Serializable] class MyData { public bool success = false; }ここでは、UnityEvent を受けて、リクエストに対応したレスポンスを返しています。
- リクエストキー が
GetData
の場合: Unity 側のデータを返信- リクエストキー が
SetData
の場合: Unity 側のデータをリクエスト値に変更 -> 変更後のデータを返信レスポンスの ContentType はひとまず Json にしました。
データを保持する MyData クラスを用意して、JsonUtilityでJsonに変換しています。
通信テストができればいいので、 Get / Post どちらも同じ内容です。コンポーネントをアタッチする
空の GameObject を作り、準備していた UnityMainThreadDispatcher と 先ほど作った UnityHttpListener、RequestHandlerをアタッチします。
次にイベントをアタッチしていきます。
インスペクタのOnGetRequest
とOnPostRequest
下部の+
ボタンからイベントを追加し、それぞれにRequest Handler (Script)
コンポーネントを貼り付けます。
プルダウンメニューから
OnGetRequest
とOnPostRequest
それぞれの呼び出す関数を設定します。
通信テストのために、Unity を再生しておきます。
通信テストしてみる
Postman を使って、通信テストをします。
Postman Downloadインストールの手順や使い方はこちら
Postmanを使ったAPIテストのやり方 - IT業務で使えるプログラミングテクニックPostman で Get テスト
メソッドを
Get
にして、http://localhost:8080/?GetData=
を送ってみます。
Unity で作った MyData が Json で返ってきてました。
次は、
http://localhost:8080/?SetData=true
を送ってみます。
ちゃんと
success
がtrue
に変わりました。メソッドを Post にすれば、Postのテストが出来ます。
まとめ
Unityだけで Post/Get リクエストを処理出来るのは、とても楽ですね。プロトタイプでは充分使えます。
今回はローカルでテストしましたが、ngrok やlocaltunnel を使って外部公開すれば、Unity でも 簡易な WebAPI が作れそうです。
参考にしたサイト
UnityでHTTPリクエストを処理してみる -Qiita
C#でHTTPSサーバ(Ver. HttpListener) -Qiita
- 投稿日:2020-01-24T16:56:54+09:00
MotionLibraryで購入したアニメーションが動かない件の解決策の一例
はじめに
Unity内のモデルを動かすためのモーションを専用に扱ったモーションサイト、MotionLibraryというものがあります。多くは1つのモーションにつき3ドル~6ドルで、量は少ないですが無料のモーションもあります。
個人的感覚ですが、基本的にモーションの購入はAssetStoreで買うよりもこっちで買う方が安く済みますし効率が良いです。問題
MotionLibraryでモーションを購入すると、FBXファイルがインポートされます。
そのFBXファイルの中のアニメーションをanimatorにアタッチしてPlayしても、モデルがいい感じに動いてくれない時があります。僕の場合、本来なら購入したモーションの動き通りにモデルが動くはずですが、モデルがずっと座ったような状態が続きました。
画像は、想定通りに動かなかった時のスクショです。解決策
私の場合、以下のようにして解決できました。
問題のFBXファイルを選択
インポートしたFBXはMotionLibraryディレクトリの中にあるはずです。その中で、想定通りに動かないFBXを選択してください。
Inspectorの確認
選択したFBXのInspectorの中のRigを見てください。
Animation TypeがHumanoid以外になってませんか。
僕の場合、このAnimetionTypeがHumanoid以外だと上手く動きませんでした。
Animation Typeの編集
先ほどのAnimationTypeを、Humanoidに変更して、右下のApplyを押してください。これでいい感じに動くようになったはずです。
さいごに
別のインターネット上の記事では他の解決方法が紹介されていたりします。
もしもこの記事の事をしても上手くいかない場合、他の記事も参考にしてください。以上です。
- 投稿日:2020-01-24T12:34:17+09:00
UniRxでダブルクリック判定を3種類の方法で書いてみる
UnityのUniRxは便利ですし、ネットの記事を読むことで基本的な事は理解できるでしょう
しかし、いざ応用するとなるとなかなか感覚を掴めませんそこで、ダブルクリック判定は、UniRxの扱いを入門するのにちょうどいいものだと思い、
UniRxによるシングル、ダブルクリック判定法を3つ書いてみました今回実装するダブルクリック判定の仕様は次のようです
・シングルクリックとダブルクリックをどちらも判定できる
・ダブルクリック時に、シングルクリックは判定しない
・連打時の事も考える準備
まずはクリック判定だけ取っておきましょう
var click = this.UpdateAsObservable() .Where(_ => Input.GetMouseButtonDown(0));先に言っておきます。シングルとダブルの判定は同じObservable内でbool型で判定します
シングルクリックならfalseを流し
ダブルクリックならtrueを流すことにします1つ目
まずは、ネットで
UniRx ダブルクリック
など検索するといくらか目にする例ですvar dClick1 = click .TimeInterval() // 前のイベントとの時間差 .Select(v => v.Interval <= TimeSpan.FromSeconds(0.2f)) // ↑ シングルならfalse、ダブルならtrue .Buffer(2, 1) .Where(b => !(b[0] && b[1])); // ↑ 連打時の判定 (3連打以上は無視) // 出力 dClick1.Subscribe(b => Debug.Log(b ? "シングルクリック" : "ダブルクリック"); // ↑ 3項演算子によるシングル、ダブルクリック出力クリックをした時間差で判定する方法ですね
余計に前の状態をBuffer
で取ってこなくてはいけませんが、UniRx初心者が理解しやすい内容と言えます。
連打時の挙動は、
最初にシングルクリック、次にダブルクリックが流れ、
後はまったく流れないです2つ目
今度はストリームの合成の概念を使います
var dClick2 = click .ThrottleFirst(TimeSpan.FromSeconds(0.2f)) // シングルクリック判定の間隔。0.2秒以内に2つ以上流さない .SelectMany(Observable.Return(false) // シングルクリックはfalseを流す .Merge(click // ダブルクリック判定につなげる .Select(_ => true) // ダブルクリックはtrueを流す .Take(TimeSpan.FromSeconds(0.2f)).Take(1))); // ダブルクリック判定。trueを流す今度は、シングルクリック判定をしたら、シングルクリック判定をやめて
ダブルクリック判定を一定時間するという方法です
・Return
により、一旦シングルクリック判定を流し、
・Merge
により、ダブルクリック判定につなげます
・SelectMany
は
元々のObservbleにイベントが流れた時に、
引数に渡したObservableからイベントを取り始めます
ThrottleFirst
によって、シングルクリック判定を一定時間やめておき、
そのうちに、ダブルクリック判定をしています
判定時間を2回指定しなければいけないので少々気持ち悪いです連打時の挙動は
シングルクリックとダブルクリックの判定が交互に流れます
素直な挙動です3つ目
var dClick3 = click .Select(_ => false).Take(1) // シングルならfalse。一つだけ通す .Concat(click.Select(_ => true) // ダブルクリックならtrue .Take(TimeSpan.FromSeconds(0.2f)).Take(1)) // ダブルクリック判定 .RepeatSafe(); // 判定が終わったら繰り返し理解の難易度が高いオペレータを使いますが、一番シンプルな実装方法です
僕はこの方法が一番好きです
Concat
を使っていますがこれは
直前のObservableがOnCompletedしたら、引数に渡したObservableに切り替える
というものです
また、引数を渡さないこともでき、その場合は
直前にSelect
で指定したObservableを使う
(この場合は一時的にIObservable<IObservable<T>>
という型になる)
というようになりますシングルクリック判定が起こってから、
ダブルクリック判定をはじめて、
すべて終了すればRepeatSafe()
により最初から判定を行う
という感じです
RepeatSafe()
はOnCompletedが連続で流れれば繰り返しを終了します
Repeat()
なんて使うな!無限ループが怖いぞ!連打時には
シングルクリックとダブルクリックの判定が交互に行われるので
シングルクリックとダブルクリックが交互に出力されます出力
最後にDebug.Logの出力だけ書きます(1つ目のところでもちらっと書きました)
var doubleClick = dClick3; doubleClick.Subscribe(b => Debug.Log(b ? "ダブルクリック" : "シングルクリック");doubleClickにdClick1や2をいれれば、そちらの方の実装を試せます
最後
ダブルクリックというのは単純なようですが、
実際に判定を取る事を考えると奥が深い処理ですこの記事を書くにあたって、
Concat
を覚えられて良かったですUniRxはまだまだ学習コストが高いですね
- 投稿日:2020-01-24T10:21:17+09:00
【Unity】SoundManagerクラスを作ってみた。
初めに
このクラスは自分なりに欲しい機能を追加しただけなので足りないと感じた方はここからさらに拡張してみてください。
使用されているメソッド拡張&Managerクラス
- SKTool
- SaveLoadManager
これらは私が過去投稿したものを使用しております。これらのクラスを導入しないと動かない場合があります。ダウンロード先
コード
using System.Collections.Generic; using UnityEngine; using SKTool.Unity; using System; using System.Collections; public class SoundManager : MonoBehaviour { const string SAVE_FILE_NAME = "SoundInformation"; //セーブデータのファイル名 const float BGM_FADE_TIME = 1f; //フェードにかかる秒数 [Serializable] public class SoundInfo { public float MainVolume; public float BackMusicVolume; public float SfxVolume; } SoundInfo soundInfo = new SoundInfo(); [SerializeField, Header("BGM")] AudioClip backMusic; public float MainVolume { set { SoundVolumeApplication(); MainVolume = value; } get { return MainVolume; } } public float BackMusicVolume { set { SoundVolumeApplication(); BackMusicVolume = value; } get { return BackMusicVolume; } } public float SfxVolume { set { SoundVolumeApplication(); SfxVolume = value; } get { return SfxVolume; } } GameObject backMusicObject; public List<AudioSource> sfxList = new List<AudioSource>(); public AudioClip clip; private void Start() { Initialize(); } void Update() { if (Input.GetKeyDown(KeyCode.Space)) { GetComponent<AudioSource>().Play(); Debug.Log(Mathf.Clamp(5, 0, 2)); } } /// <summary> /// 初期化 /// </summary> private void Initialize() { //サウンドの設定データがなければ作成 if (SaveLoadManager.Load(SAVE_FILE_NAME) == null) { Debug.Log("セーブデータを作成"); soundInfo.BackMusicVolume = 1; soundInfo.MainVolume = 1; soundInfo.SfxVolume = 1; VolumeAttach(); } //サウンドの設定データを呼び出す else { soundInfo = (SoundInfo)SaveLoadManager.Load(SAVE_FILE_NAME); VolumeAttach(); } //BGMが最初からアタッチしてあればそちらを流す if (backMusic != null) { RequestBackMusic(backMusic); } } /// <summary> /// バックミュージックをリクエストする /// </summary> /// <param name="_backMusic">流したいBGM</param> /// <param name="_overwrite">今流れているBGMを上書きするかどうか</param> /// <returns>バックミュージックを流しているAudioSource</returns> public AudioSource RequestBackMusic(AudioClip _backMusic, bool _overwrite = false, float _pitch = 1f) { AudioSource audioSource = null; //何も流れていなければ再生 if (backMusicObject == null) { backMusicObject = new GameObject("BackMusicObject"); audioSource = backMusicObject.AddComponent<AudioSource>(); StartCoroutine(BackGroundMusicFade(_backMusic,audioSource,FadeMode.FadeIn)); audioSource.loop = true; audioSource.pitch = Mathf.Clamp(_pitch, -3, 3); } //バックミュージックを上書き else if (backMusicObject != null && _overwrite == true) { audioSource.Stop(); audioSource.loop = true; audioSource.time = 0; audioSource.pitch = Mathf.Clamp(_pitch, -3, 3); StartCoroutine(BackGroundMusicFade(_backMusic, audioSource, FadeMode.FadeOut)); } return audioSource; } /// <summary> /// バックミュージックを止める /// </summary> /// <returns>バックミュージックを止められたかの結果 true or false</returns> public bool StopBackMusic() { bool result = false; if (backMusicObject != null) { backMusicObject.GetComponent<AudioSource>().Stop(); result = true; } return result; } /// <summary> /// サウンドをリクエスとする /// </summary> /// <param name="_sound">流したいサウンド</param> /// <param name="_localPos">再生させたい位置</param> /// <param name="_loop">ループ</param> /// <param name="_pitch">ピッチ(-3~3)</param> /// <param name="_spatial">立体音響(0~1)</param> /// <returns>再生させているサウンドのAudioSource</returns> public AudioSource RequestSound(AudioClip _sound, Vector3 _localPos = default, bool _loop = false, float _pitch = 1f, float _spatial = 0f) { //Soundを鳴らす用のオブジェクトを生成 GameObject tempAudio = new GameObject("TempAudio"); //鳴らす位置を決める tempAudio.transform.position = _localPos; //AudioSourceをAddする AudioSource audioSource = tempAudio.AddComponent<AudioSource>() as AudioSource; //リストに格納 sfxList.Allocation(audioSource, true); //ループさせるかどうか audioSource.loop = _loop; //音声を追加 audioSource.clip = _sound; //音声を再生 audioSource.Play(); //ピッチ調整 audioSource.pitch = Mathf.Clamp(_pitch, -3, 3); //立体音響 audioSource.spatialBlend = Mathf.Clamp(_spatial, 0, 1); //ループでなければ if (_loop == false) { //音声が再生され終えたら削除 Destroy(tempAudio, _sound.length); } //AudioSourceを返す return audioSource; } /// <summary> /// 現在なっている音に変更後の音量を適用させる /// </summary> public void SoundVolumeApplication() { soundInfo.MainVolume = MainVolume; soundInfo.BackMusicVolume = BackMusicVolume; soundInfo.SfxVolume = SfxVolume; SaveLoadManager.Save(soundInfo, SAVE_FILE_NAME); //バックミュージックがなければ処理しない if (backMusicObject != null) { //音量再適用 backMusicObject.GetComponent<AudioSource>().volume = MainVolume * BackMusicVolume; } //音量再適用 for (int i = 0; i < sfxList.Count; i++) { if (sfxList[i] == null) { continue; } else { sfxList[i].volume = MainVolume * BackMusicVolume; } } } /// <summary> /// セーブしてあった値を読み込む /// </summary> public void VolumeAttach() { MainVolume = soundInfo.MainVolume; BackMusicVolume = soundInfo.BackMusicVolume; SfxVolume = soundInfo.SfxVolume; } public enum FadeMode { FadeIn, FadeOut } IEnumerator BackGroundMusicFade(AudioClip _clip,AudioSource _audioSource,FadeMode _fadeMode) { float volume; switch (_fadeMode) { case FadeMode.FadeIn: volume = MainVolume*BackMusicVolume; _audioSource.volume = 0; _audioSource.clip = _clip; _audioSource.Play(); while (_audioSource.volume >= volume) { _audioSource.volume += volume / Time.deltaTime / BGM_FADE_TIME; yield return null; } _audioSource.volume = volume; break; case FadeMode.FadeOut: volume = _audioSource.volume; while (_audioSource.volume <= 0) { volume -= Time.deltaTime / BGM_FADE_TIME; _audioSource.volume = volume; yield return null; } if (_clip != null) { StartCoroutine(BackGroundMusicFade(_clip, _audioSource, FadeMode.FadeIn)); } break; default: break; } yield break; } }まとめ
まだデバックが完全にすんではいませんし、機能の追加もまだしたいですがとりあえずできているとこまで載せます。
不具合などありましたらご連絡ください。