ゲーム開発
Unity
UnrealEngine
C++
ゲーム数学
ゲームAI
サウンド
アニメーション
GBDK
制作日記
3DCG
Houdini
Blender
USD
グラフィックス
テクノロジ
ツール開発
フロントエンド関連
サーバサイド関連
ソフトウェア設計
ハードウェア関連
おすすめ技術書
音楽
DTM
楽器・機材
ピアノ
その他
都会のエレキベア
ラーメン日記
四コマ漫画
おすすめアイテム
おもしろコラム
  • ゲーム開発
    • Unity
    • UnrealEngine
    • C++
    • ゲーム数学
    • ゲームAI
    • サウンド
    • アニメーション
    • GBDK
    • 制作日記
  • 3DCG
    • Houdini
    • Blender
    • USD
    • グラフィックス
  • テクノロジ
    • ツール開発
    • フロントエンド関連
    • サーバサイド関連
    • ソフトウェア設計
    • ハードウェア関連
    • おすすめ技術書
  • 音楽
    • DTM
    • 楽器・機材
    • ピアノ
  • その他
    • 都会のエレキベア
    • ラーメン日記
    • 四コマ漫画
    • おすすめアイテム
    • おもしろコラム
  1. ホーム
  2. 20240122_01_unity_audio_standard

【Unity】第一回 UnityAudioを使いこなす 〜サウンド再生処理編〜

UnityサウンドUnityAudio
2024-01-22

マイケル
マイケル
みなさんこんにちは! マイケルです!
エレキベア
エレキベア
こんにちクマ〜〜
マイケル
マイケル
今日からしばらくUnityでのサウンド周りの実装について取り上げていきます! 後々CRIやWwise等のサウンドミドルウェアを触ってみようと思いますが、その前にUnity標準で搭載されているUnityAudioでどのようなことが出来るのか?をまとめていこうと思います!
エレキベア
エレキベア
サウンドミドルウェアは一定規模のゲーム開発ではほとんど採用されているクマね でも確かにUnityAudioについても知らないことが多そうクマ
マイケル
マイケル
調べながら改めて触ってみると、サウンドエフェクトやオーディオスペクトラムの描画も実装できて、想像以上にいろんなことが出来るなと思いました。 基本的なサウンド再生でしか触っていない方も多いと思うので、これを機にいろいろ触ってみると面白いと思います!
【Unity】第一回 UnityAudioを使いこなす 〜サウンド再生処理編〜
2024-01-22
【Unity】第二回 UnityAudioを使いこなす 〜AudioMixer活用編〜
2024-01-22
【Unity】第三回 UnityAudioを使いこなす 〜オーディオスペクトラム描画編〜
2024-01-22

参考書籍

マイケル
マイケル
学習するにあたり、下記書籍を参考にさせていただきました!

Unityサウンド エキスパート養成講座

マイケル
マイケル
Unityでのサウンド処理をまとめた数少ない書籍です! 発売日からそれなりに経過していますが、UnityAudioだけでなくCRIと連携した使い方まで解説されているため、興味を持った方はぜひ一読してみてください!
エレキベア
エレキベア
サウンド専門となると書籍は中々ないクマから持っておく価値はありそうクマね

UnityAudioの構成要素

マイケル
マイケル
UnityのAudio機能については公式マニュアルにまとまっていますが、大きく下記のような構成になっています。

Unity公式マニュアル - オーディオの概要

機能名
概要
AudioClip
・サウンドのアセット形式
AudioSource
・AudioClipの再生処理を行うためのコンポーネント
AudioListener
・ゲーム内空間の耳にあたるコンポーネント
AudioMixer
・AudioSourceが鳴らしている音を分類し調整やエフェクト処理を行う機能
エレキベア
エレキベア
AudioClipやAudioSourceはよく使うクマが、それ以外はあまり聞き覚えがないクマね
マイケル
マイケル
各機能はそれぞれ下記のようになっています。

AudioClip

マイケル
マイケル
AudioClipは Unityでオーディオファイルを扱う形式 です。 こちらはUnityにインポートした際に自動的にこの形式に変換されます。
20240122_01_unity_audio_standard_01

Unityスクリプトリファレンス - AudioClip

