あると便利なサウンドマネージャー(SoundManager)
前回は
- 手軽に効果音が管理・再生できる
- 再生する効果音の指定にわかりやすい別名がつけられる
まで作りました。
前回の記事:
今回は
- 同じ音源が同一・または近辺フレームで鳴って音割れするのを避ける
を実装して完成を目指します。
また、作ったマネージャーオブジェクトをどのようにして他オブジェクトからアクセスさせるか、も詳しく解説していきます。
ではやっていきましょう。
音割れ対策機能をSoundManagerに追加する
さて、音割れ対策ですが、「そもそも本当に音割れなんてするの?」という方もいらっしゃるかもしれませんね。
というより、音割れした状態を作ってからじゃないと音割れ対策ができているかどうかはよくわからないですよね。
音割れさせるのは簡単です。 同じ音を同時に重ねて再生すれば良いです。
前回作った SoundTestスクリプトを一部修正(改悪)しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
using System.Collections; using System.Collections.Generic; using System.Runtime.CompilerServices; using UnityEngine; public class SoundTest : MonoBehaviour { [SerializeField] private SoundManager soundManager; //サウンドマネージャー void Update() { if (Input.GetMouseButtonDown(0)) //左クリック { soundManager.Play("左クリック"); //サウンドマネージャーを使用して効果音再生 } if (Input.GetMouseButtonDown(1)) //右クリック { soundManager.Play("右クリック"); //サウンドマネージャーを使用して効果音再生 } if (Input.GetKeyDown(KeyCode.X)) //Xキー { soundManager.Play("Xキー"); } if (Input.GetKeyDown(KeyCode.Z)) //Zキー { soundManager.Play("Zキー"); } if (Input.GetKeyDown(KeyCode.C)) //Cキー { soundManager.Play("Cキー"); } } } |
元はこの状態ですが、Cキーの箇所だけ改悪してしまいましょう。 InputGetKeyDown の Down を消して InputGetKey にしてしまいます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
using System.Collections; using System.Collections.Generic; using System.Runtime.CompilerServices; using UnityEngine; public class SoundTest : MonoBehaviour { [SerializeField] private SoundManager soundManager; //サウンドマネージャー void Update() { if (Input.GetMouseButtonDown(0)) //左クリック { soundManager.Play("左クリック"); //サウンドマネージャーを使用して効果音再生 } if (Input.GetMouseButtonDown(1)) //右クリック { soundManager.Play("右クリック"); //サウンドマネージャーを使用して効果音再生 } if (Input.GetKeyDown(KeyCode.X)) //Xキー { soundManager.Play("Xキー"); } if (Input.GetKeyDown(KeyCode.Z)) //Zキー { soundManager.Play("Zキー"); } if (Input.GetKey(KeyCode.C)) //Cキー { soundManager.Play("Cキー"); } } } |
これでCキーのみ「押した時」ではなく、「押している間」になるので、Cキーを押し続けると、毎フレーム同じ効果音が再生されることになります。
では、スクリプトの保存をしてUnityEditor側で確認をしてみましょう。
※注意!! 音量が大きく、または音割れをする可能性が高いです。 パソコンからの音声出力をだいぶ下げてから確認することをおすすめします。
では、Cキーを押しっぱなしにしてみるとどうでしょう? 音割れに違い状態になったんじゃないでしょうか(割り当てている音によってはあまり実感出来ないかもしれません、ちょっと再生時間の長めの効果音をセットしておくとわかりやすいです)
再生時間の管理
さて、この「音割れ」を対策するために、音源(AudioClip)毎に再生タイミングを覚えておき、前回再生タイミング と 現在時間 を比較して、ある程度の間隔が開いてない場合は再生しない。というようにスクリプトを修正します。
SoundManagerスクリプトの、音源を管理しているSoundDataに
- 前回音源再生時間→
float playedTime;
をメンバ変数に追加します。
1 2 3 4 5 6 7 8 9 |
public class SoundManager : MonoBehaviour { [System.Serializable] public class SoundData { public string name; public AudioClip audioClip; public float playedTime; //前回再生した時間 } |
次に、SoundManager自体に
- 一度再生してから、次再生出来るまでの間隔(秒)→
float playableDistance;
を[SerializeField]付きでメンバ変数として追加します。
1 2 3 |
//一度再生してから、次再生出来るまでの間隔(秒) [SerializeField] private float playableDistance = 0.2f; |
そして、Playメソッドで、再生時間の保持と、前回再生時間と今回時間を比較するように修正をします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
//指定された別名で登録されたAudioClipを再生 public void Play(string name) { if (soundDictionary.TryGetValue(name, out var soundData)) //管理用Dictionary から、別名で探索 { if (Time.realtimeSinceStartup - soundData.playedTime < playableDistance) return; //まだ再生するには早い soundData.playedTime = Time.realtimeSinceStartup; //次回用に今回の再生時間の保持 Play(soundData.audioClip); //見つかったら、再生 } else { Debug.LogWarning($"その別名は登録されていません:{name}"); } } |
なお、この「現在時間」を取得するのに Time.realtimeSinceStartup
を使用しています。
これは、ゲームが起動してからの実経過時間が入っており、この「実」経過時間を使うことにより、超高速で動くPC(1000fps)と、低速で動くPC(15fps)でも 同じ挙動になるようにしています(逆に言うとフレームで管理してしまうとPCの性能によっては音割れが発生したり、しなかったりしてしまいます)
修正後のSoundManagerスクリプト全体は以下のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
using System.Collections; using System.Collections.Generic; using UnityEngine; public class SoundManager : MonoBehaviour { [System.Serializable] public class SoundData { public string name; public AudioClip audioClip; public float playedTime; //前回再生した時間 } [SerializeField] private SoundData[] soundDatas; //AudioSource(スピーカー)を同時に鳴らしたい音の数だけ用意 private AudioSource[] audioSourceList = new AudioSource[20]; //別名(name)をキーとした管理用Dictionary private Dictionary<string, SoundData> soundDictionary = new Dictionary<string, SoundData>(); //一度再生してから、次再生出来るまでの間隔(秒) [SerializeField] private float playableDistance = 0.2f; private void Awake() { //auidioSourceList配列の数だけAudioSourceを自分自身に生成して配列に格納 for (var i = 0; i < audioSourceList.Length; ++i) { audioSourceList[i] = gameObject.AddComponent<AudioSource>(); } //soundDictionaryにセット foreach (var soundData in soundDatas) { soundDictionary.Add(soundData.name, soundData); } } //未使用のAudioSourceの取得 全て使用中の場合はnullを返却 private AudioSource GetUnusedAudioSource() { for (var i = 0; i < audioSourceList.Length; ++i) { if (audioSourceList[i].isPlaying == false) return audioSourceList[i]; } return null; //未使用のAudioSourceは見つかりませんでした } //指定されたAudioClipを未使用のAudioSourceで再生 public void Play(AudioClip clip) { var audioSource = GetUnusedAudioSource(); if (audioSource == null) return; //再生できませんでした audioSource.clip = clip; audioSource.Play(); } //指定された別名で登録されたAudioClipを再生 public void Play(string name) { if (soundDictionary.TryGetValue(name, out var soundData)) //管理用Dictionary から、別名で探索 { if (Time.realtimeSinceStartup - soundData.playedTime < playableDistance) return; //まだ再生するには早い soundData.playedTime = Time.realtimeSinceStartup;//次回用に今回の再生時間の保持 Play(soundData.audioClip); //見つかったら、再生 } else { Debug.LogWarning($"その別名は登録されていません:{name}"); } } } |
では、スクリプトを保存してUnityEdtiorでプレイボタンを押して確認をしてみましょう。
Cキーを押しても、ある程度の間隔(PlayableDistance秒)で再生されるようになったのでは無いでしょうか。
とりあえず、本講座ではこれでSoundManagerに関しては一旦の完成ということになります。(非常にシンプルではありますが)
前回の冒頭にも書きましたが、便利オブジェクト(Manager)は、その時々で要件が変わってきます。
- ループ再生出来るようにしたい
- 外から好きなタイミングで停止出来るようにしたい
- 全効果音を停止出来るようにしたい
- フェードイン・フェードアウトでBGMを鳴らしたい
などなど。
あなたが思う最強のSoundManagerはぜひ、あなた自身で作ってみてください。
SoundManager 作り方のおさらい
SoundManagerの基本機能が完成しました。
今回はUnity上で動くものとして作りましたが、作り方・考え方は別のゲームエンジン等にも転用が可能な汎用的なものばかりになっているので、無駄になりにくいと思います。
また、SoundManagerに限らず、自分用(またはチーム用)の各種Manageクラス・便利処理は作っておくと、ゲームジャムやプロトタイプ作成等の時、開発速度をグンと上げることが出来て効果を発揮するので、色々と用意しておくのも良いのではないでしょうか。
また、SoundManagerへのアクセス方法としてさらに実用的な方法に関しても次の記事で解説しました。
Unityに元々あるインスペクターからではなく、シングルトン、サービスロケータなどを用いたManagerオブジェクトへのアクセス方法を身に付けたい方は次の記事も読んでみてください。
次の記事:
コメント