【Unity入門】汎用サウンドマネージャー(Sound Manager)の作り方 前編

Unity入門講座


Unityの本格ゲーム制作講座はこちら
【30日間の全額返金保証付き】

ゲームを作るに当たって、最後に手を入れがちですが、とても重要なのが「サウンド」です。

Unityには標準のサウンド機能(AudioClip,AudioSource,AudioListener)があるのですが、これをそのまま使うのはちょっと大変です。そのため、サウンドマネージャーSoundManager)を作成し、使用することが多いです。

今回はそのサウンドマネージャーの作り方について解説していきたいと思います。


【Unity入門の森の最新ゲーム開発講座もお届け】

プロジェクトの作成

ではやってみましょう。 Unityで空の2Dプロジェクトを作成し、(プロジェクト名は何でも良いですが、ここでは「SoundManagerLesson」としました)

Unityエディタが起動しましたら、Assetsフォルダに「Sounds」フォルダを作っておきます。

では、まず音素材をインポートします。

音素材インポートしてAudio ClipとしてUnityに登録

音素材はすでにお持ちの方は好きな音素材を4ファイルほどインポートしていただければいいですが、講座上は 効果音ラボ様の、ボタン・システム音[1] から、

  • 決定、ボタン押下1.mp3
  • 決定、ボタン押下2.mp3
  • 決定、ボタン押下3.mp3
  • 決定、ボタン押下4.mp3

をダウンロードしてきて使います。
初学者の方は揃えたほうがわかりやすいと思いますので同じく上記ファイル4つをダウンロードしておいてください。(以降、この4ファイル名を想定した内容になりますので、ご自分で用意した音素材を利用するかたは、適宜読み替えてください。)

では、音素材は先程作った Assets/Sounds フォルダにドラッグアンドドロップしてインポートします。

この1ファイル1ファイルが「AudioClip」です。これが「音源データ」、ちょっと古いですが「音楽CD」だと置き換えるとわかりやすいです。

Audio Source、Audio Clip、AudioListenerの役割の違い 実際に音を鳴らしてみる

では、そのうちの一つ、「決定、ボタン押下1」をSceneビューに直接ドラッグアンドドロップしてみましょう。

自動的に作成されるGameObject(決定、ボタン押下1)を選択して、Inspectorで確認をするとAudio Source コンポーネントがついてることがわかります。

この、「Audio Source」は言わば「CDプレイヤー兼スピーカー」です。

では、Play On Awake(起動したら再生)にチェックが入っていることを確認して再生ボタンを押してみましょう。

どうでしょうか。 音が鳴りましたね。

「音が聞こえる」 ということは、「」があるということです。
UnityのSceneには最初から「」の役割をする「AudioListener」がMain Camera に準備されています。

まとめるとこうなります

AudioClip 音源、音楽CD
AudioSource CDプレイヤー、スピーカー
AudioListener

さて、音は鳴りましたがゲームの音はシーンが始まってすぐ鳴らしたいものばかりじゃないですよね。

では、「決定、ボタン押下1オブジェクト」をサウンドテスト用オブジェクトに修正して、マウスクリックで音が再生されるようにしましょう。

サウンドテスト用オブジェクトの作成 Playメソッドでスクリプトから音を鳴らす

まず、この自動的に作られたオブジェクト名では勝手が悪いので名前を変更します。

Hierarchyで「決定、ボタン押下1オブジェクト」 を選択し、名前を「SoundTest」に修正します。

次に、AddComponentからNew Script で 「SoundTest」スクリプトを追加します。

スクリプトは以下のように修正します。

左クリックでInspectorで指定したclip1が、右クリックでclip2が再生されるようになっています。

スクリプトから音を任意のタイミングで再生したい場合、通常このようににAudioSourceとAudioClipを何らかの方法(上記スクリプトではSerializeFieldにして、Inspectorから)で指定しできるようにし、
実際に再生するときはAudioSourceclipに再生するAudioClipを指定してから、Playメソッドを呼んでいます。(※PlayOneShotというメソッドを使う方法もあります)

では、スクリプトを忘れずに保存して、UnityEditor上のInspectorで、AudioSourceAudioClipを指定しましょう。

Source(AudioSource)には、自分自身についている AudioSourceを、Clip1、2にはそれぞれAssets/Sounds から 「決定、ボタン押下1」と「決定、ボタン押下2」を設定しておきます。

また、このままだと実行と同時にAudioSourceに最初からセットされている音が再生されてしまうので、Play On Awake のチェックは外しておきます。

では、実行して確認してみましょう。

左クリックと右クリックでそれぞれ指定した音が再生されると思います。

ここで色々試すとわかるのですが

①右クリックと左クリックを同時にクリックしてもなるのはどちらかの音
②効果音が鳴っている間にクリックをすると、前の効果音は中断されて、新しい音が再生される

