Unityでのサウンド処理のプログラム

はじめに

いつもUnityのサウンド関係は後回しにしがちで、しかも使い方を忘れて実装のたびに調べているので備忘録を兼ねてブログでまとめておこうと思いました。 サウンドプログラムに関して特に詳しいわけではないのですが、たぶん一週間ゲームジャムくらいであればこのくらいでも足りるひとは多いのではないかと思います。 プログラムはコピペすればDOTweenを入れたりAudio Mixerの設定をしたりしても10分をかからないくらいで使えるんじゃないかと思うので、よかったら使ってみてください。

追記

プログラムの内容は適宜修正や使い方の注意のコメントを加えていく予定です。

目次

サウンド処理のプログラム全文

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;
using UnityEngine.Audio;

public class SoundController : MonoBehaviour
{
    private static SoundController instance;
    public static SoundController Instance
    {
        get { return instance; }
        private set { instance = value; }
    }

    AudioSource[] audioSources = new AudioSource[2];

    // Audio Mixerとそのグループ
    [SerializeField]
    AudioMixer audioMixer;
    [SerializeField]
    AudioMixerGroup musicMixerGroup;
    [SerializeField]
    AudioMixerGroup seMixerGroup;

    private void Awake()
    {
        if (instance == null)
        {
            instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            // 既にSoundControllerが存在するなら破棄する
            Destroy(this);
            return;
        }

        audioSources = GetComponents<AudioSource>();
        foreach(AudioSource a in audioSources)
        {
            a.outputAudioMixerGroup = musicMixerGroup;
        }
    }


    [SerializeField]
    float fadeDuration = 1.0f;              // フェードに掛ける時間

    int currentIndex = 0;                   // 現在使っているAudioSourceの番号
    bool isPlayingMusic = false;            // 現在音楽を再生しているか

    public void PlayMusic(AudioClip nextMusic, bool looping = true)
    {
        if (isPlayingMusic)
        {
            // 現在流れている音楽を消しつつ、次の音楽を再生する
            StartCoroutine(StopMusic(currentIndex));
            currentIndex = 1 - currentIndex; // 使うAudioSourceを入れ替える
            audioSources[currentIndex].clip = nextMusic;    // 次の音楽を設定
            audioSources[currentIndex].volume = 0.0f;       // 次の音楽のボリュームを0にする
            audioSources[currentIndex].DOFade(1.0f, fadeDuration);  // 次の音楽のボリュームをだんだん大きくする
            audioSources[currentIndex].Play();
        }
        else
        {
            currentIndex = 0;   // 0番目のAudioSourceを使う
            audioSources[currentIndex].clip = nextMusic;
            audioSources[currentIndex].volume = 1.0f;
            audioSources[currentIndex].Play();
        }

        isPlayingMusic = true;
    }

    // 指定されたIndexのAudioSourceを止める
    private IEnumerator StopMusic(int sourceIndex)
    {
        audioSources[sourceIndex].DOFade(0.0f, fadeDuration);
        yield return new WaitForSeconds(fadeDuration);
        audioSources[sourceIndex].Stop(); // もしfadeDuration以内の時間で何回も音楽を切り替える場合はここはコメントアウトするか、フェード中に音楽が切り替わったらコルーチンを止める処理を入れてください。
    }

    // 音楽を完全に止める
    public void StopAllMusic()
    {
        StartCoroutine(StopMusic(0));
        StartCoroutine(StopMusic(1));

        isPlayingMusic = false;
    }

    // 効果音を再生する
    public void PlaySE(AudioClip se, float lifetime = 10.0f)
    {
        GameObject seObject = new GameObject();
        seObject.AddComponent<AudioSource>();

        seObject.GetComponent<AudioSource>().outputAudioMixerGroup = seMixerGroup;
        seObject.GetComponent<AudioSource>().volume = 1.0f;
        seObject.GetComponent<AudioSource>().PlayOneShot(se);

        // 一定時間後に消滅するようにする
        Destroy(seObject, lifetime);
    }

    #region ボリューム関係

