366 lines
14 KiB
C#
366 lines
14 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Threading;
|
|
using Code.Infrastructure.Configs;
|
|
using Cysharp.Threading.Tasks;
|
|
using LitMotion;
|
|
using UnityEngine;
|
|
using UnityEngine.AddressableAssets;
|
|
using UnityEngine.Audio;
|
|
|
|
namespace Code.Infrastructure.Audio
|
|
{
|
|
public interface IAudioService
|
|
{
|
|
UniTaskVoid PlayOneShot(AudioPartition groupPartition, int id, AudioGroupType audioGroupType,
|
|
Vector3 position = new Vector3(),
|
|
AudioSourceType sourceType = AudioSourceType.NoBlend,
|
|
AudioSource audioSource = null, bool isLOD = false);
|
|
|
|
UniTaskVoid PlayRepeatedly(AudioPartition groupPartition, int id, AudioGroupType audioGroupType,
|
|
Vector3 position = new Vector3(),
|
|
AudioSourceType sourceType = AudioSourceType.NoBlend, AudioSource audioSource = null,
|
|
CancellationTokenSource cancellationTokenSource = null);
|
|
|
|
void CancelMusic(AudioPartition groupPartition, int id);
|
|
void ReleaseCancellationSource(AudioPartition groupPartition, int id);
|
|
|
|
UniTask ApplyParameter(string effectName, float value, float time, Action callback = null,
|
|
CancellationTokenSource cts = null);
|
|
|
|
UniTask ChangeGroupVolume(AudioGroupType groupType, float value, float time, Action callback = null,
|
|
CancellationTokenSource cts = null);
|
|
|
|
void SetAudioListener(AudioListener listener);
|
|
|
|
UniTaskVoid PlayRepeatedly(AudioPartition groupPartition, int id, AudioGroupType audioGroupType,
|
|
Transform parent,
|
|
AudioSourceType sourceType = AudioSourceType.NoBlend, AudioSource audioSource = null,
|
|
CancellationTokenSource cancellationTokenSource = null);
|
|
|
|
UniTaskVoid PlayOneShot(AudioPartition groupPartition, int id, AudioGroupType audioGroupType,
|
|
Transform parent,
|
|
AudioSourceType sourceType = AudioSourceType.NoBlend,
|
|
AudioSource audioSource = null);
|
|
}
|
|
|
|
public interface IAudioServiceInitializer
|
|
{
|
|
UniTask LoadMixerAsset();
|
|
}
|
|
|
|
public partial class AudioService : IAudioService, IAudioServiceInitializer
|
|
{
|
|
private IAudioFactory _audioFactory;
|
|
private IAudioSourcePool _pool;
|
|
private AudioMixer _mixer;
|
|
private AudioListener _listener;
|
|
private Dictionary<AudioGroupType, AudioMixerGroup> _audioMixerGroups = new();
|
|
|
|
public AudioService(IAudioFactory audioFactory, IAudioSourcePool pool,
|
|
IConfigLocator configLocator)
|
|
{
|
|
_audioFactory = audioFactory;
|
|
_pool = pool;
|
|
|
|
_mixerConfig = configLocator.TryGetConfigAssetReference<AudioMixerSettings>();
|
|
}
|
|
|
|
public void SetAudioListener(AudioListener listener)
|
|
{
|
|
if(_listener)
|
|
_listener.enabled = false;
|
|
|
|
_listener = listener;
|
|
_listener.enabled = true;
|
|
}
|
|
private partial void InitializeMixer();
|
|
|
|
public async UniTask LoadMixerAsset()
|
|
{
|
|
var audioMixer = await Addressables.LoadAssetAsync<AudioMixerSettings>(_mixerConfig);
|
|
|
|
_mixer = audioMixer.mixerAsset;
|
|
|
|
InitializeMixer();
|
|
}
|
|
|
|
public async UniTaskVoid PlayOneShot(AudioPartition groupPartition, int id, AudioGroupType audioGroupType,
|
|
Vector3 position = new Vector3(),
|
|
AudioSourceType sourceType = AudioSourceType.NoBlend,
|
|
AudioSource audioSource = null, bool isLOD = false)
|
|
{
|
|
if (!CheckClip(groupPartition, id)) return;
|
|
|
|
if (!audioSource) audioSource = _pool.GetSource(sourceType);
|
|
|
|
audioSource.transform.position = position;
|
|
audioSource.outputAudioMixerGroup = _audioMixerGroups[audioGroupType];
|
|
|
|
if (sourceType == AudioSourceType.NoBlend)
|
|
audioSource.PlayOneShot(GetSingleClip(groupPartition, id));
|
|
if (sourceType == AudioSourceType.Full3D)
|
|
{
|
|
audioSource.PlayOneShot(isLOD
|
|
? GetLODClip(groupPartition, id, GetLODLevelOnPosition(position))
|
|
: GetSingleClip(groupPartition, id));
|
|
}
|
|
|
|
await UniTask.NextFrame();
|
|
await UniTask.WaitUntilValueChanged(audioSource, _ => !audioSource.isPlaying);
|
|
|
|
_pool.ReleaseSource(audioSource);
|
|
}
|
|
|
|
public async UniTaskVoid PlayOneShot(AudioPartition groupPartition, int id, AudioGroupType audioGroupType,
|
|
Transform parent,
|
|
AudioSourceType sourceType = AudioSourceType.NoBlend,
|
|
AudioSource audioSource = null)
|
|
{
|
|
if (!CheckClip(groupPartition, id)) return;
|
|
|
|
if (!audioSource) audioSource = _pool.GetSource(sourceType);
|
|
|
|
audioSource.GetComponent<AudioSourceFollower>().FollowTarget = parent;
|
|
|
|
audioSource.outputAudioMixerGroup = _audioMixerGroups[audioGroupType];
|
|
|
|
if (sourceType == AudioSourceType.NoBlend)
|
|
audioSource.PlayOneShot(GetSingleClip(groupPartition, id));
|
|
if (sourceType == AudioSourceType.Full3D)
|
|
audioSource.PlayOneShot(GetLODClip(groupPartition, id, GetLODLevelOnPosition(parent.position)));
|
|
|
|
await UniTask.NextFrame();
|
|
await UniTask.WaitUntilValueChanged(audioSource, _ => !audioSource.isPlaying);
|
|
|
|
_pool.ReleaseSource(audioSource);
|
|
}
|
|
private Dictionary<AudioPartitionIdPair, CancellationTokenSource> _cancellationTokenSources = new();
|
|
private AssetReference _mixerConfig;
|
|
|
|
public void CancelMusic(AudioPartition groupPartition, int id)
|
|
{
|
|
var key = new AudioPartitionIdPair(groupPartition, id);
|
|
|
|
if (!_cancellationTokenSources.ContainsKey(key)) return;
|
|
|
|
_cancellationTokenSources[key].Cancel();
|
|
_cancellationTokenSources[key].Dispose();
|
|
_cancellationTokenSources.Remove(key);
|
|
}
|
|
|
|
public void ReleaseCancellationSource(AudioPartition groupPartition, int id)
|
|
{
|
|
_cancellationTokenSources.Remove(new AudioPartitionIdPair(groupPartition, id));
|
|
}
|
|
|
|
public async UniTaskVoid PlayRepeatedly(AudioPartition groupPartition, int id, AudioGroupType audioGroupType,
|
|
Vector3 position = new(),
|
|
AudioSourceType sourceType = AudioSourceType.NoBlend, AudioSource audioSource = null,
|
|
CancellationTokenSource cancellationTokenSource = null)
|
|
{
|
|
if (!CheckClip(groupPartition, id)) return;
|
|
|
|
if (cancellationTokenSource == null)
|
|
{
|
|
cancellationTokenSource = new CancellationTokenSource();
|
|
_cancellationTokenSources[new AudioPartitionIdPair(groupPartition, id)] = cancellationTokenSource;
|
|
}
|
|
|
|
if (!audioSource) audioSource = _pool.GetSource(sourceType);
|
|
|
|
if (!audioSource.resource)
|
|
{
|
|
if (sourceType == AudioSourceType.NoBlend)
|
|
audioSource.resource = GetSingleClip(groupPartition, id);
|
|
if (sourceType == AudioSourceType.Full3D)
|
|
audioSource.resource = GetLODClip(groupPartition, id, GetLODLevelOnPosition(position));
|
|
}
|
|
|
|
audioSource.outputAudioMixerGroup = _audioMixerGroups[audioGroupType];
|
|
audioSource.Play();
|
|
|
|
var (isCanceled, _) = await UniTask.WaitUntilValueChanged(audioSource,
|
|
_ => !audioSource.isPlaying,
|
|
cancellationToken: cancellationTokenSource.Token).SuppressCancellationThrow();
|
|
|
|
if (isCanceled)
|
|
{
|
|
cancellationTokenSource.Dispose();
|
|
|
|
_pool.ReleaseSource(audioSource);
|
|
|
|
ReleaseCancellationSource(groupPartition, id);
|
|
|
|
return;
|
|
}
|
|
|
|
PlayRepeatedly(groupPartition, id, AudioGroupType.Music, position, sourceType, audioSource,
|
|
cancellationTokenSource)
|
|
.Forget();
|
|
}
|
|
|
|
public async UniTaskVoid PlayRepeatedly(AudioPartition groupPartition, int id, AudioGroupType audioGroupType,
|
|
Transform parent,
|
|
AudioSourceType sourceType = AudioSourceType.NoBlend, AudioSource audioSource = null,
|
|
CancellationTokenSource cancellationTokenSource = null)
|
|
{
|
|
if (!CheckClip(groupPartition, id)) return;
|
|
|
|
if (cancellationTokenSource == null)
|
|
{
|
|
cancellationTokenSource = new CancellationTokenSource();
|
|
_cancellationTokenSources[new AudioPartitionIdPair(groupPartition, id)] = cancellationTokenSource;
|
|
}
|
|
|
|
if (!audioSource)
|
|
{
|
|
audioSource = _pool.GetSource(sourceType);
|
|
audioSource.GetComponent<AudioSourceFollower>().FollowTarget = parent;
|
|
}
|
|
|
|
if (!audioSource.resource)
|
|
{
|
|
if (sourceType == AudioSourceType.NoBlend)
|
|
audioSource.resource = GetSingleClip(groupPartition, id);
|
|
if (sourceType == AudioSourceType.Full3D)
|
|
audioSource.resource = GetLODClip(groupPartition, id, GetLODLevelOnPosition(parent.position));
|
|
}
|
|
|
|
audioSource.outputAudioMixerGroup = _audioMixerGroups[audioGroupType];
|
|
audioSource.Play();
|
|
|
|
var (isCanceled, _) = await UniTask.WaitUntilValueChanged(audioSource,
|
|
_ => !audioSource.isPlaying,
|
|
cancellationToken: cancellationTokenSource.Token).SuppressCancellationThrow();
|
|
|
|
if (isCanceled)
|
|
{
|
|
cancellationTokenSource.Dispose();
|
|
|
|
_pool.ReleaseSource(audioSource);
|
|
|
|
ReleaseCancellationSource(groupPartition, id);
|
|
|
|
return;
|
|
}
|
|
|
|
PlayRepeatedly(groupPartition, id, AudioGroupType.Music, parent, sourceType, audioSource,
|
|
cancellationTokenSource)
|
|
.Forget();
|
|
}
|
|
private AudioLODLevel GetLODLevelOnPosition(Vector3 position)
|
|
{
|
|
var distance = Vector3.Distance(position, _listener.transform.position);
|
|
AudioLODLevel lodLevel;
|
|
if (distance < 5f) lodLevel = AudioLODLevel.Interior;
|
|
else if (distance < 20f) lodLevel = AudioLODLevel.Close;
|
|
else if (distance < 50f) lodLevel = AudioLODLevel.Distant;
|
|
else lodLevel = AudioLODLevel.Far;
|
|
return lodLevel;
|
|
}
|
|
public AudioClip GetSingleClip(AudioPartition groupPartition, int id)
|
|
{
|
|
var reference = _audioFactory.ReferenceDictionary[groupPartition][id];
|
|
var asSingle = reference as AudioReferenceSingle;
|
|
|
|
if(asSingle != null) return asSingle.Clip;
|
|
return ((AudioReferenceRandom)reference).Clip;
|
|
}
|
|
|
|
public AudioClip GetLODClip(AudioPartition groupPartition, int id, AudioLODLevel lodLevel)
|
|
{
|
|
switch (lodLevel)
|
|
{
|
|
case AudioLODLevel.Interior:
|
|
return ((AudioReferenceLODed)_audioFactory.ReferenceDictionary[groupPartition][id]).Interior;
|
|
case AudioLODLevel.Close:
|
|
return ((AudioReferenceLODed)_audioFactory.ReferenceDictionary[groupPartition][id]).Close;
|
|
case AudioLODLevel.Distant:
|
|
return ((AudioReferenceLODed)_audioFactory.ReferenceDictionary[groupPartition][id]).Distant;
|
|
case AudioLODLevel.Far:
|
|
return ((AudioReferenceLODed)_audioFactory.ReferenceDictionary[groupPartition][id]).Far;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public bool CheckClip(AudioPartition groupPartition, int id)
|
|
{
|
|
return _audioFactory.ReferenceDictionary.ContainsKey(groupPartition)
|
|
&& _audioFactory.ReferenceDictionary[groupPartition].ContainsKey(id);
|
|
}
|
|
|
|
public async UniTask ApplyParameter(string effectName,
|
|
float value,
|
|
float time,
|
|
Action callback = null,
|
|
CancellationTokenSource cts = null)
|
|
{
|
|
_mixer.GetFloat(effectName, out float startValue);
|
|
if (time == 0f) _mixer.SetFloat(effectName, value);
|
|
else
|
|
{
|
|
// TODO Проверить отмену
|
|
if (cts != null)
|
|
await LMotion.Create(startValue, value, time)
|
|
.WithOnComplete(callback)
|
|
.WithOnCancel(() => _mixer.SetFloat(effectName, value))
|
|
.Bind(x => _mixer.SetFloat(effectName, x))
|
|
.ToUniTask(cts.Token);
|
|
else
|
|
await LMotion.Create(startValue, value, time)
|
|
.WithOnComplete(callback)
|
|
.WithOnCancel(() => _mixer.SetFloat(effectName, value))
|
|
.Bind(x => _mixer.SetFloat(effectName, x))
|
|
.ToUniTask();
|
|
}
|
|
}
|
|
|
|
private Dictionary<AudioGroupType, CancellationTokenSource> _volumeCancellationSources = new();
|
|
|
|
// TODO Очередь принятия параметров
|
|
public async UniTask ChangeGroupVolume(
|
|
AudioGroupType groupType,
|
|
float value,
|
|
float time,
|
|
Action callback = null,
|
|
CancellationTokenSource cts = null)
|
|
{
|
|
if (_volumeCancellationSources.ContainsKey(groupType)) _volumeCancellationSources[groupType].Cancel();
|
|
|
|
_volumeCancellationSources[groupType] = cts ?? new CancellationTokenSource();
|
|
|
|
await ApplyParameter(_audioMixerGroups[groupType].name, Mathf.Log10(value == 0f ? 0.001f : value) * 20,
|
|
time,
|
|
callback, cts);
|
|
}
|
|
}
|
|
|
|
public struct AudioPartitionIdPair : IEquatable<AudioPartitionIdPair>
|
|
{
|
|
public AudioPartition AudioPartition;
|
|
public int ID;
|
|
|
|
public AudioPartitionIdPair(AudioPartition audioPartition, int id)
|
|
{
|
|
AudioPartition = audioPartition;
|
|
ID = id;
|
|
}
|
|
|
|
public bool Equals(AudioPartitionIdPair other)
|
|
{
|
|
return AudioPartition == other.AudioPartition && ID == other.ID;
|
|
}
|
|
|
|
public override bool Equals(object obj)
|
|
{
|
|
return obj is AudioPartitionIdPair other && Equals(other);
|
|
}
|
|
|
|
public override int GetHashCode()
|
|
{
|
|
return HashCode.Combine((int)AudioPartition, ID);
|
|
}
|
|
}
|
|
} |