【Unity】RPGを作るチュートリアルその114 オーディオの再生や停止を管理するクラスの作成

【Unity】RPGを作るチュートリアルその114 オーディオの再生や停止を管理するクラスの作成

シンプルなRPGをUnityで作るチュートリアルシリーズの114回目です。

第113回ではBGMや効果音のオーディオファイルをインポートしました。

今回はオーディオの再生や停止を管理するクラスを作成していきます。

 

 

制作環境

MacBook Pro 2023 Apple M2 Max

Unity6 (6000.0.30f1) Silicon

 

作業内容と順序

シンプルなRPGを作る上でどんな作業が必要か、どんな順番で作っていくと良さそうか、別ページで検討しました。基本的にこの流れに沿って進めていきます。

 

チュートリアルの一覧

このシリーズ全体の一覧は以下のページにまとめています。

 

前回の内容

前回はBGMや効果音のオーディオファイルをインポートしました。

 

オーディオの再生や停止の流れ

オーディオの再生や停止は、コード内から処理を呼び出せるようにします。オーディオ全体を管理するクラスを作成し、BGMを再生するメソッド、効果音を再生するメソッドを実装します。実際の再生処理については、BGMを管理するクラス、効果音を管理するクラスに分けて、それぞれで処理を行うようにします。

BGMを管理するクラス、効果音を管理するクラスのそれぞれで、Addressablesを使って再生する対象のオーディオをロードします。BGMの場合はフィールドなどでの戦闘後、戦闘直前に再生していたポイントから再度再生できるようにします。チャンネルが2つあるので、

  • マップ用チャンネル
  • 戦闘用チャンネル

のように振り分けても良いのですが、今回はBGMを停止させるタイミングで再生位置を取得して辞書フィールドにキャッシュするようにします。

今回は実装しませんが、ボイス付きのゲームならボイス用にも管理用クラスを作っておくと良いかと思います。

 

オーディオの再生や停止を管理するクラスの作成

オーディオの管理を行うクラスではゲームオブジェクトを生成するため、MonoBehaviourを継承したクラスとして作成します。シーン内のクラスからアクセスできるように、FadeManagerなどと同様にシングルトンのクラスとして使えるようにしたいと思います。

必要な機能としては、

  • BGMや効果音の再生機能
  • フェード機能
  • AudioSourceのゲームオブジェクトを管理する機能
  • AddressablesからのAudioClip取得機能

です。ひとつのクラスに入れると処理が多くなりそうなので、BGMを管理する機能、効果音を管理する機能を別のクラスに分離したいと思います。これらのクラスもMonoBehaviourとして実装し、シングルトンのオーディオ管理のクラスの子オブジェクトにすることで、シーンロード時に削除されないようにします。親オブジェクトでDontDestroyOnLoad()が設定されていれば、子オブジェクトも削除されないようになります。

Projectウィンドウから「Assets/Scripts」のフォルダを開き、新しくフォルダを作成します。名前は [Audio] にしました。

フォルダの作成
フォルダの作成

 

作成した「Audio」のフォルダに移動し、MonoBehaviourのスクリプトファイルを作成します。名前は [AudioManager] にしました。

オーディオ全体を管理するクラス
オーディオ全体を管理するクラス

 

作成した「AudioManager」の中身は以下のように記載しました。

シングルトンのクラスにするため、FadeManagerなどと同じようにstaticなインスタンスにアクセスできるようにしています。

「BgmManager」や「SeManager」への参照をフィールドとして保持しています。Inspectorウィンドウからアサインできるように[SerializeField]をつけています。これらのクラスについては、「AudioManager」の子オブジェクトとして作成したゲームオブジェクトにアタッチするようにしたいと思います。

もしシーン内で「BgmManager」や「SeManager」が作成されていない場合はSetUpReferences()のメソッドにて動的にゲームオブジェクトを作成してからコンポーネントとしてアタッチするようにしています。

