📗
【Unity】Unityサウンドを使用したAudioManager
はじめに
初めての方も、そうでない方もこんにちは!
現役ゲームプログラマーのたむぼーです。
自己紹介を載せているので、気になる方は見ていただければ嬉しいです!
今回は
Unityサウンドを使用したAudioManager
したので紹介します
AudioManagerについて
使い方
// BGM
// _fadeDuration = フェード時間(float)
await AudioManager.Instance.Play(BgmKeys.Home, _fadeDuration);
// SE
string tag = AudioManager.Instance?.Play(SeKeys.CommonButton);
// 以下のスクリプトをオブジェクトにアタッチする事で
// その対象が出たときにオーディオを再生できます
・PlayBgm
・PlaySe
・PlayJingle
・PlayVoice
スクリプト
オーディオマネージャー(自動生成)
AudioManager.cs
using System.Collections.Generic;
using UnityEngine;
using Cysharp.Threading.Tasks;
using System.Threading;
using System;
using System.Linq;
using Utils; // 詳細は後述してます
namespace AudioSystem
{
sealed public class AudioManager : MonoBehaviour
{
private static bool _isOnDestroy = false;
private static GameObject _audioManagerObject;
private static AudioManager _instance;
private static AudioListener _audioListener;
public static AudioManager Instance
{
get
{
if (_isOnDestroy)
{
return null;
}
AudioListener audioListener = Camera.main?.GetComponent<AudioListener>();
if (_audioListener != null && audioListener != null)
{
Destroy(_audioListener);
_audioListener = null;
}
if (_audioListener == null && audioListener == null && _audioManagerObject != null)
{
_audioListener = _audioManagerObject.GetOrAddComponent<AudioListener>();
}
if (_instance == null)
{
_instance = FindObjectOfType<AudioManager>();
if (_instance == null)
{
_audioManagerObject = new GameObject("AudioManager");
_instance = _audioManagerObject.GetOrAddComponent<AudioManager>();
if (audioListener == null)
{
_audioListener = _audioManagerObject.GetOrAddComponent<AudioListener>();
}
DontDestroyOnLoad(_audioManagerObject);
}
}
return _instance;
}
}
private AudioSettings _audioSettings = null;
private AudioSource[] _bgmAudioSources = null;
private Queue<AudioSource> _seAudioSourcePool = null;
private Queue<AudioSource> _jingleAudioSourcePool = null;
private Queue<AudioSource> _voiceAudioSourcePool = null;
private List<(string, SeKeys, AudioSource, CancellationTokenHelper)> _seActiveAudioSources = null;
private List<(string, JingleKeys, AudioSource, CancellationTokenHelper)> _jingleActiveAudioSources = null;
private List<(string, VoiceKeys, AudioSource, CancellationTokenHelper)> _voiceActiveAudioSources = null;
private BgmKeys _currentBgmKey = BgmKeys.None;
private float _currentFadeDuration = 0.0f;
private CancellationTokenHelper _bgmCtsHelper = null;
private bool _isInitialize = false;
private int _currentBgmIndex = 0;
/// <summary>
/// 破壊時
/// </summary>
private void OnDestroy()
{
_isOnDestroy = true;
_instance = null;
_audioListener = null;
if (_bgmCtsHelper != null)
{
_bgmCtsHelper.Dispose();
}
if (_seActiveAudioSources != null)
{
foreach ((string tag, SeKeys key, AudioSource audioSource, CancellationTokenHelper ctsHelper) seActiveAudioSource in _seActiveAudioSources)
{
if (seActiveAudioSource.ctsHelper != null)
{
seActiveAudioSource.ctsHelper.Dispose();
}
}
}
if (_jingleActiveAudioSources != null)
{
foreach ((string tag, JingleKeys key, AudioSource audioSource, CancellationTokenHelper ctsHelper) jingleActiveAudioSources in _jingleActiveAudioSources)
{
if (jingleActiveAudioSources.ctsHelper != null)
{
jingleActiveAudioSources.ctsHelper.Dispose();
}
}
}
if (_voiceActiveAudioSources != null)
{
foreach ((string tag, VoiceKeys key, AudioSource audioSource, CancellationTokenHelper ctsHelper) voiceActiveAudioSources in _voiceActiveAudioSources)
{
if (voiceActiveAudioSources.ctsHelper != null)
{
voiceActiveAudioSources.ctsHelper.Dispose();
}
}
}
}
/// <summary>
/// Setup
/// </summary>
public void Setup()
{
if (_isInitialize)
{
return;
}
_bgmAudioSources = new AudioSource[AudioSettings.Instance.BgmAudioSourcePoolSize];
for (int i = 0; i < AudioSettings.Instance.BgmAudioSourcePoolSize; i++)
{
AudioSource audioSource = gameObject.AddComponent<AudioSource>();
audioSource.loop = true;
audioSource.volume = AudioSettings.Instance.BgmVolume.max;
audioSource.playOnAwake = false;
_bgmAudioSources[i] = audioSource;
}
_seAudioSourcePool = new Queue<AudioSource>();
_seActiveAudioSources = new List<(string, SeKeys, AudioSource, CancellationTokenHelper)>();
for (int i = 0; i < AudioSettings.Instance.SeAudioSourcePoolSize; i++)
{
AudioSource audioSource = gameObject.AddComponent<AudioSource>();
audioSource.loop = false;
audioSource.volume = AudioSettings.Instance.SeVolume.max;
audioSource.playOnAwake = false;
_seAudioSourcePool.Enqueue(audioSource);
}
_jingleAudioSourcePool = new Queue<AudioSource>();
_jingleActiveAudioSources = new List<(string, JingleKeys, AudioSource, CancellationTokenHelper)>();
for (int i = 0; i < AudioSettings.Instance.JingleAudioSourcePoolSize; i++)
{
AudioSource audioSource = gameObject.AddComponent<AudioSource>();
audioSource.loop = false;
audioSource.volume = AudioSettings.Instance.JingleVolume.max;
audioSource.playOnAwake = false;
_jingleAudioSourcePool.Enqueue(audioSource);
}
_voiceAudioSourcePool = new Queue<AudioSource>();
_voiceActiveAudioSources = new List<(string, VoiceKeys, AudioSource, CancellationTokenHelper)>();
for (int i = 0; i < AudioSettings.Instance.VoiceAudioSourcePoolSize; i++)
{
AudioSource audioSource = gameObject.AddComponent<AudioSource>();
audioSource.loop = false;
audioSource.volume = AudioSettings.Instance.VoiceVolume.max;
audioSource.playOnAwake = false;
_voiceAudioSourcePool.Enqueue(audioSource);
}
_isInitialize = true;
}
/// <summary>
/// 現在再生中のBGMを取得
/// </summary>
public BgmKeys GetCurrentBgm()
{
return _currentBgmKey;
}
/// <summary>
/// 現在再生中のBGMを取得
/// </summary>
public float GetCurrentFadeDuration()
{
return _currentFadeDuration;
}
/// <summary>
/// Play
/// </summary>
public async UniTask Play(BgmKeys key, float fadeDuration = 0.0f)
{
if (AudioSettings.Instance == null)
{
return;
}
Setup();
if (_bgmCtsHelper != null)
{
_bgmCtsHelper.Dispose();
}
_bgmCtsHelper = new CancellationTokenHelper();
_currentBgmKey = key;
_currentFadeDuration = fadeDuration;
if (AudioSettings.Instance.BgmClipList.TryGetValue(key, out AudioClip clip))
{
int nextIndex = (_currentBgmIndex + 1) % 2;
AudioSource currentSource = _bgmAudioSources[_currentBgmIndex];
AudioSource nextSource = _bgmAudioSources[nextIndex];
if (fadeDuration > 0)
{
nextSource.clip = clip;
nextSource.volume = 0;
nextSource.Play();
if (currentSource.isPlaying)
{
currentSource.Stop();
}
await FadeIn(nextSource, fadeDuration, _bgmCtsHelper.Token);
}
else
{
nextSource.clip = clip;
nextSource.volume = AudioSettings.Instance.BgmVolume.max;
nextSource.Play();
if (currentSource.isPlaying)
{
currentSource.Stop();
}
}
_currentBgmIndex = nextIndex;
}
}
/// <summary>
/// FadeOut
/// </summary>
private async UniTask FadeOut(AudioSource source, float duration, CancellationToken token)
{
float startVolume = source.volume;
try
{
while (source.volume > 0)
{
source.volume -= AudioSettings.Instance.BgmVolume.fadeRate * Time.deltaTime / duration;
await UniTask.Yield(token);
}
}
catch (OperationCanceledException)
{
if (source != null)
{
source.volume = 0;
source.Stop();
}
return;
}
source.volume = 0;
source.Stop();
}
/// <summary>
/// FadeIn
/// </summary>
private async UniTask FadeIn(AudioSource source, float duration, CancellationToken token)
{
float startVolume = AudioSettings.Instance.BgmVolume.min;
source.volume = startVolume;
try
{
while (source.volume < AudioSettings.Instance.BgmVolume.max)
{
source.volume += AudioSettings.Instance.BgmVolume.fadeRate * Time.deltaTime / duration;
await UniTask.Yield(token);
}
}
catch (OperationCanceledException)
{
if (source != null)
{
source.volume = AudioSettings.Instance.BgmVolume.max;
}
return;
}
source.volume = AudioSettings.Instance.BgmVolume.max;
}
/// <summary>
/// Stop
/// </summary>
public async UniTask Stop(BgmKeys key, float fadeDuration = 0.5f)
{
if (AudioSettings.Instance == null)
{
return;
}
AudioSource currentSource = _bgmAudioSources[_currentBgmIndex];
if (currentSource.clip == AudioSettings.Instance.BgmClipList[key] && currentSource.isPlaying)
{
if (fadeDuration > 0)
{
await FadeOut(currentSource, fadeDuration, _bgmCtsHelper.Token);
}
currentSource.Stop();
}
}
/// <summary>
/// AllBgmStop
/// </summary>
public async UniTask AllBgmStop(float fadeDuration = 0.5f)
{
foreach (BgmKeys key in AudioSettings.Instance.BgmClipList.Keys)
{
await Stop(key);
}
}
/// <summary>
/// Play
/// </summary>
public string Play(SeKeys key)
{
string tag = string.Empty;
if (AudioSettings.Instance == null)
{
return tag;
}
Setup();
if (key == SeKeys.None)
{
return tag;
}
if (AudioSettings.Instance.SeClipList.ContainsKey(key))
{
AudioSource audioSource = null;
CancellationTokenHelper ctsHelper = new CancellationTokenHelper();
if (_seAudioSourcePool.Count > 0)
{
audioSource = _seAudioSourcePool.Dequeue();
}
else
{
(string tag, SeKeys key, AudioSource audioSource, CancellationTokenHelper oldCtsHelper) seActiveAudioSource = _seActiveAudioSources.OrderBy(source => source.Item3.clip.length - source.Item3.time).FirstOrDefault();
_seActiveAudioSources.Remove(seActiveAudioSource);
seActiveAudioSource.oldCtsHelper.Dispose();
seActiveAudioSource.audioSource.Stop();
audioSource = seActiveAudioSource.audioSource;
}
tag = Guid.NewGuid().ToString();
AudioVolume volume = GetVolume(key);
audioSource.clip = AudioSettings.Instance.SeClipList[key];
audioSource.volume = volume.max;
audioSource.Play();
ReturnToPoolAfterPlay(key, audioSource, ctsHelper.Token).Forget();
_seActiveAudioSources.Add((tag, key, audioSource, ctsHelper));
AdjustVolume(_seActiveAudioSources);
return tag;
}
return tag;
}
/// <summary>
/// SEのボリュームを取得
/// </summary>
private AudioVolume GetVolume(SeKeys key)
{
if (AudioSettings.Instance.SeVolumeOverride != null && AudioSettings.Instance.SeVolumeOverride.ContainsKey(key))
{
return AudioSettings.Instance.SeVolumeOverride[key];
}
return AudioSettings.Instance.SeVolume;
}
/// <summary>
/// 再生終了時にプールに戻す
/// </summary>
private async UniTaskVoid ReturnToPoolAfterPlay(SeKeys key, AudioSource audioSource, CancellationToken token)
{
await UniTask.WaitWhile(() => audioSource.isPlaying, cancellationToken: token);
_seActiveAudioSources.RemoveAll(_ => _.Item3 == audioSource);
_seAudioSourcePool.Enqueue(audioSource);
}
/// <summary>
/// SeStopTag
/// </summary>
public void SeStopTag(string tag)
{
if (AudioSettings.Instance == null)
{
return;
}
if (tag == string.Empty)
{
return;
}
if (_seActiveAudioSources == null)
{
return;
}
foreach ((string tag, SeKeys key, AudioSource audioSource, CancellationTokenHelper ctsHelper) seActiveAudioSource in _seActiveAudioSources)
{
if (tag == seActiveAudioSource.tag && seActiveAudioSource.audioSource.isPlaying)
{
Stop(seActiveAudioSource);
break;
}
}
}
/// <summary>
/// Stop
/// </summary>
public void Stop(SeKeys key)
{
if (AudioSettings.Instance == null)
{
return;
}
if (key == SeKeys.None)
{
return;
}
if (_seActiveAudioSources == null)
{
return;
}
foreach ((string tag, SeKeys key, AudioSource audioSource, CancellationTokenHelper ctsHelper) seActiveAudioSource in _seActiveAudioSources)
{
if (seActiveAudioSource.audioSource.clip == AudioSettings.Instance.SeClipList[key] && seActiveAudioSource.audioSource.isPlaying)
{
Stop(seActiveAudioSource);
break;
}
}
}
/// <summary>
/// AllSeStop
/// </summary>
public void AllSeStop()
{
if (_seActiveAudioSources == null)
{
return;
}
foreach ((string tag, SeKeys key, AudioSource audioSource, CancellationTokenHelper ctsHelper) seActiveAudioSource in _seActiveAudioSources)
{
if (seActiveAudioSource.audioSource.isPlaying)
{
Stop(seActiveAudioSource);
}
}
_seActiveAudioSources.Clear();
}
/// <summary>
/// Stop
/// </summary>
private void Stop((string tag, SeKeys key, AudioSource audioSource, CancellationTokenHelper ctsHelper) seActiveAudioSource)
{
seActiveAudioSource.audioSource.Stop();
_seAudioSourcePool.Enqueue(seActiveAudioSource.audioSource);
_seActiveAudioSources.Remove(seActiveAudioSource);
}
/// <summary>
/// Seのボリューム調整
/// </summary>
private void AdjustVolume(List<(string tag, SeKeys key, AudioSource audioSource, CancellationTokenHelper ctsHelper)> targets)
{
var groupedTargets = targets.GroupBy(target => target.key);
foreach (var group in groupedTargets)
{
AudioVolume seVolume = GetVolume(group.Key);
float maxTotalVolume = seVolume.totalMax;
float totalVolume = group.Sum(target => target.audioSource.volume);
if (totalVolume > maxTotalVolume)
{
float adjustmentFactor = maxTotalVolume / totalVolume;
foreach (var target in group)
{
target.audioSource.volume *= adjustmentFactor;
}
}
}
}
/// <summary>
/// Play
/// </summary>
public string Play(JingleKeys key)
{
string tag = string.Empty;
if (AudioSettings.Instance == null)
{
return tag;
}
Setup();
if (key == JingleKeys.None)
{
return tag;
}
if (AudioSettings.Instance.JingleClipList.ContainsKey(key))
{
AudioSource audioSource = null;
CancellationTokenHelper ctsHelper = new CancellationTokenHelper();
if (_jingleAudioSourcePool.Count > 0)
{
audioSource = _jingleAudioSourcePool.Dequeue();
}
else
{
(string tag, JingleKeys key, AudioSource audioSource, CancellationTokenHelper oldCtsHelper) jingleActiveAudioSource = _jingleActiveAudioSources.FirstOrDefault();
_jingleActiveAudioSources.Remove(jingleActiveAudioSource);
jingleActiveAudioSource.oldCtsHelper.Dispose();
jingleActiveAudioSource.audioSource.Stop();
audioSource = jingleActiveAudioSource.audioSource;
}
tag = Guid.NewGuid().ToString();
AudioVolume volume = GetVolume(key);
audioSource.clip = AudioSettings.Instance.JingleClipList[key];
audioSource.volume = volume.max;
audioSource.Play();
ReturnToPoolAfterPlay(key, audioSource, ctsHelper.Token).Forget();
_jingleActiveAudioSources.Add((tag, key, audioSource, ctsHelper));
AdjustVolume(_jingleActiveAudioSources);
return tag;
}
return tag;
}
/// <summary>
/// Jingleのボリュームを取得
/// </summary>
private AudioVolume GetVolume(JingleKeys key)
{
if (AudioSettings.Instance.JingleVolumeOverride != null && AudioSettings.Instance.JingleVolumeOverride.ContainsKey(key))
{
return AudioSettings.Instance.JingleVolumeOverride[key];
}
return AudioSettings.Instance.JingleVolume;
}
/// <summary>
/// 再生終了時にプールに戻す
/// </summary>
private async UniTaskVoid ReturnToPoolAfterPlay(JingleKeys key, AudioSource audioSource, CancellationToken token)
{
await UniTask.WaitWhile(() => audioSource.isPlaying, cancellationToken: token);
_jingleActiveAudioSources.RemoveAll(_ => _.Item3 == audioSource);
_jingleAudioSourcePool.Enqueue(audioSource);
}
/// <summary>
/// JingleStopTag
/// </summary>
public void JingleStopTag(string tag)
{
if (AudioSettings.Instance == null)
{
return;
}
if (tag == string.Empty)
{
return;
}
if (_jingleActiveAudioSources == null)
{
return;
}
foreach ((string tag, JingleKeys key, AudioSource audioSource, CancellationTokenHelper ctsHelper) jingleActiveAudioSource in _jingleActiveAudioSources)
{
if (tag == jingleActiveAudioSource.tag && jingleActiveAudioSource.audioSource.isPlaying)
{
Stop(jingleActiveAudioSource);
break;
}
}
}
/// <summary>
/// Stop
/// </summary>
public void Stop(JingleKeys key)
{
if (AudioSettings.Instance == null)
{
return;
}
if (key == JingleKeys.None)
{
return;
}
if (_jingleActiveAudioSources == null)
{
return;
}
foreach ((string tag, JingleKeys key, AudioSource audioSource, CancellationTokenHelper ctsHelper) jingleActiveAudioSource in _jingleActiveAudioSources)
{
if (jingleActiveAudioSource.audioSource.clip == AudioSettings.Instance.JingleClipList[key] && jingleActiveAudioSource.audioSource.isPlaying)
{
Stop(jingleActiveAudioSource);
break;
}
}
}
/// <summary>
/// AllJingleStop
/// </summary>
public void AllJingleStop()
{
if (_jingleActiveAudioSources == null)
{
return;
}
foreach ((string tag, JingleKeys key, AudioSource audioSource, CancellationTokenHelper ctsHelper) jingleActiveAudioSource in _jingleActiveAudioSources)
{
if (jingleActiveAudioSource.audioSource.isPlaying)
{
Stop(jingleActiveAudioSource);
}
}
_jingleActiveAudioSources.Clear();
}
/// <summary>
/// Stop
/// </summary>
private void Stop((string tag, JingleKeys key, AudioSource audioSource, CancellationTokenHelper ctsHelper) jingleActiveAudioSource)
{
jingleActiveAudioSource.ctsHelper.Dispose();
jingleActiveAudioSource.audioSource.Stop();
_jingleAudioSourcePool.Enqueue(jingleActiveAudioSource.audioSource);
_jingleActiveAudioSources.Remove(jingleActiveAudioSource);
}
/// <summary>
/// Jingleのボリューム調整
/// </summary>
private void AdjustVolume(List<(string tag, JingleKeys key, AudioSource audioSource, CancellationTokenHelper ctsHelper)> targets)
{
var groupedTargets = targets.GroupBy(target => target.key);
foreach (var group in groupedTargets)
{
AudioVolume volume = GetVolume(group.Key);
float maxTotalVolume = volume.totalMax;
float totalVolume = group.Sum(target => target.audioSource.volume);
if (totalVolume > maxTotalVolume)
{
float adjustmentFactor = maxTotalVolume / totalVolume;
foreach (var target in group)
{
target.audioSource.volume *= adjustmentFactor;
}
}
}
}
/// <summary>
/// Play
/// </summary>
public string Play(VoiceKeys key)
{
string tag = string.Empty;
if (AudioSettings.Instance == null)
{
return tag;
}
Setup();
if (key == VoiceKeys.None)
{
return tag;
}
if (AudioSettings.Instance.VoiceClipList.ContainsKey(key))
{
AudioSource audioSource = null;
CancellationTokenHelper ctsHelper = new CancellationTokenHelper();
if (_voiceAudioSourcePool.Count > 0)
{
audioSource = _voiceAudioSourcePool.Dequeue();
}
else
{
(string tag, VoiceKeys key, AudioSource audioSource, CancellationTokenHelper oldCtsHelper) voiceActiveAudioSource = _voiceActiveAudioSources.OrderBy(source => source.Item3.clip.length - source.Item3.time).FirstOrDefault();
_voiceActiveAudioSources.Remove(voiceActiveAudioSource);
voiceActiveAudioSource.oldCtsHelper.Dispose();
voiceActiveAudioSource.audioSource.Stop();
audioSource = voiceActiveAudioSource.audioSource;
}
tag = Guid.NewGuid().ToString();
AudioVolume volume = GetVolume(key);
audioSource.clip = AudioSettings.Instance.VoiceClipList[key];
audioSource.volume = volume.max;
audioSource.Play();
ReturnToPoolAfterPlay(key, audioSource, ctsHelper.Token).Forget();
_voiceActiveAudioSources.Add((tag, key, audioSource, ctsHelper));
AdjustVolume(_voiceActiveAudioSources);
return tag;
}
return tag;
}
/// <summary>
/// Voiceのボリュームを取得
/// </summary>
private AudioVolume GetVolume(VoiceKeys key)
{
if (AudioSettings.Instance.VoiceVolumeOverride != null && AudioSettings.Instance.VoiceVolumeOverride.ContainsKey(key))
{
return AudioSettings.Instance.VoiceVolumeOverride[key];
}
return AudioSettings.Instance.VoiceVolume;
}
/// <summary>
/// 再生終了時にプールに戻す
/// </summary>
private async UniTaskVoid ReturnToPoolAfterPlay(VoiceKeys key, AudioSource audioSource, CancellationToken token)
{
await UniTask.WaitWhile(() => audioSource.isPlaying, cancellationToken: token);
_voiceActiveAudioSources.RemoveAll(_ => _.Item3 == audioSource);
_voiceAudioSourcePool.Enqueue(audioSource);
}
/// <summary>
/// VoiceStopTag
/// </summary>
public void VoiceStopTag(string tag)
{
if (AudioSettings.Instance == null)
{
return;
}
if (tag == string.Empty)
{
return;
}
if (_voiceActiveAudioSources == null)
{
return;
}
foreach ((string tag, VoiceKeys key, AudioSource audioSource, CancellationTokenHelper ctsHelper) voiceActiveAudioSource in _voiceActiveAudioSources)
{
if (tag == voiceActiveAudioSource.tag && voiceActiveAudioSource.audioSource.isPlaying)
{
Stop(voiceActiveAudioSource);
break;
}
}
}
/// <summary>
/// Stop
/// </summary>
public void Stop(VoiceKeys key)
{
if (AudioSettings.Instance == null)
{
return;
}
if (key == VoiceKeys.None)
{
return;
}
if (_voiceActiveAudioSources == null)
{
return;
}
foreach ((string tag, VoiceKeys key, AudioSource audioSource, CancellationTokenHelper ctsHelper) voiceActiveAudioSource in _voiceActiveAudioSources)
{
if (voiceActiveAudioSource.audioSource.clip == AudioSettings.Instance.VoiceClipList[key] && voiceActiveAudioSource.audioSource.isPlaying)
{
Stop(voiceActiveAudioSource);
break;
}
}
}
/// <summary>
/// AllVoiceStop
/// </summary>
public void AllVoiceStop()
{
if (_voiceActiveAudioSources == null)
{
return;
}
foreach ((string tag, VoiceKeys key, AudioSource audioSource, CancellationTokenHelper ctsHelper) voiceActiveAudioSource in _voiceActiveAudioSources)
{
if (voiceActiveAudioSource.audioSource.isPlaying)
{
Stop(voiceActiveAudioSource);
}
}
_voiceActiveAudioSources.Clear();
}
/// <summary>
/// Stop
/// </summary>
private void Stop((string tag, VoiceKeys key, AudioSource audioSource, CancellationTokenHelper ctsHelper) voiceActiveAudioSource)
{
voiceActiveAudioSource.audioSource.Stop();
_voiceAudioSourcePool.Enqueue(voiceActiveAudioSource.audioSource);
_voiceActiveAudioSources.Remove(voiceActiveAudioSource);
}
/// <summary>
/// Seのボリューム調整
/// </summary>
private void AdjustVolume(List<(string tag, VoiceKeys key, AudioSource audioSource, CancellationTokenHelper ctsHelper)> targets)
{
var groupedTargets = targets.GroupBy(target => target.key);
foreach (var group in groupedTargets)
{
AudioVolume volume = GetVolume(group.Key);
float maxTotalVolume = volume.totalMax;
float totalVolume = group.Sum(target => target.audioSource.volume);
if (totalVolume > maxTotalVolume)
{
float adjustmentFactor = maxTotalVolume / totalVolume;
foreach (var target in group)
{
target.audioSource.volume *= adjustmentFactor;
}
}
}
}
}
}
オーディオ設定(Singleton)
AudioSettings.cs
using System.Collections.Generic;
using UnityEngine;
using Utils; // 詳細は後述してます
namespace AudioSystem
{
sealed public class AudioSettings : SingletonMonoBehaviour<AudioSettings>
{
[Header("BGMの音量")]
[SerializeField]
private AudioVolume _bgmVolume = null;
public AudioVolume BgmVolume => _bgmVolume;
[Space]
[Header("SEの音量")]
[SerializeField]
private AudioVolume _seVolume = null;
public AudioVolume SeVolume => _seVolume;
[SerializeField]
private SerializableDictionary<SeKeys, AudioVolume> _seVolumeOverride = null;
public SerializableDictionary<SeKeys, AudioVolume> SeVolumeOverride => _seVolumeOverride;
[Space]
[Header("Jingleの音量")]
[SerializeField]
private AudioVolume _jingleVolume = null;
public AudioVolume JingleVolume => _jingleVolume;
[SerializeField]
private SerializableDictionary<JingleKeys, AudioVolume> _jingleVolumeOverride = null;
public SerializableDictionary<JingleKeys, AudioVolume> JingleVolumeOverride => _jingleVolumeOverride;
[Space]
[Header("Voiceの音量")]
[SerializeField]
private AudioVolume _voiceVolume = null;
public AudioVolume VoiceVolume => _voiceVolume;
[SerializeField]
private SerializableDictionary<VoiceKeys, AudioVolume> _voiceVolumeOverride = null;
public SerializableDictionary<VoiceKeys, AudioVolume> VoiceVolumeOverride => _voiceVolumeOverride;
[Space]
[Header("同時再生可能数")]
[SerializeField]
private int _bgmAudioSourcePoolSize = 2;
public int BgmAudioSourcePoolSize => _bgmAudioSourcePoolSize;
[SerializeField]
private int _seAudioSourcePoolSize = 30;
public int SeAudioSourcePoolSize => _seAudioSourcePoolSize;
[SerializeField]
private int _jingleAudioSourcePoolSize = 5;
public int JingleAudioSourcePoolSize => _jingleAudioSourcePoolSize;
[SerializeField]
private int _voiceAudioSourcePoolSize = 5;
public int VoiceAudioSourcePoolSize => _voiceAudioSourcePoolSize;
[Space]
[Header("クリップとキーの紐づけ")]
[SerializeField]
private SerializableDictionary<BgmKeys, AudioClip> _bgmClipList = null;
public Dictionary<BgmKeys, AudioClip> BgmClipList => _bgmClipList;
[SerializeField]
private SerializableDictionary<SeKeys, AudioClip> _seClipList = null;
public Dictionary<SeKeys, AudioClip> SeClipList => _seClipList;
[SerializeField]
private SerializableDictionary<JingleKeys, AudioClip> _jingleClipList = null;
public Dictionary<JingleKeys, AudioClip> JingleClipList => _jingleClipList;
[SerializeField]
private SerializableDictionary<VoiceKeys, AudioClip> _voiceClipList = null;
public Dictionary<VoiceKeys, AudioClip> VoiceClipList => _voiceClipList;
}
}
オーディオ音量
AudioVolume.cs
using System;
using UnityEngine;
namespace AudioSystem
{
[Serializable]
public class AudioVolume
{
[Range(0.0f, 1.0f)]
public float min = 0.0f;
[Range(0.0f, 1.0f)]
public float max = 1.0f;
[Range(0.0f, 1.0f)]
public float fadeRate = 0.01f;
[Range(0.0f, 10.0f)]
public float totalMax = 1.0f;
}
}
オーディオKeys
AudioKeys.cs
namespace AudioSystem
{
public enum BgmKeys
{
None,
}
public enum SeKeys
{
None,
}
public enum JingleKeys
{
None,
}
public enum VoiceKeys
{
None,
}
}
再生スクリプト
BGM再生
PlayBgm.cs
using UnityEngine;
namespace AudioSystem
{
public class PlayBgm : MonoBehaviour
{
[SerializeField]
private BgmKeys _key = BgmKeys.None;
[SerializeField]
private bool _isSwitching = false;
[SerializeField]
private float _fadeDuration = 0.5f;
private BgmKeys _prevKey = BgmKeys.None;
/// <summary>
/// アクティブになった時
/// </summary>
private async void OnEnable()
{
if (AudioManager.Instance != null)
{
if (_isSwitching)
{
_prevKey = AudioManager.Instance.GetCurrentBgm();
}
await AudioManager.Instance.Play(_key, _fadeDuration);
}
}
/// <summary>
/// 破壊時
/// </summary>
private async void OnDestroy()
{
if (AudioManager.Instance != null)
{
if (_isSwitching && _prevKey != BgmKeys.None)
{
await AudioManager.Instance.Play(_prevKey, _fadeDuration);
}
}
}
}
}
SE再生
PlaySe.cs
using Cysharp.Threading.Tasks;
using System;
using UnityEngine;
namespace AudioSystem
{
public class PlaySe : MonoBehaviour
{
[SerializeField]
private SeKeys _key = SeKeys.None;
[SerializeField]
private float _waitTime = 0.0f;
[SerializeField]
private bool _isEndPlayback = false;
[SerializeField, ReadOnly]
private string _tag = string.Empty;
/// <summary>
/// アクティブになった時
/// </summary>
private async void OnEnable()
{
if (_waitTime > 0.0f)
{
await UniTask.Delay(TimeSpan.FromSeconds(_waitTime));
}
_tag = AudioManager.Instance?.Play(_key);
}
/// <summary>
/// アクティブになった時
/// </summary>
private void OnDestroy()
{
if (_isEndPlayback)
{
return;
}
AudioManager.Instance?.SeStopTag(_tag);
}
}
}
Jingle再生
PlayJingle.cs
using Cysharp.Threading.Tasks;
using System;
using UnityEngine;
namespace AudioSystem
{
public class PlayJingle : MonoBehaviour
{
[SerializeField]
private JingleKeys _key = JingleKeys.None;
[SerializeField]
private float _waitTime = 0.0f;
[SerializeField]
private bool _isEndPlayback = false;
[SerializeField, ReadOnly]
private string _tag = string.Empty;
/// <summary>
/// アクティブになった時
/// </summary>
private async void OnEnable()
{
if (_waitTime > 0.0f)
{
await UniTask.Delay(TimeSpan.FromSeconds(_waitTime));
}
_tag = AudioManager.Instance.Play(_key);
}
/// <summary>
/// アクティブになった時
/// </summary>
private void OnDestroy()
{
if (_isEndPlayback)
{
return;
}
AudioManager.Instance?.JingleStopTag(_tag);
}
}
}
Voice再生
PlayVoice.cs
using Cysharp.Threading.Tasks;
using System;
using UnityEngine;
namespace AudioSystem
{
public class PlayVoice : MonoBehaviour
{
[SerializeField]
private VoiceKeys _key = VoiceKeys.None;
[SerializeField]
private float _waitTime = 0.0f;
[SerializeField]
private bool _isEndPlayback = true;
[SerializeField, ReadOnly]
private string _tag = string.Empty;
/// <summary>
/// アクティブになった時
/// </summary>
private async void OnEnable()
{
if (_waitTime > 0.0f)
{
await UniTask.Delay(TimeSpan.FromSeconds(_waitTime));
}
_tag = AudioManager.Instance?.Play(_key);
}
/// <summary>
/// アクティブになった時
/// </summary>
private async void OnDestroy()
{
if (_isEndPlayback)
{
return;
}
AudioManager.Instance?.VoiceStopTag(_tag);
}
}
}
Utilsについて
以下の機能を共通処理としてUtils名前空間に定義してます
・GetOrAddComponent
以下のようなEX処理を定義してます。
/// <summary>
/// コンポーネント取得/追加
/// </summary>
static public T GetOrAddComponent<T>(this GameObject obj) where T : Component
{
T component = obj.GetComponent<T>();
if (component != null)
{
return component;
}
return obj.AddComponent<T>();
}
・CancellationTokenHelper
こちらの記事で紹介している、UniTaskのキャンセル処理を使用しています。
・SingletonMonoBehaviour
Discussion