共通設定
項目名
概要
Force To Mono
モノラル音源へ変換するか
Load In Background
メモリへロードする際、非同期読み込みを行うか
Ambisonic
VR向けの設定
プラットフォーム別設定
項目名
概要
LoadType
読み込み方法 ※後述
Preload Audio Data
シーンロード時にサウンドデータをメモリに読み込むか
Compression Format
圧縮設定
LoadTypeの種類
項目名
概要
効果
Decompress On Load
デコード処理を全て行ってからメモリに配置する
CPU負荷:小
メモリ容量:大
Compressed In Memory
圧縮データを展開せずにメモリに読み込み、再生時に展開する
CPU負荷:中
メモリ容量:中
Streaming
圧縮データをメモリに置かず、再生時に随時ストレージから読み込む
CPU負荷:大
メモリ容量:小
マイケル
マイケル
音源の種類や用途によって設定を変えることで最適化することができます。 具体例として、BGMとSEで設定を分ける例は下記になります。
BGMの設定例

容量が大きいため Load In Background で非同期読み込みを行い、
Compressed In Memory で再生時に展開する。

20240122_01_unity_audio_standard_06

SEの設定例

再生速度を優先するため Force To Mono でモノラルに設定し、
Preload Audio Data、Decompress On Load で事前に読み込み、デコード処理を行う。

20240122_01_unity_audio_standard_05

エレキベア
エレキベア
CPU負荷やメモリ容量を考慮しながら、再生速度と品質どちらを優先すべきかで設定しているクマね

AudioSource

マイケル
マイケル
AudioSourceは AudioClipの再生処理を行うためのコンポーネント です。
20240122_01_unity_audio_standard_02

Unityスクリプトリファレンス - AudioSource

マイケル
マイケル
AudioClip項目にclipを設定することで再生することができます。 その他、ボリュームやピッチ調整等の再生時の設定が主な役割ですが、こちらは後ほど触ってみましょう。
エレキベア
エレキベア
AudioClipをAudioSourceで再生する・・・ ここまでは分かったクマね

AudioListener

マイケル
マイケル
AudioListenerは ゲーム内空間の耳にあたるコンポーネント で、ゲーム中に一つしか存在しません。 こちらのを介してゲーム全体の音の再生、停止等を行えます。
20240122_01_unity_audio_standard_03
▲MainCameraにデフォルトで付いている

Unityスクリプトリファレンス - AudioListener

エレキベア
エレキベア
MainCameraに設定されてるの気付かなかったクマ・・・

AudioMixer

マイケル
マイケル
AudioMixerは AudioSourceが鳴らしている音を分類し調整やエフェクト処理を行う機能 です。 こちらは設定手順等が複雑なので、詳細は次回解説します!
20240122_01_unity_audio_standard_07

Unityスクリプトリファレンス - AudioMixer

エレキベア
エレキベア
これはあまり触ってない人も多そうクマね

プロジェクト全体のサウンド設定

マイケル
マイケル
最後に、ゲーム全体のサウンド設定は Edit > Project Settings > Audio から変更することができます。 全体のボリューム設定や最大同時再生数などの項目があります。
20240122_01_unity_audio_standard_04

Unityスクリプトリファレンス - AudioSettings

エレキベア
エレキベア
まあ基本はデフォルトでよさそうクマね

オーディオの再生処理を実装する

マイケル
マイケル
それでは早速オーディオの再生処理を実装してみます。 今回作ったサンプルプロジェクトは下記のGitHubリポジトリとして上げていますので、こちらもよければご参照ください!

GitHub - unity-audio-sample

20240122_01_unity_audio_standard_09
▲サンプルプロジェクト内で今回解説する範囲の機能

サウンドの再生処理

マイケル
マイケル
サウンド関連の処理については、基本的に UnityAudioServiceクラス内にまとめてあります。

GitHub - UnityAudioService.cs

マイケル
マイケル
まず使用するAudioClipの読込処理は下記になります。 サンプルということで今回はResources配下から読み込むようにしています。
        /// <summary>
        /// Audioファイル格納パス
        /// </summary>
        private static readonly string AudioFileDirPath = "Audio/";

        /// <summary>
        /// キャッシュしたAudioClip
        /// key: ファイル名
        /// </summary>
        private readonly IDictionary<string, AudioClip> _cachedAudioDictionary = new Dictionary<string, AudioClip>();

        /// <summary>
        /// AudioClipの読み込み
        /// </summary>
        /// <param name="audioName"></param>
        /// <returns></returns>
        private AudioClip LoadAudioClip(string audioName)
        {
            // ファイル名をキーとしてキャッシュする
            if (!_cachedAudioDictionary.ContainsKey(audioName))
            {
                var audioClip = Resources.Load(AudioFileDirPath + audioName) as AudioClip;
                _cachedAudioDictionary.Add(audioName, audioClip);
            }
            return _cachedAudioDictionary[audioName];
        }