    public float musicVolume = 0.5f;     // 音楽のボリューム
    public float soundEffectVolume = 0.5f;        // 効果音のボリューム
    // 音楽のボリュームをオーディオミキサーに設定する
    public void SetMusicVolume(float value)
    {
        musicVolume = value;
        float decibel = 20.0f * Mathf.Log10(musicVolume);

        if (float.IsNegativeInfinity(decibel))
            decibel = -96f;

        audioMixer.SetFloat("MusicVolume", decibel);
    }

    // 効果音のボリュームをオーディオミキサーに設定する
    public void SetSoundEffectVolume(float value)
    {
        soundEffectVolume = value;
        float decibel = 20.0f * Mathf.Log10(soundEffectVolume);

        if (float.IsNegativeInfinity(decibel))
            decibel = -96f;

        audioMixer.SetFloat("SoundEffectVolume", decibel);
    }

    #endregion

    #region 色んな所で使う効果音などはSoundControllerで直接呼び出せるようにする。

    // クリックの効果音
    [SerializeField]
    AudioClip clickSE;
    public void PlayClickSE()
    {
        PlaySE(clickSE);
    }
    #endregion
}

使い方

1.DOTweenの導入

まずUnityのAsset StoreよりDOTweenというアセットをインポートします。 僕は有料版のDOTween Proを使用していますが、恐らく無料版でもこのプログラムに関しては問題ないと思いますので、そちらのURLを張っておきます。

assetstore.unity.com

2.SoundControllerクラスの作成

プロジェクトウィンドウから右クリック>Create>C# Scriptを選択し、SoundControllerという名前を付けてください。(もっといい名前があれば変えて結構です) 次にこのブログの”サウンド処理のプログラム全文”にあるスクリプトを全部コピーしたら、先ほど作成したSoundCotrollerをダブルクリックしてエディタで開き、Ctrl+AからのCtrl+Vでペーストしてください。

3.最初のシーンにSoundControllerコンポーネントを付けたオブジェクトを用意する

ゲームを開始したときに一番最初に読み込まれるシーン(タイトルなど)を開きます。 HierarchyウィンドウからCreate Emptyで空のゲームオブジェクトを作成し、Add Componentから先ほどのSoundControllerを1つ、AudioSourceを2つアタッチしてください。

f:id:ItsukiNamito:20190328202641p:plain
こんな感じにしてください。

4.Audio Mixerを用意する

WindowメニューからAudio Mixerを選択するか、Ctrl+8でAudio Mixerウィンドウを開いてください。

Audio Mixerウィンドウから次のような作業をお願いします。
1.Mixers欄の右にあるプラスボタンをクリックして、新規のMixerを追加してください。名前はMain等でいいと思います。
2.Masterグループを選択した状態でGroup欄の右にあるクリックを押してMusicグループとSEグループをMasterグループ直下に作成してください。(Musicグループ下にSEグループを作らないように気を付けてください。)MusicグループはBGMグループなど自分の分かりやすい名前を付けて構いません。
3.Musicグループを選択し、InspectorウィンドウのAttenuationのすぐ下にあるVolumeの文字の上で右クリックをし、「Expose 'Volume (of Music)' to script」クリックします。これでMusicのボリュームをスクリプトから変更できるようになったのですが、デフォルトの名前がMyExposedParamになっているので、Audio Mixerウィンドウの右上のExposed Parametersから名前をMusicVolumeなど分かりやすいように変更しておきましょう。
4.最後にSEグループを選び、Inspectorウィンドウから「Add Effect」ボタンをクリックして「Normalize」エフェクトを選びましょう。これで同時に沢山の効果音が鳴らされても、音割れがしなくなります。(たぶん)

f:id:ItsukiNamito:20190328205204p:plain
文字が小さくて見えない場合はクリックすると拡大画像が表示されます。

f:id:ItsukiNamito:20190328213017p:plain

5.SoundControllerコンポーネントに必要な情報を割り当てる

Projectウィンドウを開くとMainミキサーアセットがあります。これのトグルを開きます。 3の手順で用意したSoundControllerの付いたゲームオブジェクトをクリックし、ドラッグアンドドロップで下の図のように割り当てます。

f:id:ItsukiNamito:20190328214903p:plain
Projectウィンドウから赤線でつながっているInspectorウィンドウの項目へドラッグアンドドラッグしてください。

Sound Controllerの一番下にあるClick SEと書いてある場所にはボタンをクリックしたときの効果音のAudioClipを設定してください。

ここまでで準備は終わりです。

