本投稿は2024年9月時点の内容になります。アップデートにより変更となる場合があります。また環境によって違いがあると思いますのであくまで参考として、ご了承ください。
様々な書籍、ブログや動画を参考にさせていただきました。多すぎて一つ一つはお紹介できませんが感謝です。
初心者の自分がUnity ソフトウエアでゲームを作ってみました。とりあえずシンプルなものということでモグラたたきに挑戦です。ゲーム作ってみるかという感じになったときに、いいタイミングで某ゲームのイベントシナリオ内ミニゲームにモグラたたきが実装されていたのでUIとかエフェクトとか、諸々の仕様をぱくって参考にして作ってみましたよ。様々なHowToの中の選択肢のひとつとして、同じ初心者さんの参考になればよいです。
\ チェック /
目次
今回はゲームにBGMをつけていきます。
今回はゲームにBGMをつけていきたいと思います。
前回の「効果音をつける」を踏まえて、BGMをつけていきます。
\ ゲームに効果音をつけるをチェック /
本記事のポイント
- BGMをつけることができる
- シングルトンパターンをつかって、シーンを跨いでも途切れることなくBGMを流す方法
AudioClip 音素材の準備
今回はユニティちゃん公式の「キャラクターソング・アルバム Vol.1『UNITE IN THE SKY』」を使わせていただきました。
ScriptableObjectでAudioClipを管理する
今回も効率よくBGMを使えるようにScriptableObjectを使ってデータベース化していきます。
ScriptableObjectの使い方はこちらも参考にしてみて下さい。
以下スクリプトです。
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu]
public class BGMSettingDB : ScriptableObject
{
public List<BGMSettingData>BGMSettingList=new List<BGMSettingData>();
public BGMSettingData FindBGMSettingDataBYName(string name)
{
BGMSettingData bgmSettingData = BGMSettingList.Find(BGMSettingData => BGMSettingData.Name == name);
if(bgmSettingData == null)
{
Debug.Log("指定された名前がステータスリストに見当たりません");
}
return bgmSettingData;
}
}
[System.Serializable]
public class BGMSettingData
{
public string Name;
public AudioClip BGM;
public float StartTime;
public float EndTime;
[Range(0f,1f)]
public float Volume;
public bool IsLoop;
}
スクリプトざっくり解説
- (6-20)ScriptableObjectのデータベース化の処理
- (6)「作成」メニューに表示
- (11-19)データベースに登録したオーディオクリップを含むBGMSettingDataを名前で取得するメソッド
- (25-31)BGMSettingDataの項目
- (27,28,31)今回はBGMなのである開始時間と終了時間でループできる仕様にします。そのパラメータです。
オーディオクリップデータベースの作成
- プロジェクト上で右クリック>作成>「BGMSettingDB」作成をクリック 任意の名前で作成します。
- 今回はひとまずメイン用、ポーズ時用、ゲームオーバー時用のBGMを用意しました。
カウントダウンで音を出したいので、例としてCountDownオブジェクトに付けてみます。
BGMManagerを作成
BGMをあつかうBGMManagerを準備します。
BGMManageの作成
- ヒエラルキー上に任意の名前で空のオブジェクトを作成します。今回は「BGMManager」としました。
- BGMManagerにAudioSourceコンポーネントを追加します。
- スクリプト「BGMManager」を作成してアタッチします。
スクリプトからBGMを再生する
以下スクリプトです。
using System.Collections;
using System.Collections.Generic;
using UnityEditor.AssetImporters;
using UnityEngine;
public class BGMManager : MonoBehaviour
{
[SerializeField] AudioSource audioSource;
[SerializeField] BGMSettingDB bgmSettingDB;
//現在のBGMの名前
string currentBGMName;
//BGMSettingDataの設定を取得するためのフィールド
float startTime;
float endTime;
bool isLoop;
//ひとつ前のBGMの名前と一時停止した時間
string previousBGMName;
float previousBGMPauseTime;
//再生フラグ
bool isPlay;
//初期化 再生フラグをfalse
public void InitBGMManager()
{
isPlay = false;
GameManager.Instance.SetBGMManager(this);
}
//BGMのセット
public void SetBGM(string name)
{
//BGMSettingDataの取得
BGMSettingData settingData = bgmSettingDB.FindBGMSettingDataBYName(name);
float startTime = settingData.StartTime;
float endTime = settingData.EndTime;
string newBGMName=settingData.Name;
AudioClip newBGM=settingData.BGM;
//再生していたオーディオクリップがあった場合は名前を覚えておく
if (audioSource.clip != null)
{
previousBGMName = currentBGMName;
}
//現在のBGMの名前を新たにセットしたBGMの名前に変更
currentBGMName = newBGMName;
//BGMSettingDataの開始時間を取得
if (0 <= startTime && startTime <= newBGM.length)
{
this.startTime = startTime;
}
else
{
this.startTime=0;
Debug.Log("開始時間は0に設定されました。");
}
//BGMSettingDataの終了時間を取得
if (0 <= endTime && endTime <= newBGM.length)
{
this.endTime = endTime;
}
else
{
this.endTime = newBGM.length;
Debug.Log("終了時間はクリップの最終時間に設定されました。");
}
//BGMSettingDataのループ設定を取得
this.isLoop = settingData.IsLoop;
//BGMSettingDataボリュームを取得
audioSource.volume = settingData.Volume;
//AudioSourceコンポーネントにクリップをセット
audioSource.clip=newBGM;
//終了時間より開始時間が大きいときは
//先頭に戻れるようにAudioSourceコンポーネントのループをOnに
//それ以外の時はOffにする
if (endTime < startTime)
{
audioSource.loop=true;
}
else
{
audioSource.loop=false;
}
}
//BGM再生のメソッド
public void PlayBGM(string name)
{
SetBGM(name);
audioSource.time = startTime;
audioSource.Play();
isPlay = true;
}
//一時停止のメソッド
public void PauseBGM()
{
previousBGMPauseTime = audioSource.time;
StopBGM();
PlayBGM("pause");
}
//再開のメソッド(一時停止前のBGMを再生)
public void UnPauseBGM()
{
StopBGM();
PlayBGM(previousBGMName);
audioSource.time=previousBGMPauseTime;
}
//停止のメソッド
public void StopBGM()
{
audioSource.Stop();
isPlay=false;
}
//再生の処理更新(ループ設定のチェック)
public void UpdateAudioPlayTime()
{
if (isPlay)
{
CheckAudioPlayTime();
}
}
//終了時間を迎えた際のループの具体的な処理
public void CheckAudioPlayTime()
{
//再生時間が終了時間を過ぎたら再生の更新
if ((startTime<=endTime && endTime <= audioSource.time)
|| (endTime <= audioSource.time && audioSource.time < startTime))
{
switch (isLoop)
{
//ループ設定がtrueなら開始時間に戻って再生
case true:
audioSource.time = startTime;
break;
//ループ設定がfalseなら停止
case false:
audioSource.Stop();
break;
}
}
}
// Update is called once per frame
void Update()
{
UpdateAudioPlayTime();
}
private void OnEnable()
{
InitBGMManager();
}
}
スクリプトざっくり解説
- (11)現在のBGMの名前
- (13-15)BGMSettingDataの設定を取得するためのフィールド
- (17,18)ひとつ前のBGMの名前と一時停止した時間
- (20)再生フラグ
- (26)GameManagerに自身を渡す
- (29-86)BGMの設定
- (32-36)BGMSettingDataの取得
- (39-42)再生していたオーディオクリップがあった場合は名前を覚えておく
- (43)現在のBGMの名前を新たにセットしたBGMの名前に変更
- (47-55)BGMSettingDataの開始時間を取得
- (57-67)BGMSettingDataの終了時間を取得
- (70,72)BGMSettingDataのループ設定とボリュームを取得
- (74)AudioSourceコンポーネントにクリップをセット
- (78-85)設定で終了時間より開始時間の方が大きいときは、先頭に戻れるようにAudioSourceコンポーネントのループをOnに、それ以外の時はOffにする
- (88-95)BGM再生のメソッド
- (99-103)一時停止のメソッド
- (105-110)再開のメソッド(一時停止前のBGMを再生)
- (112-116)停止のメソッド
- (118-124)再生の処理更新(ループ設定のチェック)
- (127-146)終了時間を迎えた際のループの具体的な処理
- (130-145)再生時間が終了時間を過ぎたら再生の更新
いろいろ盛り込んだので少し長いですね。見づらくてすみません。
つづいてGameManagerを変更します。
public class GameManager : Singleton<GameManager>
{
public GameState CurrentGameState { get; set; }
public float PlayTime { get; private set; }
BGMManager BGMManager;
EnemyManager enemyManager;
EffectManager effectManager;
//省略
public void SetBGMManager(BGMManager BGMManager)
{
this.BGMManager = BGMManager;
}
//省略
private void Gamestart()
{
PlayTime = 0;
BGMManager.PlayBGM("main");
}
//省略
private void Pause()
{
PauseUIController.OpenUI();
BGMManager.PauseBGM();
}
IEnumerator Resume()
{
PauseUIController.CloseUI();
BGMManager.UnPauseBGM();
yield return new WaitForSecondsRealtime(0.8f);
UpdateCurrentState(GameState.active);
}
private void Retry()
{
BGMManager.StopBGM();
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
UpdateCurrentState(GameState.gamestart);
}
private void GameOver()
{
enemyManager.RemoveAllEnemies();
effectManager.RemoveAllEffects();
GameOverUIController.OpenUI();
BGMManager.PlayBGM("gameover");
}
//省略
}
スクリプトざっくり解説
- BGMManagerのメソッドを使って、各状況に応じてBGMを再生、一時停止、停止します。
ゲーム再生するとBGMが流れたと思います。しかしリトライでLoadSceneするとBGMが流れなくなってしまいました。
どうやらDontDestroyOnLoadすればいいらしいので、以前やったシングルトンパターンを使ってみます。
今回の問題点
- LoadSceneするとBGMが流れなくなってしまいました
解決方法
- BGMManagerをDontDestroyOnLoadすればいいのでシングルトンパターンを使う
- よりシンプルな場合はAudioSourceコンポーネントの「ゲーム開始時に再生」をオンにしてもいい
BGMManagerにジェネリックシングルトンを適用する
BGMManagerをシングルトンパターンにするのは簡単です。ゲームマネージャーのときに作ったジェネリックシングルトンを適用していきます。シングルトンパターンについてはこちらをどうぞ。
\ シングルトンパターンの解説の実装例 /
以下スクリプトの変更点です。
public class BGMManager : Singleton<BGMManager>
{
//省略
public void InitBGMManager()
{
isPlay = false;
}
//省略
}
スクリプトざっくり解説
- MonoBehaviourクラスの代わりにジェネリックシングルトンSingletonを継承して、不必要になったGameManager.Instance.SetBGMManager(this)を削除。
- ※ジェネリックシングルトンクラスを実装している場合に限ります。内容はシングルトンパターンで確認してください。
つづいてGameManagerです。
public class GameManager : Singleton<GameManager>
{
public GameState CurrentGameState { get; set; }
public float PlayTime { get; private set; }
//省略
private void Gamestart()
{
PlayTime = 0;
BGMManager.Instance.PlayBGM("main");
}
//省略
private void Pause()
{
PauseUIController.OpenUI();
BGMManager.Instance.PauseBGM();
}
IEnumerator Resume()
{
PauseUIController.CloseUI();
BGMManager.Instance.UnPauseBGM();
yield return new WaitForSecondsRealtime(0.8f);
UpdateCurrentState(GameState.active);
}
private void Retry()
{
BGMManager.Instance.StopBGM();
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
UpdateCurrentState(GameState.gamestart);
}
private void GameOver()
{
enemyManager.RemoveAllEnemies();
effectManager.RemoveAllEffects();
GameOverUIController.OpenUI();
BGMManager.Instance.PlayBGM("gameover");
}
//省略
}
スクリプトざっくり解説
- BGMManagerをシングルトンにしたので、メソッドはBGMManager.Instance.で呼び出します。
ゲーム再生してみます。今度はリトライ後も問題ないと思います。
まとめ
まとめ
- BGMをスクリプトから鳴らすときはAudioSource.Playを使うといい
- AudioSource.timeを使うと区間を決めた再生や、そのループ再生などができる
- ScriptableObjectを使ってAudioClipを管理すると便利
- シーンまたぎ(LoadScene)でBGMが途切れてしまう場合はシングルトンパターンを使ってDontDestroyOnLoadすればいい
\ Unityのスクリプトを書くのに役立ちます /
もっと早く教えてほしかった!Unity C#入門
MARU
\ 初学に使った書籍です /
動画×解説でかんたん理解! Unityゲームプログラミング超入門
大角 茂之/大角 美緒
おすすめ記事
Unity ソフトウエアでゲーム制作#1モグラたたき編(28.ゲームに効果音…
Unity ソフトウエアでゲーム制作#1モグラたたき編(27.エフェクト2パ…
Unity ソフトウエアでゲーム制作#1モグラたたき編(26.エフェクトその1)
Unity ソフトウエアでゲーム制作#1モグラたたき編(25.プレハブバリア…
Unity ソフトウエアでゲーム制作#1モグラたたき編(24.Buttonで…
Unity ソフトウエアでゲーム制作#1モグラたたき編(23.シンプルなアニ…