PlayBgm()ではBGMの再生を行います。このクラス内で再生処理を行うのではなく、BGM関連の処理を担当する「BgmManager」の再生処理を呼び出しています。外部から呼び出す際には、再生する対象のBGM名、記憶した再生位置から再生するかどうかのフラグ、ループするかどうかのフラグを渡すようにしています。

StopBgm()では個別にBGM名を指定して停止処理を行います。StopAllBgm()では全てのBGMを停止します。音量をフェードアウトさせる時間も指定できるので、短い時間で指定することで「ブツッ」と途切れることを防げます。

FadeAudio()のコルーチンはBGMの音量を徐々に変えるためのコルーチンで、BGMや効果音のフェードアウト時に使います。今回は実装しませんが、再生時のフェードインなどもこのコルーチンを使うことで実現できます。

「BgmManager」や「SeManager」についてはこの後の作業で実装するため、現時点ではコンパイルエラーが出ます。

 

チャンネルの情報を保持するクラスの作成

チャンネルの情報を保持するクラスについても作成していきます。BGMの場合、チャンネルに対応するAudioSourceが再生中かどうか、AudioSourceの再生位置はどこか、といった形でチャンネルの情報をまとめて管理できると便利なので、情報保持用のクラスを作成したいと思います。

必要な情報としては、

  • チャンネルID
  • 再生中のオーディオ名
  • チャンネルに対応するAudioSource

で、これらをフィールドとして保持します。また、再生中かどうかをすぐ確認できるようにメソッドも用意します。

Projectウィンドウから「Assets/Scripts/Audio」のフォルダにて、MonoBehaviourのスクリプトファイルを作成します。名前は [AudioChannelInfo] にしました。

チャンネル情報を保持するクラス
チャンネル情報を保持するクラス

 

作成した「AudioChannelInfo」の中身は以下のように記載しました。

上で列挙した項目をフィールドとして追加しました。再生中かどうかのフラグについてはIsPlaying()のメソッドを使って判定します。

 

BGMを管理するクラスの作成

次にBGMを管理するクラスを作成します。必要な機能としては、

  • BGMのチャンネル情報を保持するリストやBGMごとの再生位置の辞書
  • BGMの音量、チャンネルのゲームオブジェクト名、チャンネル数などの定義
  • 再生機能と停止機能
  • 再生位置を取得するメソッド
  • 停止しているAudioSourceを取得するメソッド

などですかね。必要に応じて機能を追加していきたいと思います。

Projectウィンドウから「Assets/Scripts/Audio」のフォルダにて、MonoBehaviourのスクリプトファイルを作成します。名前は [BgmManager] にしました。

BGMを管理するクラス
BGMを管理するクラス

 

作成した「BgmManager」の中身は以下のように記載しました。

Addressablesを使用してBGMのファイルをロードするため、「using UnityEngine.AddressableAssets;」と「using UnityEngine.ResourceManagement.AsyncOperations;」のusingディレクティブを入れています。

「_audioChannelInfoList」のフィールドでは生成済みのチャンネル情報のリストを保持します。チャンネル情報内にAudioSourceの情報も含まれているので、これを使って再生するチャンネルを指定していきます。

「_bgmPlaybackPositions」の辞書ではBGM名と再生位置を対応させています。BGMごとに、最後にどこまで再生していたかの情報を保持するようにして、次回再生するタイミングで同じ場所から再生するようにします。例えばフィールドを歩いている際に、エンカウントによる戦闘が発生した場合、戦闘終了後にフィールドのBGMを同じ位置から再生できるようにします。

「Volume」はBGMの再生音量の定義値、「ChannelPrefix」はBGMチャンネル用のゲームオブジェクトの名前、「ChannelCount」はBGMで使用するチャンネル数の定義値です。今回のチュートリアルでは音量調整機能を実装しないので固定で指定していますが、実際にリリースする際にはBGMや効果音、ボイスの音量調整機能を作成して、それぞれで設定値を保持するのがベストです。

