【Unity】Addressable Asset SystemでSoundManagerを書き直した。
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");
}
}
}