▲AudioClipの読み込み
マイケル
マイケル
そして読み込んだAudioClipをAudioSourceに設定するのですが、 今回は下記のようにオブジェクトにAddComponentする形で生成しました。 BGM用で二つ生成しているのは、この後紹介するクロスフェード処理で同時再生する時に必要になるためです。

        /// <summary>
        /// AudioSource
        /// </summary>
        private readonly AudioSource _seAudioSource; // SE再生用
        private readonly List<AudioSource> _bgmAudioSourceList; // BGM再生用

        /// <summary>
        /// 一時停止中か?
        /// </summary>
        private bool _isPause = false;

        public UnityAudioService()
        {

・・・略・・・

            // AudioManagerオブジェクトを作成
            var audioManager = GameObject.Find(AudioManagerObjectName);
            if (audioManager == null)
            {
                audioManager = new GameObject(AudioManagerObjectName);
                Object.DontDestroyOnLoad(audioManager);
                _seAudioSource = CreateSeAudioSource(audioManager);

                // BGMはフェード用で2つ生成しておく
                _bgmAudioSourceList = new List<AudioSource>();
                _bgmAudioSourceList.Add(CreateBgmAudioSource(audioManager));
                _bgmAudioSourceList.Add(CreateBgmAudioSource(audioManager));
            }
            _isPause = false;
        }

        /// <summary>
        /// BGM用AudioSource生成
        /// </summary>
        /// <param name="parentObject"></param>
        /// <returns></returns>
        public AudioSource CreateBgmAudioSource(GameObject parentObject)
        {
            return CreateAudioSource(parentObject, true);
        }

        /// <summary>
        /// SE用AudioSource生成
        /// </summary>
        /// <param name="parentObject"></param>
        /// <returns></returns>
        public AudioSource CreateSeAudioSource(GameObject parentObject)
        {
            return CreateAudioSource(parentObject, false);
        }

        /// <summary>
        /// AudioSource生成
        /// </summary>
        /// <param name="parentObject"></param>
        /// <param name="isLoop"></param>
        /// <returns></returns>
        private AudioSource CreateAudioSource(GameObject parentObject, bool isLoop)
        {
            var audioSource = parentObject.AddComponent<AudioSource>();
            audioSource.loop = isLoop;
            audioSource.playOnAwake = false; // デフォルトでtrueのため明示的にオフにする
            return audioSource;
        }
▲AudioSourceの生成
マイケル
マイケル
あとは生成したAudioSourceに対して、Play関数PlayOneShot関数 を呼び出すことで再生することができます。 今回は別途Optionクラスも用意し、ボリュームやピッチ調整も行えるようにしました。
        /// <summary>
        /// 効果音再生
        /// </summary>
        /// <param name="audioName"></param>
        /// <param name="option"></param>
        public void PlayOneShot(string audioName, IAudioService.AudioOption option = null)
        {
            PlayOneShot(_seAudioSource, audioName, option);
        }

        /// <summary>
        /// 効果音再生
        /// </summary>
        /// <param name="audioSource"></param>
        /// <param name="audioName"></param>
        /// <param name="option"></param>
        public void PlayOneShot(AudioSource audioSource, string audioName, IAudioService.AudioOption option = null)
        {
            var audioClip = LoadAudioClip(audioName);
            audioSource.volume = option?.Volume ?? 1f;
            audioSource.pitch = option?.Pitch ?? 1f;
            audioSource.PlayOneShot(audioClip);
        }

        /// <summary>
        /// BGM再生
        /// </summary>
        /// <param name="audioName"></param>
        /// <param name="option"></param>
        public void PlayBgm(string audioName, IAudioService.AudioOption option = null)
        {
            StopAllBgm();

            var audioSource = _bgmAudioSourceList[0];
            audioSource.clip = LoadAudioClip(audioName);
            audioSource.volume = option?.Volume ?? 1f;
            audioSource.pitch = option?.Pitch ?? 1f;
            audioSource.Play();
        }

        /// <summary>
        /// BGM停止
        /// </summary>
        public void StopAllBgm()
        {
            // 再生中のBGMを全て取得して停止する
            var audioSources = _bgmAudioSourceList.Where(audioSource => audioSource.isPlaying);
            if (audioSources.Count() <= 0)
            {
                return;
            }

            foreach (var audioSource in audioSources)
            {
                audioSource.Stop();
                audioSource.clip = null;
            }
        }
