スポンサーリンク

【Unity】Addressable Asset SystemでSoundManagerを書き直した。

08/24/2018

Addressable Asset Systemとは

開発・テストはResourcesを使ってテストしていました。
まぁ楽なんですよね。開発中は。
リリース直前になって、AssetBundleを使い始めようとしていました。ちゃんと取り組んだことないけど、AssetBundle 扱いが難しいし、面倒です。
切り替えに伴いソースにも手が入るので、なるべく早いタイミングできりかえるべきなんだけど、開発効率が悪化しすぎ。

できれば、Resourcesのまま開発進めたいけど、リリース時にいろいろと問題があります。
ビルド時のコンテンツ構成から替えられない。画像一枚 差し替える事をしたい場合でも、アプリケーションを再配布・・・・

この問題の回答が、Addressable Asset Systemらしいです。

いいことばっかりに見えます。実際に使ってみて、本当に簡単に実装できました。
自分は、正式リリースまで待つ性分なのです。不具合で苦労するのが嫌い。ですが、今回はこれを上回る便利さ。
preview版ですが、組み込むことにしました。 

Addressable Asset Systemのインストール

本日(2018/07/24現在)、preview-0.1.2 です。

"com.unity.addressables": "0.0.22-preview",

もしくは、
"com.unity.addressables": "0.1.2-preview",

を下記のように、追加し、Unityを起動するとインストールされます。

インストール後、最新バージョンにアップしましょう。[window]→[PackageManager]にて、Updateボタンを押下です。

AddressableAssetsの使い方

ちゃんとしたドキュメントは見つけられませんが、先駆者の方が使い方を解説してくれています。感謝

SoundManager

まずは、サウンド管理をAddressableAssets化しようと思います。
専用にテスト環境を用意してませんので、開発中のソースをさっくりと公開します。
見落としがなければ、大丈夫だと思いますが、動かない場合ごめんね。細かな解説もサイトではしませんので、読み取ってもらえるとw

指摘事項や不明点はTwitterで問い合わせください。説明を追加したり訂正します。

SingletonMonoBehaviour

まずは、SingletonMonoBehaviourです。マネージャ機能を作成する場合、ほぼ必須だと思います。
かなり昔に、どこかのサイトを参考にして作った物ですが・・・どこを参考にしたのか?覚えてませんw 
おまじない的に置いてください。

SoundManager

非同期にしています。呼び出し側で、Barとか出しやすいように。
あー そうそうAudioMixerを必要とします。
構成は、下記のようにしてください。

作ったら、さっそくAddressableAssetsに登録して、Labelを適当に。

Clipには、LabelをBGM・SEと付けて。これは必須ではありません。内部ではSEとBGMを分けてlist管理していますので、見栄え的に。
また、今回は、使い方がわからなかったので、使ってませんが、プレロードはLabel単位なので付けておくといいかなと。

・使い方
まずは、初期化。

//SoundManager シングルトン初期化
        soundManager = SoundManager.Instance;
	StartCoroutine(soundManager.AudioInitialize());

その後、クリップを登録。

yield return soundManager.AudioClipListing("BGM", 1, "BGM/8bit-act01_title_loop.ogg");

こうした理由は、本番はFireBase StoregeにClipのListを用意して読みだして登録する。だけど、開発中は、FireBase StoregeにClipのListを用意するのが面倒。だと思うからです。
なので、Textで適用に書けるようにしました。本来は、プレロードなのかもしれませんが・・・実装の跡が見えますが、よくわからなかったわw

再生は、

soundManager.PlayBgm("BGM/8bit-act01_title_loop.ogg")

あと、番号でも、アドレスでも再生できます。

soundManager.PlayBgm(1)

必ず再生したいSEは、専用チャンネルで鳴らします。SEは、1音専用4チャンネル 4音チャンネルです。
BGMは、環境音とBGMと分ける場合や、BGM切替・割り込みBGMのためにBGMを2種。
未実装ですがVoice拡張。

てな感じです。次回、AddressableAssetsをFireBaseStoregeに配置してみます。

追記:普通のレンタルサーバに置くことにしました。よく考えたら、コストめっちゃかかることに気が付いたww