という状態です。

サウンドマネージャーを作る

さて、単純に音を鳴らすだけなら比較的すぐ出来ましたが、ゲームで音を鳴らしたいケースはたくさんありますよね。

  • ジャンプボタンを押したらジャンプするので、そのときにはジャンプ音を鳴らしたい
  • コインを取ったら、コインを取得した音を鳴らしたい
  • ボタンを押したら、クリック音を鳴らしたい
  • ゴールしたら、ファンファーレを鳴らしたい
  • 敵のHPが0になったら、爆発音を鳴らしたい

大体は、「プレイヤーが起こした行動」に対して「効果音」がなります。

そのため、基本的にはスクリプトで複数のAudioSourceとAudioClipを管理して音を鳴らす必要があります。

しかし例えば

  • コインを取ったら、コインを取得した音を鳴らしたい

を例に上げると、コインが画面上に100個あったとして、全て(100個)のコインオブジェクト一つ一つにAudioSourceコンポーネントが必要でしょうか?

コインを連続して取得した場合には音階が上がっていく演出なんかもいいですよね!
でも、全てのコインに音階の違うAudioClipを持たせて置くことに?

やってみるとわかるのですが、この方法はとても手間がかかります(もちろん、その方法が正解のパターンもあります。 音の出どころが重要な3Dゲームなどでは特に)

なので、

AudioClip(音源)とAudioSource(CDプレイヤー)を一括で管理するオブジェクト

が欲しくなります。 これが俗に言う「SoundManager(サウンドマネージャー)」です。

SoundManagerのような汎用的なオブジェクトは、要求する機能が人それぞれ(ゲームそれぞれ)なので、100人いれば100の細かく機能が異なるSoundManagerが出来たりします。

もちろん本講座で作成したSoundManagerをそのまま使っても良いですが、適宜カスタマイズするのも勉強になると思います。

サウンドマネージャーに必要な3機能 音の管理・音の名称設定・音割れ防止

今回作るSoundManagerに必要な要件としては

  • 手軽に効果音が管理・再生できる
  • 再生する効果音の指定にわかりやすい別名がつけられる
  • 同じ音源が同一・または近辺フレームで鳴って音割れするのを避ける

とします。

  • 手軽に効果音が管理・再生できる

はそのままですが、

  • 再生する効果音の指定にわかりやすい別名がつけられる

は、ちょっとめずらしいかもしれません。

先程作ったSoundTestスクリプトを例に上げると

再生するものが決まっている場合などはこれでもいいんですが、効果音候補がたくさんある、または逆に「ここで音を鳴らす」と決めてはいるけれど、まだ効果音を探していない。
等、具体的に「どの音源を再生する」とは決められない時などに

このように、文字列の別名で再生する効果音を指定しておいて、後でこの「右クリック」や「左クリック」の文字列に対してペアのAudioClipを指定する。という機能です。(詳しくは後述)

また

  • 同じ音源が同一・または近辺フレームで鳴って音割れするのを避ける

は、何も考えずにAudioSourceとAudioClipで効果音をつけているとありがちなのですが、だいぶ前に前述した例の

  • コインを取ったら、コインを取得した音を鳴らしたい

のような、1つとは限らないアイテムの取得音で、画面上に大量にあった場合にそのまま愚直に効果音を再生すると、同じ音が一気に再生されることになります。

するとどうなるかというと、良くて「音量が急に大きく」なってしまいますし、悪くて「音割れ」して、ガビガビしたノイズが出てしまい、ゲームをプレイしてくれている人に不快感を与えてしまいかねません。

この辺りも考慮したサウンドマネージャーを作っていきたいと思います。

SoundManagerオブジェクト作成

では、早速作っていきましょう。

SceneビューでCTRL+SHIFT+Nキーを押して新規オブジェクトを作成し、名前は「SoundManager」にしておきます。

次に、AddComponentからNew Script で 「SoundManager」スクリプトを追加します。

では、このSoundManagerスクリプトを修正していきます。

まず、

  • 手軽に効果音が管理・再生できる

を作っていきましょう。

AudioClip(音源)を再生するためのAudioSourceを準備します。
AudioSourceはCDプレイヤー兼スピーカーでしたね。

基本、1つのAudioSourceでは1つのAudioClipしか再生出来ません。逆にいうとAudioSourceの数だけ、(理論上)効果音が同時に鳴らせることになります。
そのため、最初にある程度の数用意&管理しておきたいので

このように、SoundManagerオブジェクトにAddComponent(コンポーネントの追加)でAudioSourceを指定数(今回は20個)追加して、配列で管理するようにしておきます。