▲BGM、SEの再生処理
        /// <summary>
        /// オプション
        /// </summary>
        public class AudioOption
        {
            /// <summary>
            /// ボリューム
            /// </summary>
            public float Volume = 1f;

            /// <summary>
            /// ピッチ
            /// </summary>
            public float Pitch = 1f;

            public AudioOption(float volume, float pitch)
            {
                Volume = volume;
                Pitch = pitch;
            }
        }
▲指定できるオプション
マイケル
マイケル
これでオーディオファイル名とオプションを指定して関数呼び出しすることで再生できるようになっているはずです。
エレキベア
エレキベア
ここまでは簡単クマ〜〜〜

フェードイン、フェードアウト

マイケル
マイケル
次は少し凝って、再生・停止を行う際に音量をフェードさせるようにしてみます。 今回はUnity標準機能のみで実装するためにコルーチンを使用しましたが、MonoBehaviourが必要になるため可能であればUniTaskを使用して実装した方がいいと思います。
        /// <summary>
        /// BGMフェードイン再生
        /// </summary>
        public void PlayBgmFadeIn(MonoBehaviour mono, string audioName, float fadeTime, IAudioService.AudioOption option = null)
        {
            var audioSource = _bgmAudioSourceList.FirstOrDefault(audioSource => !audioSource.isPlaying);
            if (audioSource == null)
            {
                Debug.LogError($"all play audio sources!!");
                return;
            }

            StopAllBgmFadeOut(mono, fadeTime);
            mono.StartCoroutine(PlayBgmFadeInCoroutine(audioSource, audioName, fadeTime, option));
        }

        private IEnumerator PlayBgmFadeInCoroutine(AudioSource audioSource, string audioName, float fadeTime, IAudioService.AudioOption option = null)
        {
            var startVolume = 0f;
            var targetVolume = option?.Volume ?? 1f;

            audioSource.clip = LoadAudioClip(audioName);
            audioSource.volume = startVolume;
            audioSource.pitch = option?.Pitch ?? 1f;
            audioSource.Play();

            for (var t = 0f; t < fadeTime; t += Time.deltaTime)
            {
                audioSource.volume = Mathf.Lerp(startVolume, targetVolume, Mathf.Clamp01(t / fadeTime));
                yield return null;
            }
            audioSource.volume = targetVolume;
        }

        /// <summary>
        /// BGMフェードアウト停止
        /// </summary>
        public void StopAllBgmFadeOut(MonoBehaviour mono, float fadeTime)
        {
            // 再生中のBGMを全て取得して停止する
            var audioSources = _bgmAudioSourceList.Where(audioSource => audioSource.isPlaying);
            if (audioSources.Count() <= 0)
            {
                return;
            }

            foreach (var audioSource in audioSources)
            {
                mono.StartCoroutine(StopBgmFadeOutCoroutine(audioSource, fadeTime));
            }
        }

        private IEnumerator StopBgmFadeOutCoroutine(AudioSource audioSource, float fadeTime)
        {
            var startVolume = audioSource.volume;
            var targetVolume = 0f;
            for (var t = 0f; t < fadeTime; t += Time.deltaTime)
            {
                audioSource.volume = Mathf.Lerp(startVolume, targetVolume, Mathf.Clamp01(t / fadeTime));
                yield return null;
            }
            audioSource.volume = targetVolume;

            audioSource.Stop();
            audioSource.clip = null;
        }
▲再生・停止時のフェード処理
マイケル
マイケル
あとはこの関数に対してフェードさせる時間を渡すようにすれば完了です。 今回は線形補間で変化させましたが、より自然に見せたい場合にはイージング関数等を用いるとよいと思います。
        private void PlayFadeBgm01()
        {
            AudioService.PlayBgmFadeIn(this, AudioName.BgmMain, 1f);
        }

        private void PlayFadeBgm02()
        {
            AudioService.PlayBgmFadeIn(this, AudioName.BgmPlay, 1f);
        }
エレキベア
エレキベア
あとは処理の連打防止なんかも考えないといけなそうクマね

オブジェクトからSEを鳴らす

