【Unity】2Dシューティングを作る ~音を鳴らそう~

unity

プレイするゲームがないなら、自分で作ればいいじゃない!

ということで Unity 初心者が「ゲームを作る」ゲームをやっていきます!

今回は「2Dサウンド(BGM、SE)対応」の記事です

 

スポンサーリンク

内容

「BGM、SE」を再生するクラス【SoundManager】の作成

 

音を鳴らすには

音に必要なのは「耳と音源」になります

 

Audio Listener

Unity での、Main Camera に始めから追加されている

2Dゲームの場合はこのままで問題ない

 

Audio Source

Unity での音源、音を鳴らしたいオブジェクトに追加する

今回はサウンドマネージャーのスクリプトから追加します

 

Audio Clip

音のデータ、これを AudioSource で再生する

BGM等のサイズの大きいデータは「ロードタイプをストリーミング」にしておくといい

 

BGMとSEのスクリプト

別に分けなくてもいいかもしれないが…

 

SE用

AudioSource を設定して、再生するだけのシンプルなもの

public class SeSound
{
    AudioSource source = null;
    
    // オーディオソースの設定
    public void SetAudioSource(AudioSource _source)
    {
        source = _source;
    }

    // 再生
    public void Play(AudioClip _clip, float _volume)
    {
        source.PlayOneShot(_clip, _volume);
    }
}

 

AudioSource.PlayOneShot(AudioClip, float)

音データ音量(0~1)を設定する
※音量の部分は省略可能(その場合は1になる)

重複して再生ができる:SE1を再生中にSE2を再生してもSE1が消えない

 

BGM用

SEの処理に停止関係とフェードの処理を追加している

public class BgmSound
{
    // BGM状態
    private enum eBgmState
    {
        BGM_STOP = 0,
        BGM_PLAY,
        BGM_FADEIN,
        BGM_FADEOUT,
    };

    AudioSource source = null;              // オーディオソース

    float fade = 0;                         // フェード処理用
    float fadeTime = 0;                     // フェード時間
    float volume = 0;                       // ボリューム
    bool isPause = false;                   // 一時停止中か
    eBgmState state = eBgmState.BGM_STOP;   // サウンドの状態
    

    // 更新
    public void Update()
    {
        if (isPause) { return; }

        // フェードイン
        if(state == eBgmState.BGM_FADEIN)
        {
            fade += Time.deltaTime;
            if(fade >= fadeTime)
            {
                state = eBgmState.BGM_PLAY;
                SetVolume(volume);
            }
            else
            {
                source.volume = Mathf.Lerp(0, volume, fade / fadeTime);
            }
        }
        // フェードアウト
        else if (state == eBgmState.BGM_FADEOUT)
        {
            fade += Time.deltaTime;
            if (fade >= fadeTime)
            {
                Stop();
            }
            else
            {
                source.volume = Mathf.Lerp(volume, 0, fade / fadeTime);
            }
        }
    }

    // オーディオソースの設定
    public void SetAudioSource(AudioSource _source)
    {
        source = _source;
    }

    // 再生中か
    public bool IsPlaying()
    {
        // 一時停止も判定に入れる?
        //if (isPause)
        //{
        //    return false;
        //}

        return source.isPlaying;
    }

    // 再生
    public void Play(AudioClip _clip, float _volume, float _fade, bool _loop)
    {
        fade = 0;
        fadeTime = _fade;
        volume = _volume;
        isPause = false;

        source.clip = _clip;
        source.volume = 0;
        source.loop = _loop;
        source.Play();

        state = eBgmState.BGM_FADEIN;
    }

    // フェードアウト
    public void FadeOut(float _fade)
    {
        fade = 0;
        fadeTime = _fade;
        state = eBgmState.BGM_FADEOUT;
    }
    
    // 一時停止
    public void Pause()
    {
        isPause = true;

        source.Pause();
    }
    // 一時停止解除
    public void UnPause()
    {
        isPause = false;

        source.UnPause();
    }

    // 停止
    public void Stop()
    {
        source.Stop();

        state = eBgmState.BGM_STOP;
    }

    // 音量設定
    public void SetVolume(float _volume)
    {
        if (state != eBgmState.BGM_PLAY)
        {
            volume = _volume;
            return;
        }

        // 再生中の音は設定する
        source.volume = _volume;
    }
}

 

AudioSource.Play(ulong)

ディレイタイムを設定する
※ディレイタイムの部分は省略可能(その場合は0になり、即再生される)

重複して再生されない:BGM1を再生中にBGM2を再生するとBMG1が消える

 

SoundManager のスクリプト

BGMとSEのクラスを管理するクラス

まだ単純な操作しかできない、適宜欲しい機能を追加する

※ LikeDictionary は後述

public class SoundManager : MonoBehaviour
{
    public static SoundManager Instance { get; private set; } = null;   // シングルトン

    [SerializeField] LikeDictionary<string, AudioClip> soundDictionary; // サウンドデータ

    BgmSound[] bgmTbl = new BgmSound[2];                     // BGM
    SeSound se;                                              // SE
    List<string> nameList = new List<string>();              // SE用の再生リスト

    float bgmVolume = 1.0f;                                  // BGMのボリューム
    float seVolume = 1.0f;                                   // SEMのボリューム