次に、このAudioSourceは1回使ったら終わりではなく、使いまわす必要があります。
しかし、音は再生時間が短いものもあれば長いものもありますよね。
10秒の効果音を再生している時に、別の音を鳴らすからといって10秒の効果音再生中のAudioSourceを使ってしまえば、当然効果音が途中で切れてしまいます。

そこで「未使用(使用済み)のAudioSource」を取得するメソッド「GetUnusedAudioSource」メソッドを用意しましょう。

AudioSourceにはisPlayingというプロパティがあり、それを見ることで現在再生中かどうかの判断が出来ます。
これを使用し、isPlaying == false、すなわち未使用(または使用済み)のAudioSourceを見つけてreturn で返却しているわけです。
なお(レアケースですが)全ての用意したAudioSource(今回は20個)が全て使用中。という場合はnullを返却するようにしてあります。

Linq

なお、このGetUnusedAudioSourceメソッドですが、LinqLanguage-Integrated Query)という書き方(+ expression bodied)で書くと、なんと以下の1行になります。

※ただし、使用するためには using に System.Linq; の追加が必要になります

Linqは配列やListなどの操作をする拡張メソッドが多数用意されており、使いこなせると非常に便利ですので、どこかの機会で勉強してみるのも良いと思います。

最後に、引数で渡された音源(AudioClip)を未使用のAudioSourceを使用して再生する、Playメソッドを作ります。

もし、未使用のAudioSourceをローカル変数audioSourceに代入し、未使用のAudioSourceが見つからない場合(nullだった場合)は何もせずにreturnをするようにしています。

これで、

  • 手軽に効果音が管理・再生できる

を満たしたSoundManager プロトタイプVer1 の完成です。

現時点でのSoundManagerスクリプトは以下の通りです(最初から用意されているStartメソッド、Updateメソッドは使用しないので削ってあります)

では早速使ってみましょう。

先ほどのSoundTestスクリプトを修正し、SoundManagerを使って効果音が再生できるようにしてみましょう。

もう自分自身のAudioSourceは使う必要がないので(SoundManagerが管理してくれるものを使うので)、AudioSourceのメンバ変数は削除し、代わりにSoundManagerをInspectorから指定出来るようにしています。
また、効果音の再生もSoundManagerPlayメソッドAudioClipを指定して再生するように修正しました。

では2つのスクリプト(SoundManager,SoundTest)を忘れずに保存し、UnityEditorに戻りましょう。

SoundTestのInspectorでSoundManagerがセット出来るようになっていますので、HierarchyのSoundManagerオブジェクトをドラッグアンドドロップでセットしておきます。

また、自身のAudioSourceはもう使用しませんので、右クリック→Remove Componentで削除してしまいましょう。

では、再生ボタンを押して確認してみましょう。

AudioSourceの管理をSoundManagerに任せているので、先程の修正前のSoundTestスクリプトの

①右クリックと左クリックを同時にクリックしてもなるのはどちらかの音
②効果音が鳴っている間にクリックをすると、前の効果音は中断されて、新しい音が再生される

という状態が解消され、マウスボタンを連打をしたり、右クリックと左クリックを同時にしたりすると音が多重に重なって再生されているのが分かると思います。

別名文字列による効果音再生

では次に

  • 再生する効果音の指定にわかりやすい別名がつけられる

の為に、文字列(string)→nameと、実際の音源(AudioClip)→audioClipのペアを管理するクラス、SoundDataを作り、そのSoundDataを配列(SoundData[])で管理するsoundDatasもメンバ変数で用意します。
なお、このSoundDataクラスSoundManagerのインナークラスとして作るので、追加場所はSoundManagerクラスになります。

SoundDataクラスの宣言の前に[System.Serializable]を付けることで、Inspectorから値がセット出来るようになります。

まだ別名用の枠を用意しただけですが、スクリプトを保存し、UnityEditorに戻って確認をしてみましょう。

SoundManagerスクリプトのInspectorにSound Datas という項目が増え、Sizeを指定すると(今回は4にしました)別名(Name)音源(Audio Clip)がペアでセット出来るようになりましたね!

右クリック 決定、ボタン押下1
左クリック 決定、ボタン押下2
Zキー 決定、ボタン押下3
Xキー 決定、ボタン押下4

例では上記の様にセットしましたが、適当に入れ替えても構いません。
(というより、後でここを入れ替えるだけで再生される効果音を切り替えられるようにするのが最終目的です)

では、実際にこの「別名」を指定することで「別名」とペアになっている音源(AudioClip)を再生されるように管理していきましょう。

この、SoundData配列変数 soundDatasから毎回name(名前)をキーにして探しても良いのですが、仮に100個のSoundDataがsoundDatasに格納されたと考えると、目的の別名を探すのに最速で1個め、最遅で100個目なので、平均で50個探すことになります。登録件数が多ければ多いほど探索に時間がかかるようになってしまいますね(O(n)というやつです。)

