Unity初心者が2Dタワーディフェンスを作っています。
今回はゲームにBGMと効果音をつけていきます。
- Mac mini (M1, 2020)
- Unity 2022.3.36f1
BGMと効果音をインポート
ゲーム内で使用する音楽と効果音は無料素材を使わせていただきます。今回、音楽は超有名な魔王魂様、効果音はいつもの効果音ラボ様からお借りしています。
素材をダウンロードしたら、Unityにインポートします。「Assets/Audios」フォルダを作成して、そこに音声ファイルをドラッグ&ドロップします。

AudioManagerスクリプトの作成
BGMと効果音を管理するためのC#スクリプトAudioManager.csを新規作成します。
using UnityEngine;
using System.Collections;
public class AudioManager : MonoBehaviour
{
public static AudioManager Instance { get; private set; }
[Header("BGM Clips")]
[SerializeField] private AudioClip titleBGM; // タイトル画面用BGM
[SerializeField] private AudioClip[] stageBGMs = new AudioClip[5]; // 0:ステージ1, 1:ステージ2, ...
[SerializeField] private AudioClip gameOverBGM; // ゲームオーバー用BGM
[SerializeField] private AudioClip victoryBGM; // ステージクリア用BGM
[Header("SE Clips")]
[SerializeField] private AudioClip clickSE; // クリック音
[SerializeField] private AudioClip turretPlacementSE; // 砲台配置音
[SerializeField] private AudioClip turretUpgradeSE; // 砲台強化音
[SerializeField] private AudioClip turretSaleSE; // 砲台売却音
[SerializeField] private AudioClip turretFiringSE; // 砲弾発射音
[SerializeField] private AudioClip enemyDefeatSE; // 敵撃破音
[SerializeField] private AudioClip fortressDamageSE; // 城ダメージ音
private AudioSource bgmSource; // BGM用のAudioSource
private AudioSource seSource; // SE用のAudioSource
private float bgmDefaultVolume = 0.4f; // BGMのデフォルト音量
private float seDefaultVolume = 0.8f; // SEのデフォルト音量
private Coroutine bgmFadeCoroutine; // BGMフェード用のコルーチン
[SerializeField] private float bgmFadeDuration = 1f; // フェード時間(秒)
private void Awake()
{
// シングルトンパターンの実装
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
// BGM用のAudioSourceを追加
bgmSource = gameObject.AddComponent<AudioSource>(); // BGM用のAudioSourceを追加
bgmSource.loop = true; // BGMはループ再生
bgmSource.volume = bgmDefaultVolume; // 初期音量を設定
bgmSource.playOnAwake = false; // BGMは最初は再生しない
// SE用のAudioSourceを追加
seSource = gameObject.AddComponent<AudioSource>(); // SE用のAudioSourceを追加
seSource.loop = false; // SEはループしない
seSource.volume = seDefaultVolume; // 初期音量を設定
seSource.playOnAwake = false; // SEも最初は再生しない
}
else
{
Destroy(gameObject);
}
}
/// <summary>
/// ゲーム状態に応じてBGMを切り替える
/// </summary>
/// <param name="state">現在のゲーム状態</param>
/// <param name="currentStageId">現在のステージID</param>
public void PlayBGM(GameManager.GameState state, int currentStageId = 1)
{
AudioClip nextClip = null; // 次に再生するBGMクリップ
// ゲーム状態に応じてBGMを選択
switch (state)
{
case GameManager.GameState.Title:
nextClip = titleBGM;
break;
case GameManager.GameState.Preparing:
case GameManager.GameState.Paused:
StopBGM();
return;
case GameManager.GameState.Playing:
if (currentStageId >= 1 && currentStageId <= stageBGMs.Length)
nextClip = stageBGMs[currentStageId - 1];
break;
case GameManager.GameState.GameOver:
nextClip = gameOverBGM;
break;
case GameManager.GameState.Victory:
nextClip = victoryBGM;
break;
}
// BGMが変更されていない場合は何もしない
if (bgmSource.clip == nextClip && bgmSource.isPlaying) return;
// フェードアウトしてからBGMを切り替え
if (bgmFadeCoroutine != null) StopCoroutine(bgmFadeCoroutine);
bgmFadeCoroutine = StartCoroutine(FadeAndChangeBGM(nextClip));
}
/// <summary>
/// BGMを停止する
/// </summary>
public void StopBGM()
{
// フェードアウトしてBGMを停止
if (bgmFadeCoroutine != null) StopCoroutine(bgmFadeCoroutine);
bgmFadeCoroutine = StartCoroutine(FadeAndChangeBGM(null));
}
/// <summary>
/// BGMをフェードアウトしてから次のBGMに切り替える
/// </summary>
/// <param name="nextClip">次に再生するBGMクリップ</param>
private IEnumerator FadeAndChangeBGM(AudioClip nextClip)
{
float startVolume = bgmSource.volume; // 現在のBGMの音量
float t = 0f; // フェード時間の経過時間
while (t < bgmFadeDuration)
{
// 音量を徐々に下げる
t += Time.unscaledDeltaTime;
bgmSource.volume = Mathf.Lerp(startVolume, 0f, t / bgmFadeDuration);
yield return null;
}
bgmSource.Stop(); // BGMを停止
bgmSource.clip = nextClip; // 次のBGMクリップを設定
bgmSource.volume = bgmDefaultVolume; // デフォルト音量に戻す
if (nextClip != null)
{
bgmSource.Play(); // 次のBGMを再生
}
}
/// <summary>
/// SEを鳴らす
/// </summary>
/// <param name="type">鳴らすSEの種類</param>
public void PlaySE(SEType type)
{
AudioClip clip = null; // 鳴らすSEのクリップ
// SEの種類に応じてクリップを選択
switch (type)
{
case SEType.Click: clip = clickSE; break;
case SEType.TurretPlacement: clip = turretPlacementSE; break;
case SEType.TurretUpgrade: clip = turretUpgradeSE; break;
case SEType.TurretSale: clip = turretSaleSE; break;
case SEType.TurretFiring: clip = turretFiringSE; break;
case SEType.EnemyDefeat: clip = enemyDefeatSE; break;
case SEType.FortressDamage: clip = fortressDamageSE; break;
}
// SEのクリップがnullでなければ再生
if (clip != null)
{
seSource.PlayOneShot(clip);
}
}
/// <summary>
/// SEの種類を定義する列挙型
/// </summary>
public enum SEType
{
Click,
TurretPlacement,
TurretUpgrade,
TurretSale,
TurretFiring,
EnemyDefeat,
FortressDamage
}
}
- シングルトンパターンにします。いつものヤツですね。
Awake()
で、BGM用とSE用のAudioSourceをそれぞれ設定します。PlayBGM()
はゲーム状態(GameState
)に応じてBGMを切り替えるメソッドです。切替時にはフェードアウトをします。StopBGM()
はBGMを停止するメソッドです。こちらも停止時にフェードアウトします。FadeAndChangeBGM()
はBGMをフェードアウトして次のBGMを再生するメソッドです。PlayBGM()
とStopBGM()
から呼び出されます。PlaySE()
は効果音を鳴らすメソッドです。引数で効果音の種類を指定します。
ヒエラルキーで空のオブジェクトを作成し、名前を「AudioManager」にします。これにAudioManagerスクリプトをアタッチします。
AudioManagerオブジェクトのインスペクターでAudio Clipをアサインします。

