Parallels Desktop 35%OFF セール

【Unity】タワーディフェンス(24) BGMと効果音【クソゲー制作】

タワーディフェンスを作る(24)

Unity初心者が2Dタワーディフェンスを作っています。

今回はゲームにBGMと効果音をつけていきます。

環境
  • Mac mini (M1, 2020)
  • Unity 2022.3.36f1
目次

BGMと効果音をインポート

ゲーム内で使用する音楽と効果音は無料素材を使わせていただきます。今回、音楽は超有名な魔王魂様、効果音はいつもの効果音ラボ様からお借りしています。

素材をダウンロードしたら、Unityにインポートします。「Assets/Audios」フォルダを作成して、そこに音声ファイルをドラッグ&ドロップします。

タワーディフェンス240

AudioManagerスクリプトの作成

STEP

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()は効果音を鳴らすメソッドです。引数で効果音の種類を指定します。
STEP

ヒエラルキーで空のオブジェクトを作成し、名前を「AudioManager」にします。これにAudioManagerスクリプトをアタッチします。

STEP

AudioManagerオブジェクトのインスペクターでAudio Clipをアサインします。

タワーディフェンス241

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);
    // ここまで

    // ...省略...
}

さいごに

ゲームの音が鳴るようになりました。ほぼ完成です。いや、もう完成?

連載が長すぎて終わるタイミングがわからなくなってます。

でわでわ

タワーディフェンスを作る(24)

この記事が気に入ったら
いいね または フォローしてね!

シェアしてね

コメント

コメントする

目次