そのため、nameキーとしてSoundDataを管理するDictionary→(Dictionary<string,SoundData>)である、soundDictionaryを先にメンバ変数に用意し

AwakeメソッドでsoundDatasを全てAddするようにします。

では、別名指定による効果音再生処理としてPlayメソッドを追加します。

まず、DictionaryのTryGetValueというメソッドを使っています。

これは、Dictonary内に第1引数で指定したキー(今回はstring:name)が見つかれば、第2引数で指定したout変数に格納してtrueを返却、見つからない場合はfalseを返却するというものです。

無事見つかった場合(trueの場合)は、既にあるAudioClipを受け取るPlayメソッドを呼んで再生、
見つからなかった場合(falseの場合)はログに警告を表示しています。

メソッドのオーバーロード(Overload):多重定義

「え!? Playメソッドって既にあるのに、またPlayメソッドを追加するの!?」と思った方もおられるかもしれませんね。

もちろん、これはタイピングミスでも、そしてエラーにもなりません。正しい処理です。
C#では引数で区別さえできればメソッド名が重複しても構わないからです(C#に限らず他の言語でもありますが)

これをオーバーロード:多重定義と呼びます。

もちろんメソッド名を分けても良いといえば良いのですが(PlayClipとPlayWithNameなど)、処理の意味合いが全く同じ(今回の場合、どちらも効果音の「再生」)の場合、逆にメソッド名は同じにしてしまって、引数として渡すものによって処理が分岐されるほうが使う側からすると理解しやすいのでこのオーバーロードを使いました(今回は正直微妙なラインですが・・・)

用法用量を守って正しくお使いください。

これで

  • 再生する効果音の指定にわかりやすい別名がつけられる

を満たしたSoundManager プロトタイプVer2 が完成しました。

SoundManagerスクリプト全体は以下になります。

 

では、先ほどのSoundTestスクリプトをさらに修正し、別名を指定して効果音が再生できるようにしてみましょう。

今、SoundTestスクリプトは以下の状態のはずです

AudioClipは直接持つ必要がなくなったので、10~14行目のメンバ変数 clip1とclip2は削除します。
そして、soundManagerのPlayメソッドでAudioClipを指定していましたが、別名を指定するように修正します。

“Xキー”,”Zキー”という別名でもAudioClipを登録してあるので、そちらも確認できるようにコードを追加しましょう。

ではスクリプトを保存して右クリック、左クリック、Xキー、Zキーでそれぞれ効果音が再生されるか確認してみましょう。

どうでしょうか。SoundManagerで管理された別名と音源のペアで効果音が再生出来るようになりましたね!

Cキーも追加したくなった

さて、ゲームを作っていると、予定より機能を追加することになるなんてことは日常茶飯事です。

Cキーも追加されることになったとします。しかし、効果音は4つ全て使ってあります。今やっている実装が終わったら、新しい効果音を探してきて、それからスクリプトを修正して・・・

となんやかんややっていて効果音を付け忘れ、Cキーだけ音が鳴らない。
なんてことは回避したいですよね。

そういう時におすすめしているのが、効果音再生の処理を先に書いてしまう事です。

このようにSoundTestスクリプトに、Cキーが押された場合の処理を追加してしまいます。

これでスクリプト保存をして、UnityEditorで実行してみましょう。

Cキーを押すと

登録されていない旨の警告(Warning)は出ますが、ゲームが中断したりはしませんから、他部分も作り込んでいくことが出来ます。

このように、警告が表示される状態にまずしておけば効果音付け忘れもなくなりますよね。

もちろん、警告を警告のまま放っておいては意味がありませんから、折を見てちゃんと効果音を探してSoundManagerに追加しましょう。

なお、同じ音源(AudioClip)を複数の別名につけても構いません。
一旦、適当な音を付けておいて、最後に正しくしていく。 という方法も取れますね。

Size を増やして枠を増やしたら、”Cキー”と音源のペアも追加しましょう

次回

今回は

  • 手軽に効果音が管理・再生できる
  • 再生する効果音の指定にわかりやすい別名がつけられる

まで作りました。

次回は

  • 同じ音源が同一・または近辺フレームで鳴って音割れするのを避ける

を実装して完成を目指したいと思います。

次回の記事:

【Unity入門】汎用サウンドマネージャー(Sound Manager)の作り方 後編
あると便利なサウンドマネージャー(SoundManager) 前回は 手軽に効果音が管理・再生できる 再生する効果音の指定にわかりやすい別名がつけられる まで作りました。 前回の記事: 今回は...

【Unity入門の森の最新ゲーム開発講座もお届け】

コメント

タイトルとURLをコピーしました