6.自分のスクリプトからSoundControllerのメソッドを呼び出す

後は自分のスクリプトからBGMを流したいタイミングでSoundController.Instance.PlayMusic(流したいBGMのAudioClip);を呼び出し、効果音を鳴らしたいタイミングでSoundController.Instance.PlaySE(流したい効果音のAudioClip);を呼び出せば使うことが出来ます。もしBGMを止めたい場合はSoundController.Instance.StopAllMusic();で止めることが出来ます。

BGMを流す簡単な例を書いておきます。

public class TitleManager : MonoBehavior
{
 public AudioClip titleBGM;
 void Start()
 {
  SoundController.Instance.PlayMusic(titleBGM);
 }
}

プログラムの解説(興味があれば読んでください)

private static SoundController instance;
public static SoundController Instance
{
 get { return instance; }
 private set { instance = value; }
}

private void Awake()
{
 if(instance == null)
 {
  instance = this;
  DontDestroyOnLoad(gameObject);
 }
 else
 {
  Destroy(this);
  return;
 }
~
}

SoundControllerはシングルトン的な使い方をしたかったので、instanceに唯一のインスタンスを保持しておき、void Awake()のタイミングで既にinstanceにインスタンスがあれば、自分は2つ目以降なので破棄、そうでなければ自分をinstanceに保持させています。もしSingletonMonobehaviorクラスなどを自作している場合には適宜書き換えて使ってください。
この作りの場合は音を鳴らしたい場合このSoundControllerを配置してあるシーンからテストする必要があります。もし、ゲームシーンの音の確認の為にタイトルから経由してくるのが大変な場合はこちらのサイトなどを参考に、Instance参照時にGameObjectの生成、SoundController,AudioSource2つのAddComponentなどを行う処理書いてください。

    int currentIndex = 0;                   // 現在使っているAudioSourceの番号
    bool isPlayingMusic = false;            // 現在音楽を再生しているか

    public void PlayMusic(AudioClip nextMusic, bool looping = true)
    {
        if (isPlayingMusic)
        {
            // 現在流れている音楽を消しつつ、次の音楽を再生する
            StartCoroutine(StopMusic(currentIndex));
            currentIndex = Mathf.Abs(currentIndex - 1); // 使うAudioSourceを入れ替える
            audioSources[currentIndex].clip = nextMusic;    // 次の音楽を設定
            audioSources[currentIndex].volume = 0.0f;       // 次の音楽のボリュームを0にする
            audioSources[currentIndex].DOFade(1.0f, fadeDuration);  // 次の音楽のボリュームをだんだん大きくする
            audioSources[currentIndex].Play();
        }
        else
        {
            currentIndex = 0;   // 0番目のAudioSourceを使う
            audioSources[currentIndex].clip = nextMusic;
            audioSources[currentIndex].volume = 0.0f;
            audioSources[currentIndex].DOFade(1.0f, fadeDuration);
            audioSources[currentIndex].Play();
        }

        isPlayingMusic = true;
    }

このSoundControllerでは音楽を切り替える際に、現在流れているBGMのボリュームを少しずつ小さくし、次に流れるBGMのボリュームを少しづつ大きくします。 クロスフェードっていう処理らしいです。 準備の段階でAudioSourceを二つ追加してもらった理由はこのBGMの切り替え時に前のBGMと次のBGMの両方が流れているタイミングがあるためです。

    // 指定されたIndexのAudioSourceを止める
    private IEnumerator StopMusic(int sourceIndex)
    {
        audioSources[sourceIndex].DOFade(0.0f, fadeDuration);
        yield return new WaitForSeconds(fadeDuration);
        audioSources[sourceIndex].Stop();
    }

sourceIndexで指定された方のAudioSourceを停止させます。この際フェード終了後にStopを呼んでいるため、フェード中にさらに音楽を切り替えると音楽が鳴らなくなってしまいます。
頻繁に流れる音楽が変わる場合、お手数ですが3行目のaudioSources[sourceIndex].Stop();はコメントアウトして使うか(ただこの場合常に2つAudioSourceが再生されている状態で、片方のボリュームが0だから一曲しか再生されていないように聞こえます。)、フェードアウト中に再生される場合はコルーチンを止める処理を別に描く必要があります。

    // 効果音を再生する
    public void PlaySE(AudioClip se, float lifetime = 10.0f)
    {
        GameObject seObject = new GameObject();
        seObject.AddComponent<AudioSource>();

        seObject.GetComponent<AudioSource>().outputAudioMixerGroup = seMixerGroup;
        seObject.GetComponent<AudioSource>().volume = 1.0f;
        seObject.GetComponent<AudioSource>().PlayOneShot(se);

        // 一定時間後に消滅するようにする
        Destroy(seObject, lifetime);
    }