    void Awake()
    {
        if (Instance == null)
        {
            // インスタンスがない場合は代入
            Instance = this;

            // シーン切り替え時に消さないようにする
            DontDestroyOnLoad(this.gameObject);
        }
        else
        {
            // ある場合は2つ目なのでゲームオブジェクトを消す
            Destroy(this.gameObject);

            return;
        }

        // LikeDictionaryの初期化
        soundDictionary.Init();

        // オーディオソースの設定
        for (int i=0; i<bgmTbl.Length; ++i)
        {
            bgmTbl[i] = new BgmSound();
            bgmTbl[i].SetAudioSource(gameObject.AddComponent<AudioSource>());
        }
        {
            se = new SeSound();
            se.SetAudioSource(gameObject.AddComponent<AudioSource>());
        }

        nameList.Clear();
    }

    void LateUpdate()
    {
        // BGMの更新
        foreach (BgmSound _bgm in bgmTbl)
        {
            _bgm.Update();
        }

        // 再生があれば再生
        foreach (string _name in nameList)
        {
            Play(_name);
        }

        // 再生リストを空に
        nameList.Clear();
    }

    // BGM再生
    public void PlayBgm(string _name, float _fade = 0, bool _loop = true)
    {
        foreach (BgmSound _bgm in bgmTbl)
        {
            if (!_bgm.IsPlaying())
            {
                _bgm.Play(soundDictionary.Value(_name), bgmVolume, _fade, _loop);
                break;
            }
        }
    }
    
    // BGM切り替え
    public void ChangeBgm(string _name, float _fade = 0, bool _loop = true)
    {
        // 今鳴っているものをフェードアウト
        foreach (BgmSound _bgm in bgmTbl)
        {
            if (_bgm.IsPlaying())
            {
                _bgm.FadeOut(_fade);
                break;
            }
        }

        // 再生
        PlayBgm(_name, _fade, _loop);
    }

    // SE再生(予約)
    public void PlaySe(string _name)
    {
        // 同じフレームに同じSEは再生しないようにする
        if(nameList.Exists(n => n == _name)){
            return;
        }

        nameList.Add(_name);
    }

    // BGMの一時停止
    public void PauseBgm()
    {
        foreach (BgmSound _sound in bgmTbl)
        {
            _sound.Pause();
        }
    }

    // BGMの一時停止を解除
    public void UnPauseBgm()
    {
        foreach (BgmSound _sound in bgmTbl)
        {
            _sound.UnPause();
        }
    }

    // BGMの音量を設定
    public void SetBgmVolume(float _volume)
    {
        bgmVolume = Mathf.Clamp(_volume, 0, 1.0f);

        foreach (BgmSound _sound in bgmTbl)
        {
            _sound.SetVolume(bgmVolume);
        }
    }

    // SEの音量を設定
    public void SetSeVolume(float _volume)
    {
        seVolume = Mathf.Clamp(_volume, 0, 1.0f);
    }

    // SE再生
    void Play(string _name)
    {
        se.Play(soundDictionary.Value(_name), seVolume);
    }
}

1フレームで同じ SE を鳴らさないように nameList で制御している

void LateUpdate()

Update処理の最後で呼び出される

BGM の更新(フェード関係)と List にある SE の再生を行う

 

 

LikeDictionary のスクリプト

サウンド情報をインスペクタービューで設定したかったので、色々試した結果

Dictionary を使って名前から AudioClip を参照する形になりました

こんな感じ

 

Dictionary だと…

しかし Unity の仕様で Dictionary はインスペクタービューに表示されません!

ということで色々調べて「エディタ拡張」など様々なやり方を見た結果…

List で設定したものを Dictionary に移すという形になりました

 

LikeDictionary.cs

値しか取れない、他の機能は適宜追加する

[Serializable]
public class LikeDictionary<Tkey, Tvalue>
{
    [Serializable]
    private struct DictionaryData
    {
        public Tkey key;
        public Tvalue value;
    }

    [SerializeField] List<DictionaryData> list;     // エディター用のリスト
    Dictionary<Tkey, Tvalue> dictionary;            // サウンドデータ

    // list ⇒ dictionary にデータを移す
    public void Init()
    {
        dictionary = new Dictionary<Tkey, Tvalue>();

        while (list.Count > 0)
        {
            dictionary.Add(list[0].key, list[0].value);
            list.RemoveAt(0);
        }
    }

    // 値を取得
    public Tvalue Value(Tkey _key)
    {
        return dictionary[_key];
    }
}

Init を呼ぶと List ⇒ Dictionary にデータを移す

 

処理的に設定数が増えてくると重くなりそうですが

求めすぎると先に進めなくなるので、重くなった時に考えることにしましょう

 

再生してみる

空のオブジェクトを作成⇒「SoundManager のスクリプト」を追加します
※そのオブジェクトをプレハブにしておく

インスペクタービューで「再生用の名前とサウンドデータ」を設定する

あとは音を再生したい箇所にスクリプトを書いていきましょう

// BGM再生の例
SoundManager.Instance.ChangeBgm("bgm_title", 0.2f);

// SE再生の例
SoundManager.Instance.PlaySe("cursor_move");

 

サンプル動画

 

さいごに

音を鳴ると一気に雰囲気が変わりますね!

今回のものは簡単な設定しかしていませんが

Audio~ は他にも色々な設定が可能なので、確認してみるといいかもしれません

 

かれいど

ゲームをしたり、作ったり
色々な事に挑戦していきたい!

サッカー観戦も趣味で
主にJ1リーグを観ています。

かれいどをフォローする
unity
スポンサーリンク
シェアする
かれいどブログ

コメント