マイケル
マイケル
最後に、オブジェクト自身の位置を考慮したSE再生について紹介します。
マイケル
マイケル
AudioSourceは位置情報も反映するため、遠くから鳴らすと音が小さく、近くで鳴らすと音が大きくなるといった3D再生が可能になっています。 この位置情報を有効にするためには、オブジェクトにアタッチしたAudioSourceに対して spatialBlend(通常再生と3D再生のブレンド具合) の値を0以上にする必要があります。
        private AudioSource _audioSource;

        private void Start()
        {
            // AudioSourceを設定
            _audioSource = AudioService.CreateSeAudioSource(gameObject);
            _audioSource.spatialBlend = 0.7f; // 3D再生のブレンド具合

・・・略・・・

        }
▲オブジェクト側のAudioSourceのspatialBlend値を設定する
マイケル
マイケル
あとは設定したAudioSourceを渡す形で再生すれば、位置情報が反映された状態で再生されるはずです。

        public void PlayOneShot(string audioName)
        {
            var option = new IAudioService.AudioOption(1f, _sePitch);
            AudioService.PlayOneShot(_audioSource, audioName, option);
        }

・・・略・・・

        // ----- 移動 -----
        private class StateMove : StateMachine<RobotBehaviour>.StateBase
        {
            private static readonly float Speed = 3.5f;
            private Vector3 _targetPosition;

            public override void OnStart()
            {
                // 範囲内でランダムに目的地を決める
                var randomPosX = UnityEngine.Random.Range(-3f, 3f);
                var randomPosY = UnityEngine.Random.Range(-2f, 2f);
                var randomPosZ = UnityEngine.Random.Range(-3f, 3f);
                _targetPosition = new Vector3(randomPosX, randomPosY, randomPosZ);

                // 動き始める時にSEを再生する
                Owner.PlayOneShot(AudioName.SeApproach);
            }

・・・略・・・

        }
▲AudioSourceを渡して再生する
マイケル
マイケル
サンプルではランダムに動き回るオブジェクトから再生する機能を用意していますので、よければ確認してみてください!
20240122_01_unity_audio_standard_08
▲「Start3DSE」ボタン押下で確認できます。

エレキベア
エレキベア
(見覚えのあるロボットクマ・・・)
マイケル
マイケル
ちなみにアニメーションによってSEを再生したい場合には、下記のようにアニメーションイベントを設定して呼び出すことでモーションに応じた再生が可能です。 こちらも組み込んでありますので、興味のある方はご参照ください!
20240122_01_unity_audio_standard_10
▲アニメーションイベントの設定

20240122_01_unity_audio_standard_11
▲設定したアニメーションイベント

        public void PutHitCallback()
        {
            // アニメーション内のイベントで呼び出される
            PlayOneShot(AudioName.SeDecide);
        }
▲アニメーションイベントから呼び出す関数
エレキベア
エレキベア
これで基本的なサウンドの再生周りはマスターしたクマね

おわりに

マイケル
マイケル
というわけで今回はUnityAudioによる再生周りの処理についてでした! どうだったかな??
エレキベア
エレキベア
改めてみるとなんとなく使ってた機能も多かったクマ でもやっぱりサウンドがあると楽しいクマね
マイケル
マイケル
次回は更に踏み込んで、AudioMixerを使用した設定画面やエフェクト適用周りを試してみます! お楽しみに!!
エレキベア
エレキベア
楽しみクマ〜〜〜〜〜

【Unity】第一回 UnityAudioを使いこなす 〜サウンド再生処理編〜 〜完〜

【Unity】第一回 UnityAudioを使いこなす 〜サウンド再生処理編〜
2024-01-22
【Unity】第二回 UnityAudioを使いこなす 〜AudioMixer活用編〜
2024-01-22
【Unity】第三回 UnityAudioを使いこなす 〜オーディオスペクトラム描画編〜
2024-01-22

UnityサウンドUnityAudio
2024-01-22

関連記事
【Unity】Timeline × Excelでスライドショーを効率よく制作する
2024-10-31
【Unity】Boidsアルゴリズムを用いて魚の群集シミュレーションを実装する
2024-05-28
【ゲーム数学】第九回 p5.jsで学ぶゲーム数学「フーリエ解析」
2024-05-12
【Unity】GoでのランキングAPI実装とVPSへのデプロイ方法についてまとめる【Go言語】
2024-04-14
【Unity】第二回 Wwiseを使用したサウンド制御 〜インタラクティブミュージック編〜
2024-03-30
【Unity】第一回 Wwiseを使用したサウンド制御 〜基本動作編〜
2024-03-30
【Unity】第二回 CRI ADXを使用したサウンド制御 〜インタラクティブミュージック編〜
2024-03-28
【Unity】第一回 CRI ADXを使用したサウンド制御 〜基本動作、周波数解析編〜
2024-03-28