Play()のメソッドではBGM名を使ってAddressablesの機能でAudioClipをロードしてAudioSourceにセットします。ロード完了時にhandle.Completedのイベントが呼ばれるので、そのタイミングでAudioSourceの情報をセットして再生します。

このクラスの初期化の前にPlay()が呼ばれることも考慮して、SetUpAudioSource()のメソッドを使ってAudioSourceの初期化を行います。このメソッドでは「_audioChannelInfoList」のリスト内に対象のチャンネルIDがあるかどうかを確認し、なければゲームオブジェクトを作成してAudioSourceをアタッチしています。

Stop()のメソッドでは個別のBGMを停止させ、StopAllBgm()のメソッドでは全てのBGMを停止させます。停止時には音量のフェードアウトを行うので、StopBgmProcess()のコルーチンを呼び出しています。今回は基本的にBGMを止める時には全てのBGMを止めたいので、「AudioManager」のStopAllBgm()をメインに使用していきます。

BGMを停止させる際にはKeepPlaybackPosition()のメソッドを呼んで再生位置を取得しています。再生位置はAudioSourceのフィールドから取得できるので、BGM名とその値をセットで辞書に登録します。再生時に同じ場所から再生する時にはGetPlaybackPosition()のメソッドから再生位置を取得してそれをセットする流れになります。

再生に使用するAudioSourceを取得する際にはGetAudioChannelInfo()のメソッドを呼び出します。このメソッドの中では停止しているチャンネルのIDを取得するGetStoppedAudioSourceChannelId()を呼んでいて、そのIDを元にチャンネル情報を取得してAudioSourceを取得しています。(そのままAudioSourceを返すようにしても良かったかも?)

 

効果音を管理するクラスの作成

続いて効果音を管理するクラスを作成します。必要な機能としては、

  • 生成済みのAudioSourceのゲームオブジェクトを保持するリスト
  • 効果音の音量、チャンネルのゲームオブジェクト名の定義
  • 再生機能と停止機能
  • 停止しているAudioSourceを取得するメソッド

です。BGMの時と同様に必要に応じて機能を追加していきたいと思います。

Projectウィンドウから「Assets/Scripts/Audio」のフォルダにて、MonoBehaviourのスクリプトファイルを作成します。名前は [SeManager] にしました。

効果音を管理するクラス
効果音を管理するクラス

 

作成した「SeManager」の中身は以下のように記載しました。

こちらも同様にAddressablesを使用して効果音のファイルをロードするため、「using UnityEngine.AddressableAssets;」と「using UnityEngine.ResourceManagement.AsyncOperations;」のusingディレクティブを入れています。

効果音に関しても「_audioChannelInfoList」のフィールドを実装してオーディオのチャンネル情報を保持するようにします。個別のチャンネルで管理するというよりは、AudioSourceの再生状態を取得する目的が大きいです。

「Volume」は効果音の再生音量の定義値、「ChannelPrefix」は効果音チャンネル用のゲームオブジェクトの名前の定義値です。

Play()では指定した名前の効果音を再生します。ループ再生もできるようにしていますが、基本はループしない単発での運用がメインになるかと思います。効果音に関しては再生位置は記憶させず、毎回ファイルの冒頭から再生するようにします。

StopAllSe()では作成済みの全てのチャンネルについて再生を停止します。停止する際にはBGMと同様にフェードアウトさせるため、StopSeProcess()のコルーチンを呼ぶようにします。

GetAudioSource()のメソッドでは再生対象のAudioSourceを取得します。GetStoppedAudioSource()のメソッドを使って停止しているチャンネルのAudioSourceを取得するようにします。BGMと異なり、作成済みの全てのチャンネルが再生中の場合は新しくゲームオブジェクトを作成し、AudioSourceをアタッチします。新しいチャンネルIDはGetNewChannelId()のメソッドで割り当てます。

 

オーディオ再生と停止の処理

今回実装したオーディオ関連の機能を確認するため、タイトル画面でBGMを再生、停止する機能を実装したいと思います。場面に応じたBGMや効果音の設定は次回以降に実装していきます。