using System;
using UnityEngine;

public abstract class SingletonMonoBehaviour<T> : MonoBehaviour where T : MonoBehaviour
{
    private static T instance;
    public static T Instance
    {
        get
        {
            if (instance == null)
            {
                Type t = typeof(T);

                instance = (T)FindObjectOfType(t);
                if (instance == null)
                {
                    instance = new GameObject(t.Name).AddComponent<T>();
                    Debug.Log(t.Name + " を作成しました。");
                }
            }
            return instance;
        }
    }

    virtual protected void Awake()
    {
        // 他のGameObjectにアタッチされているか調べアタッチされている場合 Destroy 
        if (this != Instance)
        {
            Destroy(this);
            //Destroy(this.gameObject);
            Debug.Log(
                typeof(T) +
                " は既に他のGameObjectにアタッチされているため、コンポーネントを破棄しました." +
                " アタッチされているGameObjectは " + Instance.gameObject.name + " です.");
            return;
        }
        //DontDestroyOnLoad(this.gameObject);
    }

}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Audio;
using UnityEngine.AddressableAssets;

namespace Section31Develop
{
    [System.Serializable]
    public class SoundVolume
    {
        public float bgm = 1.0f;
        public float se = 1.0f;
        public float singleSe = 1.0f;

        public bool mute = false;

        public void Reset()
        {
            bgm = 1.0f;
            se = 1.0f;
            singleSe = 1.0f;
            mute = false;
        }
    }

    public class SoundManager : SingletonMonoBehaviour<SoundManager>
    {
        private AudioMixer mixer = null;
        public AudioMixerGroup[] mixerBgmGroup;
        public AudioMixerGroup[] mixerSpBgmGroup;
        public AudioMixerGroup[] mixerSeGroup;
        public AudioMixerGroup[] mixerSingleSeGroup;
        public AudioMixerGroup[] mixerVoiceGroup;

        /// <summary>オーディオミキサーパス</summary>
        private const string AUDIO_MIXER    = "MasterAudioMixer";
        /// <summary>SEパス</summary>
        private const string AUDIO_SE       = "SE";
        /// <summary>BGMパス</summary>
        private const string AUDIO_BGM      = "BGM";

        public enum AudioType
        {
            SingleSE,
            SE,
            BGM,
            SPBGM,
            Voice
        }
        public SoundVolume volume = new SoundVolume();

        [AssetReferenceLabelRestriction("SE")]
        private List <AudioClip> seClips = new List<AudioClip>();
        [AssetReferenceLabelRestriction("BGM")]
        private List <AudioClip> bgmClips  = new List<AudioClip>();
        private Dictionary<string, int> seIndexes = new Dictionary<string, int>();
        private Dictionary<string, int> bgmIndexes = new Dictionary<string, int>();

        const int seChannel = 4;
        const int singleSeChannel = 4;
        private AudioSource   bgmSource;
        private AudioSource   spBgmSource;
        private AudioSource[] seSources = new AudioSource[seChannel];
        private AudioSource[] singleSeSources = new AudioSource[singleSeChannel];

        Queue<int> seRequestQueue = new Queue<int>();

        //------------------------------------------------------------------------------
        override protected void Awake()
        {
            //必ずbase.Awake()必要
            base.Awake();
            //DontDestroy
            DontDestroyOnLoad(this.gameObject);
            Debug.Log("DontDestroyOnLoad:" + this.gameObject);
        }