効果音は先ほどの2つのAudio Sourceではなく新規にGameObjectを生成し、AudioSourceを追加して、そのAudioSourceから効果音を流させています。効果音が鳴り終わってしばらくした後(デフォルトでは10秒後)に自動的にそのGameObjectを破棄するようにしてあります。

    #region ボリューム関係

    public float musicVolume = 0.5f;     // 音楽のボリューム
    public float soundEffectVolume = 0.5f;        // 効果音のボリューム
    // 音楽のボリュームをオーディオミキサーに設定する
    public void SetMusicVolume(float value)
    {
        musicVolume = value;
        float decibel = 20.0f * Mathf.Log10(musicVolume);

        if (float.IsNegativeInfinity(decibel))
            decibel = -96f;

        audioMixer.SetFloat("MusicVolume", decibel);
    }

    // 効果音のボリュームをオーディオミキサーに設定する
    public void SetSoundEffectVolume(float value)
    {
        soundEffectVolume = value;
        float decibel = 20.0f * Mathf.Log10(soundEffectVolume);

        if (float.IsNegativeInfinity(decibel))
            decibel = -96f;

        audioMixer.SetFloat("SoundEffectVolume", decibel);
    }

    #endregion

これはコンフィグ画面でスライダーなどでボリュームを変更することを想定して用意しています。 SetMusicVolumeの引数がfloatになっていますが、ここに0~1の値を与えるとAudio Mixerのボリュームを変更してくれます。SetSoundEffectVolumeも同様です。 また、AudioMixerはボリュームがデシベルなので、0~1の値を変換する処理を行っています。

    #region 色んな所で使う効果音などはSoundControllerで直接呼び出せるようにする。

    // クリックの効果音
    [SerializeField]
    AudioClip clickSE;
    public void PlayClickSE()
    {
        PlaySE(clickSE);
    }
    #endregion

BGMや効果音を鳴らす際に基本的には鳴らす側で使いたいAudioClipを設定して、PlayMusicやPlaySEで呼び出すといった使い方を想定しています。 しかしクリック音のように色んな所から使われ、汎用的に呼び出される効果音に関してはSoundController側で設定するようにしてあります。こうすることでクリックの音を変えたいと思った時にいちいち鳴らしている場所を探してAudioClipを差し替えなくてもSoundControllerで一つ変えるだけで済みます。

参考にしたサイト

hecres.hatenablog.com

www.slideshare.net

おわりに

このプログラムはどなたでも使用していただいて構いません。音を付けたいけど一週間ゲームジャムで時間がないという場合や、Unity初心者でサウンドの処理をどうすればいいか分からないといった方に参考になればいいなと思っています。(僕もまだ初心者かもしれませんが…) また、サウンドの事分かるよ!という方など補足や訂正があればぜひ教えていただけるととても嬉しいです。

ちなみにこの内容では頻繁に音楽が切り替わる場合や3Dゲームなどで音の発生位置によって聞こえ方を変えるみたいなのには対応できないと思います。

おまけ

ブログの記事の最後で好きなものを宣伝することにしているのですが、今回はサウンド処理なので有名な方ですが一番好きな作曲家の浜渦正志さんについて紹介します。


SaGa Frontier II OST - Roman

残念ながら僕は音楽について表現する言葉は詳しくないのですが、浜渦さんの音楽はとても透明感が高くて(表現あってるのかな?)あまり激しくないので、リラックスするときや作業用BGMとしても疲れないのでとてもおススメです! たぶん浜渦さんの音楽で一番有名なのはFF13の閃光かな?


FF13 戦闘曲閃光 高音質

僕はこれらの音楽はCDやiTunesから購入して聞いてるのですがスクウェアエニックスゲーム音楽は買おうと思えば買えるものが多いのでとても助かっています。