タイトル画面を表示したタイミングでタイトル画面用のBGMを再生するようにします。既存の「TitleManager」に再生処理を実装します。

実装内容はシンプルで、StartProcess()のコルーチン内からPlayBgm()を呼び出します。BGMの名前だけ指定して1行で呼び出せるようにしているのでこの後の作業も楽になるかと思います。

 

タイトル画面ではシーンを遷移する際にBGMを停止します。「TitleStartController」にて停止処理を呼び出しましょう。

新しくコルーチンとしてSwitchSceneProcess()を作成しました。既存のOnFinishedFade()のメソッド内に記載していた処理をSwitchSceneProcess()のコルーチンに移し、StopAllBgm()の処理を呼び出しています。BGMのフェードアウトを待った後、シーンをロードするようにします。これは「Game」シーンで読み込んだマップのBGMまでフェードアウトしないようにするためです。

 

スクリプトのアタッチ

作成したスクリプトをアタッチしていきましょう。

「Title」シーンにて、Hierarchyウィンドウから最上位に空のゲームオブジェクトを作成します。名前は [AudioManager] にしました。「FlagManager」などと同様に、アタッチしたコンポーネント内でDontDestroyOnLoad()を呼ぶので最上位に配置しています。

また、「AudioManager」の子オブジェクトとして空のゲームオブジェクトを2つ作成し、名前をそれぞれ [BgmManager][SeManager] にします。

最上位にゲームオブジェクトを作成
最上位にゲームオブジェクトを作成

 

「AudioManager」では参照のアサインが必要になるので、先に子オブジェクトから同名のスクリプトファイルをアタッチしていきます。Hierarchyウィンドウから「BgmManager」を選択し、Inspectorウィンドウにて「BgmManager」のスクリプトをアタッチします。

BGMを管理するクラス
BGMを管理するクラス

 

Hierarchyウィンドウから「SeManager」を選択し、同様にInspectorウィンドウにて同名のスクリプトをアタッチします。

効果音を管理するクラス
効果音を管理するクラス

 

Hierarchyウィンドウから「AudioManager」を選択し、同名のスクリプトをアタッチします。また、参照についてもアサインしておきます。

オーディオを管理するクラス
オーディオを管理するクラス

 

動作確認

スクリプトをアタッチしてシーンを保存したら動作を確認していきましょう。

今回の確認はシンプルで、「Title」シーンからゲームを実行して、タイトル用のBGMが再生されればOKです。確認時はミュート状態を解除しておくと分かりやすいです。ミュートしたまま「あれ……再生されないのはなんで……?」とコードを眺めるのは精神的にダメージがありますからね(1敗)

タイトル画面にて、BGMが再生されること、ループしていることを確認します。また、ゲームを開始した時にタイトル画面のBGMがフェードアウトして聞こえなくなることも確認しましょう。

チュートリアル内で作成した音源はボリューム控えめにしているので、必要に応じて音量の定義値などを変更していただければと思います。

画像だと分かりにくいですがBGMが流れています。
画像だと分かりにくいですがBGMが流れています。

 

今回のブランチ

 

まとめ

今回はオーディオの再生や停止を管理するクラスを作成しました。とてもよく使う機能なので、プロジェクト内でどこからでも簡単に呼び出せるようにしておくと便利です。

次回はマップのBGMを再生する機能を実装します。

 

     

ゲーム開発の攻略チャートを作りました!

CTA-IMAGE

「ゲームを作ってみたいけど、何から手を付けていいか分からない!」


そんなお悩みをお持ちの方向けに、todoがアプリをリリースした経験を中心に、ゲーム作りの手順や考慮すべき点をまとめたe-bookを作成しました。ゲーム作りはそれ自体がゲームのように楽しいプロセスなので、「攻略チャート」と名付けています。


ゲームを作り始めた時にぶつかる壁である「何をしたら良いのか分からない」という悩みを吹き飛ばしましょう!