        public IEnumerator AudioInitialize()
        {
            //AudioMixerのLoadAsset
            if (mixer == null)
            {
                //AUDIO_MIXERを非同期でロード
                Addressables.LoadAsset<AudioMixer>(AUDIO_MIXER).Completed += op => {
                    //
                    Debug.Log("op.Result " + op.Result);
                    mixer = op.Result;
                };
            }
            do
            {
                //完了待合せ
                yield return null;
            } while (mixer == null);

            //
            mixerSeGroup = mixer.FindMatchingGroups(AudioType.SE.ToString());
            mixerSpBgmGroup = mixer.FindMatchingGroups(AudioType.SPBGM.ToString());
            mixerSingleSeGroup = mixer.FindMatchingGroups(AudioType.SingleSE.ToString());
            mixerBgmGroup = mixer.FindMatchingGroups(AudioType.BGM.ToString());

            //BGM
            bgmSource = gameObject.AddComponent<AudioSource>();
            bgmSource.loop = true;
            bgmSource.outputAudioMixerGroup = mixerBgmGroup[0];
            //SpBGM
            spBgmSource = gameObject.AddComponent<AudioSource>();
            spBgmSource.loop = false;
            spBgmSource.outputAudioMixerGroup = mixerSpBgmGroup[0];
            //SE
            for (int i = 0; i < seSources.Length; i++)
            {
                seSources[i] = gameObject.AddComponent<AudioSource>();
                seSources[i].outputAudioMixerGroup = mixerSeGroup[0];
            }
            //SingleSE
            for (int i = 0; i < singleSeSources.Length; i++)
            {
                singleSeSources[i] = gameObject.AddComponent<AudioSource>();
                singleSeSources[i].outputAudioMixerGroup = mixerSingleSeGroup[0];
            }
        }

        public IEnumerator AudioClipListing(string _label,int _no,string _clip)
        {
            AudioClip m_audioClip = null;
            //_Clipを非同期でロード
            Addressables.LoadAsset<AudioClip>(_clip).Completed += op => {
                //結果をセット
                m_audioClip = op.Result;
                //Debug.Log("ラベル:" + _label + " NO:" + _no + " Clip:" + _clip + " 結果:" + m_audioClip);
            };
            do
            {
                //完了待合せ
                yield return null;
            } while (m_audioClip == null);

            switch (_label)
            {
                case "BGM":
                    {
                        bgmClips.Add(m_audioClip);
                        bgmIndexes[_clip] = _no;
                        break;
                    }
                case "SE":
                    {
                        seClips.Add(m_audioClip);
                        seIndexes[_clip] = _no;
                        break;
                    }
            }
        }

        public IEnumerator AudioClipPreload(string label)
        {
            //LabelのAssetをすべてプレロードする
            var op = Addressables.PreloadDependencies(label, null);
            op.Completed += (res) =>
            {

            };
            yield return op;
        }

        //------------------------------------------------------------------------------
        void Update()
        {
            //SE多重処理
            if (seRequestQueue.Count != 0)
            {
                int sound_index = seRequestQueue.Dequeue();
                PlaySeImpl(sound_index);
            }
        }

        //------------------------------------------------------------------------------
        private void PlaySeImpl(int index)
        {
            if (0 > index || seClips.Count <= index)
            {
                return;
            }

            foreach (AudioSource source in seSources)
            {
                if (false == source.isPlaying)
                {
                    source.clip = seClips[index];
                    source.Play();
                    return;
                }
            }
        }

        //------------------------------------------------------------------------------
        public int GetSeIndex(string name)
        {
            return seIndexes[name];
        }

        //------------------------------------------------------------------------------
        public int GetBgmIndex(string name)
        {
            return bgmIndexes[name];
        }

        //------------------------------------------------------------------------------
        public void PlayBgm(string name)
        {
            int index = bgmIndexes[name];
            PlayBgm(index);
        }

        //------------------------------------------------------------------------------
        public void PlayBgm(int index)
        {
            if (0 > index || bgmClips.Count <= index)
            {
                return;
            }

            if (bgmSource.clip == bgmClips[index])
            {
                return;
            }

            bgmSource.Stop();
            bgmSource.clip = bgmClips[index];
            bgmSource.Play();
        }

        //------------------------------------------------------------------------------
        public void StopBgm()
        {
            bgmSource.Stop();
            bgmSource.clip = null;
        }