BGMを鳴らす
BGMを鳴らすためにGameManagerスクリプトを修正します。たった1行です。
/// <summary>
/// ゲーム状態を変更する
/// </summary>
/// <param name="newState">新しいゲーム状態</param>
public void ChangeState(GameState newState)
{
previousState = CurrentState; // 前の状態を保存
CurrentState = newState;
// ここから追加
// BGMの再生
AudioManager.Instance.PlayBGM(newState, CurrentStageId);
// ここまで
// ...省略...
}
以上で、ゲーム状態とステージ番号に応じてBGMを切り替えてくれるようになりました。いえい。
効果音を鳴らす
効果音を鳴らすために、スクリプトの各所にPlaySE()
メソッドを追加します。
ボタンのクリック音
ステージ選択ボタン、スタート・ポーズボタン、再開ボタン、タイトルへ戻るボタンのクリック音を鳴らすために、GameManagerスクリプトを修正します。
/// <summary>
/// スタート・ポーズボタンが押されたときの処理
/// </summary>
private void OnStartPauseButtonClicked()
{
// ここから追加
// クリック音を鳴らす
AudioManager.Instance.PlaySE(AudioManager.SEType.Click);
// ここまで
// ...省略...
}
/// <summary>
/// 再開ボタンが押されたときの処理
/// </summary>
private void OnRestartButtonClicked()
{
// ここから追加
// クリック音を鳴らす
AudioManager.Instance.PlaySE(AudioManager.SEType.Click);
// ここまで
// ...省略...
}
/// <summary>
/// ステージを選択する
/// </summary>
/// <param name="stageId">選択するステージのID</param>
private IEnumerator SelectStageCoroutine(int stageId)
{
currentStageId = stageId; // ステージIDを設定
// ここから追加
// クリック音を鳴らす
AudioManager.Instance.PlaySE(AudioManager.SEType.Click);
// ここまで
// ...省略...
}
/// <summary>
/// タイトルへ戻るボタンが押されたときの処理
/// </summary>
public void OnBackButtonClicked()
{
// ここから追加
// クリック音を鳴らす
AudioManager.Instance.PlaySE(AudioManager.SEType.Click);
// ここまで
ChangeState(GameState.Title);
}
次のウェーブボタンのクリック音は、EnemySpawnerスクリプトを修正します。
/// <summary>
/// 次のウェーブへスキップする
/// </summary>
private void SkipToNextWave()
{
// ここから追加
// クリック音を鳴らす
AudioManager.Instance.PlaySE(AudioManager.SEType.Click);
// ここまで
// ...省略...
}
砲台選択ボタンのクリック音、砲台設置音
砲台選択ボタンのクリック音と砲台設置音を鳴らすために、TurretGeneratorスクリプトを修正します。
/// <summary>
/// 砲台を配置する
/// </summary>
/// <param name="gridPosition">配置するグリッド座標</param>
/// <param name="levelData">配置する砲台のレベルデータ</param>
private void PlaceTurret(Vector3Int gridPosition, TurretSetting.TurretLevelData levelData)
{
//ここから追加
// 砲台設置音を鳴らす
AudioManager.Instance.PlaySE(AudioManager.SEType.TurretPlacement);
//ここまで
// ...省略...
}
/// <summary>
/// 砲台アイコンを選択する
/// </summary>
/// <param name="turretIndex">選択する砲台のインデックス</param>
public void SelectTurret(int turretIndex)
{
//ここから追加
// クリック音を鳴らす
AudioManager.Instance.PlaySE(AudioManager.SEType.Click);
//ここまで
// ...省略...
}
砲台の強化音、売却音、発砲音
砲台の強化音、売却音、発砲音は、TurretControllerスクリプトを修正します。
/// <summary>
/// 攻撃
/// </summary>
private void Attack()
{
// ...省略...
// ここから追加
// 発砲音を鳴らす
AudioManager.Instance.PlaySE(AudioManager.SEType.TurretFiring);
// ここまで
}
/// <summary>
/// アップグレード開始時の処理
/// </summary>
private void StartUpgrade()
{
// ここから追加
// クリック音を鳴らす
AudioManager.Instance.PlaySE(AudioManager.SEType.Click);
// ここまで
// ...省略...
}
/// <summary>
/// アップグレード完了後の処理
/// </summary>
/// <param name="newTurretLevelData">アップグレード後のレベルデータ</param>
private void FinishUpgrade(TurretSetting.TurretLevelData newTurretLevelData)
{
// ここから追加
// アップグレード音を鳴らす
AudioManager.Instance.PlaySE(AudioManager.SEType.TurretUpgrade);
// ここまで
// ...省略...
}
/// <summary>
/// 売却コルーチン
/// </summary>
public IEnumerator SellTurretCoroutine()
{
// ...省略...
// ここから追加
// クリック音を鳴らす
AudioManager.Instance.PlaySE(AudioManager.SEType.Click);
// ここまで
// ...省略...
// TurretGeneratorに通知して砲台の設置済みリストから削除
TurretGenerator turretGenerator = FindObjectOfType<TurretGenerator>();
if (turretGenerator != null)
{
turretGenerator.ResetTurretPlacement(transform.position);
}
// ここから追加
// 売却音を鳴らす
AudioManager.Instance.PlaySE(AudioManager.SEType.TurretSale);
// ここまで
// ...省略...
}
敵の撃破音
EnemyControllerスクリプトを修正します。
/// <summary>
/// 敵を撃破する
/// </summary>
private void DefeatEnemy()
{
// ここから追加
// 敵の撃破音を鳴らす
AudioManager.Instance.PlaySE(AudioManager.SEType.EnemyDefeat);
// ここまで
// ...省略...
}
城のダメージ音
FortressControllerスクリプトを修正します。
/// <summary>
/// ダメージを受ける
/// </summary>
public void TakeDamage()
{
// ここから追加
// ダメージ音を鳴らす
AudioManager.Instance.PlaySE(AudioManager.SEType.FortressDamage);
// ここまで
// ...省略...
}
さいごに
ゲームの音が鳴るようになりました。ほぼ完成です。いや、もう完成?
連載が長すぎて終わるタイミングがわからなくなってます。
でわでわ
コメント