        public IEnumerator FadeOutBgm()
        {
            float fadeDeltaTime = 0;
            float fadeOutSeconds = 1.5f;
            bool isFadeOutPlaying = true;
            do
            {
                fadeDeltaTime += Time.deltaTime;
                if (fadeDeltaTime >= fadeOutSeconds)
                {
                    fadeDeltaTime = fadeOutSeconds;
                    isFadeOutPlaying = false;
                    bgmSource.Stop();
                    bgmSource.clip = null;
                }
                bgmSource.volume = (float)(1.0 - fadeDeltaTime / fadeOutSeconds) * volume.bgm;
                Debug.Log("bgmSource.volume " + bgmSource.volume);
                yield return null;
            } while (isFadeOutPlaying);
        }
        //------------------------------------------------------------------------------
        public void PlaySpBgm(string name)
        {
            int index = bgmIndexes[name];
            PlaySpBgm(index);
        }

        //------------------------------------------------------------------------------
        public void PlaySpBgm(int index)
        {
            if (0 > index || bgmClips.Count <= index)
            {
                return;
            }

            if (spBgmSource.clip == bgmClips[index])
            {
                return;
            }

            spBgmSource.Stop();
            spBgmSource.clip = bgmClips[index];
            spBgmSource.Play();
        }

        //------------------------------------------------------------------------------
        public void StopSpBgm()
        {
            spBgmSource.Stop();
            spBgmSource.clip = null;
        }

        public IEnumerator FadeOutSpBgm()
        {
            float fadeDeltaTime = 0;
            float fadeOutSeconds = 1.5f;
            bool isFadeOutPlaying = true;
            do
            {
                fadeDeltaTime += Time.deltaTime;
                if (fadeDeltaTime >= fadeOutSeconds)
                {
                    fadeDeltaTime = fadeOutSeconds;
                    isFadeOutPlaying = false;
                    bgmSource.Stop();
                    bgmSource.clip = null;
                }
                bgmSource.volume = (float)(1.0 - fadeDeltaTime / fadeOutSeconds) * volume.bgm;
                Debug.Log("bgmSource.volume " + bgmSource.volume);
                yield return null;
            } while (isFadeOutPlaying);
        }

        //------------------------------------------------------------------------------
        public void PlaySe(string name)
        {
            PlaySe(GetSeIndex(name));
        }
        //一旦queueに溜め込んで重複を回避しているので
        //再生が1frame遅れる時がある
        public void PlaySe(int index)
        {
            if (!seRequestQueue.Contains(index))
            {
                seRequestQueue.Enqueue(index);
            }
        }
        public void StopSe()
        {
            ClearAllSeRequest();
            foreach (AudioSource source in seSources)
            {
                source.Stop();
                source.clip = null;
            }
        }
        public void ClearAllSeRequest()
        {
            seRequestQueue.Clear();
        }
        //チャンネルに指定の音を鳴らす 
        public void PlaySingleSe(int ch ,string name)
        {
            PlaySingleSe(ch,GetSeIndex(name));
        }
        //
        private void PlaySingleSe(int ch,int index)
        {
            if (0 > index || seClips.Count <= index)
            {
                return;
            }
            if (false == singleSeSources[ch].isPlaying)
            {
                singleSeSources[ch].clip = seClips[index];
                singleSeSources[ch].Play();
                return;
            }
        }
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AddressableAssets;

namespace Section31Develop
{
    public class AddressableAssetTest : SingletonMonoBehaviour<AddressableAssetTest>
    {
        SoundManager soundManager;

        override protected void Awake()
        {
            //必ずbase.Awake()必要
            base.Awake();
            //SoundManager シングルトン
            soundManager = SoundManager.Instance;
            //DontDestroy
            DontDestroyOnLoad(this.gameObject);
            Debug.Log("DontDestroyOnLoad:" + this.gameObject);
        }
        private IEnumerator Start()
        {
            //初期化
            StartCoroutine(soundManager.AudioInitialize());
            //StartCoroutine(soundManager.AudioPreload("BGM"));
            yield return soundManager.AudioClipListing("BGM", 0, "8bit-act01_title_loop");
            yield return soundManager.AudioClipListing("BGM", 1, "8bit-act02_select_loop");
            yield return soundManager.AudioClipListing("BGM", 2, "8bit-act03_gamestart");
            yield return soundManager.AudioClipListing("BGM", 3, "8bit-act04_stage01_loop");

            Debug.Log("再生開始");
            soundManager.PlayBgm("8bit-act04_stage01_loop");
        }
    }
}