NLDClient-yudde/ProjectNLD/Assets/Code/Scripts/Framework/Audio/AudioManager.cs

930 lines
26 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

using System;
using System.Collections.Generic;
using System.Linq;
using Cysharp.Threading.Tasks;
using Framework;
using PhxhSDK;
using UnityEngine;
using UnityEngine.Audio;
using Constants = Framework.Constants;
using Object = UnityEngine.Object;
/// <summary>
/// 音频管理器 - 统一的音频系统管理
/// - 对象池管理
/// - 播放控制
/// - 资源加载
/// - 音量管理
/// - 业务状态管理
/// </summary>
public class AudioManager: Singlenton<AudioManager>, IStart, IInitable, IUpdatable
{
#region Fields & Properties
// Unity GameObject
public GameObject AudioRoot;
public AudioListener GlobalListener;
// 业务状态
public static bool DisableAudio = false;
public bool IsPlayingAlbum { get; set; }
public bool IsPlayingBGM { get; set; }
public bool GlobalIsOpenSpeak;
// 对象池限制
public int MusicPoolLimit = 1;
public int SoundPoolLimit = 100;
public int SpeakPoolLimit = 1;
// 音频池
private Dictionary<int, AudioPlayer> _allAudioDic;
private Dictionary<string, long> _playSoundTimeStampDic;
// 播放列表(用于限制播放数量)
private List<int> _playingMusicList;
private List<int> _playingSoundList;
private List<int> _playingSpeakList;
// Listener 跟随状态
private bool _isFollowCamera = false;
#endregion
#region Volume Properties (便捷访问 AudioVolumeSettings)
/// <summary>
/// 全局总音量
/// </summary>
public float GlobalVolume
{
get => AudioVolumeSettings.Instance.GlobalVolume;
set => AudioVolumeSettings.Instance.GlobalVolume = value;
}
/// <summary>
/// 音乐音量
/// </summary>
public float GlobalMusicVolume
{
get => AudioVolumeSettings.Instance.MusicVolume;
set => AudioVolumeSettings.Instance.MusicVolume = value;
}
/// <summary>
/// 音效音量
/// </summary>
public float GlobalSoundsVolume
{
get => AudioVolumeSettings.Instance.SoundVolume;
set => AudioVolumeSettings.Instance.SoundVolume = value;
}
/// <summary>
/// 语音音量
/// </summary>
public float GlobalSpeakVolume
{
get => AudioVolumeSettings.Instance.SpeakVolume;
set => AudioVolumeSettings.Instance.SpeakVolume = value;
}
/// <summary>
/// 设置音乐开关
/// </summary>
public void SetMusicEnable(bool isEnable)
{
GlobalMusicVolume = isEnable ? 1 : 0;
}
/// <summary>
/// 设置音效开关
/// </summary>
public void SetSoundEnable(bool isEnable)
{
GlobalSoundsVolume = isEnable ? 1 : 0;
}
#endregion
#region Lifecycle (Framework)
private void AfterSwitchCamera(bool isRegister = true)
{
if (isRegister)
{
CameraManager.Instance.AfterSwitchMainCamera += AfterSwitchMainCamera;
}
else
{
CameraManager.Instance.AfterSwitchMainCamera -= AfterSwitchMainCamera;
}
}
public void Init()
{
_allAudioDic = new Dictionary<int, AudioPlayer>();
_playingMusicList = new List<int>();
_playingSoundList = new List<int>();
_playingSpeakList = new List<int>();
_playSoundTimeStampDic = new();
EventManager.Instance.Register(EventManager.EventName.Audio_ErrorAudioPlay, _OnErrorAudioPlay);
AfterSwitchCamera();
}
public void Release()
{
EventManager.Instance.Unregister(EventManager.EventName.Audio_ErrorAudioPlay, _OnErrorAudioPlay);
AfterSwitchCamera(false);
}
public void Start()
{
if (!AudioRoot)
{
AudioRoot = new GameObject();
AudioRoot.name = "Audio(Singleton)";
Object.DontDestroyOnLoad(AudioRoot);
GlobalListener = AudioRoot.AddComponent<AudioListener>();
_InitListenerPos();
}
}
public void Update(float dt)
{
_SetListenerPos();
// 遍历所有音频
foreach (var kv in _allAudioDic)
{
var audio = kv.Value;
if (audio.State != PlayState.Stopped)
{
audio.Update(dt);
}
}
}
#endregion
#region Asset Loading
private string GetFullPath(string key, bool isI18N = false)
{
var cfg = TableManager.Instance.Tables.AudioSourceConfig.GetOrDefault(key);
if (cfg == null)
{
DebugUtil.LogError($"音频Key{key}找不到!");
return "";
}
var path = cfg.Path;
if (isI18N)
{
if (path.Contains(LanguageManager.I18N))
{
var eLanguage = LanguageManager.Instance.GetCurrentLanguage();
var replacePath = LanguageManager.PathDic[eLanguage];
path = path.Replace(LanguageManager.I18N, replacePath);
}
return path;
}
return cfg.Path;
}
private float _GetNormalizeVolume(string key)
{
var cfg = TableManager.Instance.Tables.AudioSourceConfig.GetOrDefault(key);
if (cfg == null)
{
DebugUtil.LogError($"音频Key{key}找不到!");
return 0f;
}
var volume = cfg.VolumeScale;
return volume;
}
private bool _CheckPath(string key)
{
var cfg = TableManager.Instance.Tables.AudioSourceConfig;
if (cfg.GetOrDefault(key) != null)
{
return true;
}
return false;
}
public async UniTask<float> GetClipLength(string key)
{
var path = GetFullPath(key);
var clip = await AssetManager.Instance.LoadAssetAsync<AudioClip>(path);
if (clip)
{
return clip.length;
}
return -1;
}
#endregion
#region Audio Pool Management
/// <summary>
/// 获取可用的Audio
/// </summary>
private AudioPlayer _GetUsableAudio(PhxhSDK.EAudioType eAudioType, bool isLoop)
{
try
{
var limit = _CheckIsAudioLimited(eAudioType);
if (limit)
{
return _GetOldAudio(eAudioType);
}
var freeAudio = _GetFreeAudio(eAudioType);
return freeAudio ?? _CreateNewAudio(eAudioType, isLoop);
}
catch (Exception e)
{
DebugUtil.LogError($"AudioManger, GetUsableAudio Fail.{e}");
}
return null;
}
private AudioPlayer _GetOldAudio(PhxhSDK.EAudioType eAudioType)
{
var playingList = _GetPlayingList(eAudioType);
if (playingList.Count > 0)
{
var audio = GetAudioByID(playingList[0]);
return audio;
}
// 如果没有正在播放的音频,创建新的(使用默认不循环)
return _CreateNewAudio(eAudioType, isLoop: false);
}
private int _GetLimit(PhxhSDK.EAudioType eAudioType)
{
switch (eAudioType)
{
case PhxhSDK.EAudioType.Music:
return MusicPoolLimit;
case PhxhSDK.EAudioType.Sound:
return SoundPoolLimit;
case PhxhSDK.EAudioType.Speak:
return SpeakPoolLimit;
default:
return -1;
}
}
private List<int> _GetPlayingList(PhxhSDK.EAudioType eAudioType)
{
switch (eAudioType)
{
case PhxhSDK.EAudioType.Music:
return _playingMusicList;
case PhxhSDK.EAudioType.Sound:
return _playingSoundList;
case PhxhSDK.EAudioType.Speak:
return _playingSpeakList;
default:
return _playingSoundList;
}
}
private bool _CheckIsAudioLimited(PhxhSDK.EAudioType eAudioType)
{
var limit = _GetLimit(eAudioType);
var playingList = _GetPlayingList(eAudioType);
return playingList.Count >= limit;
}
private AudioPlayer _GetFreeAudio(PhxhSDK.EAudioType eAudioType)
{
foreach (var kv in _allAudioDic)
{
var audio = kv.Value;
if (audio.AudioType == eAudioType && audio.State == PlayState.Stopped)
{
return audio;
}
}
return null;
}
private AudioPlayer _CreateNewAudio(PhxhSDK.EAudioType eAudioType, bool isLoop)
{
var rootGo = new GameObject()
{
name = "Audio"
};
rootGo.transform.SetParent(AudioRoot.transform);
var audio = new AudioPlayer(eAudioType, rootGo);
// 设置 GameObject 名称
string typeName = eAudioType switch
{
PhxhSDK.EAudioType.Music => "Music",
PhxhSDK.EAudioType.Sound => "Sound",
PhxhSDK.EAudioType.Speak => "Speak",
_ => "Audio"
};
rootGo.name = $"{typeName}{audio.AudioId}";
if (isLoop)
{
rootGo.name += "Loop";
}
_allAudioDic.Add(audio.AudioId, audio);
return audio;
}
/// <summary>
/// 添加到播放列表
/// </summary>
public void Add2Playing(int id)
{
var audio = GetAudioByID(id);
if (audio == null)
return;
var list = _GetPlayingList(audio.AudioType);
if (!list.Contains(id))
{
list.Add(id);
}
}
/// <summary>
/// 从播放列表移除
/// </summary>
public void RemovePlaying(int id)
{
_playingMusicList.Remove(id);
_playingSoundList.Remove(id);
_playingSpeakList.Remove(id);
}
#endregion
#region Play API (Internal)
/// <summary>
/// 内部播放接口 - GameObject
/// </summary>
private int _PlayAudio(string key, PhxhSDK.EAudioType type, GameObject posObj = null, bool isLoop = false,
int priority = AudioConstant.DefaultPriority, bool isFade = false, float fadeTime = 0f)
{
var settings = AudioVolumeSettings.Instance;
switch (type)
{
case PhxhSDK.EAudioType.Sound:
if (settings.GlobalVolume <= 0 || settings.SoundVolume <= 0)
return -1;
break;
case PhxhSDK.EAudioType.Speak:
if (settings.GlobalVolume <= 0 || settings.SpeakVolume <= 0)
return -1;
break;
}
if (string.IsNullOrEmpty(key))
return -1;
switch (type)
{
case PhxhSDK.EAudioType.Music:
return _PlayMusic(key, posObj, isLoop, isFade, fadeTime);
case PhxhSDK.EAudioType.Sound:
return _PlaySound(key, posObj, isLoop, priority, isFade, fadeTime);
case PhxhSDK.EAudioType.Speak:
return _PlaySpeakSound(key, posObj, isFade, fadeTime);
default:
return -1;
}
}
/// <summary>
/// 内部播放接口 - Vector3
/// </summary>
private int _PlayAudio(string key, PhxhSDK.EAudioType type, Vector3 pos, bool loop = false,
int priority = AudioConstant.DefaultPriority, bool isFade = false, float fadeTime = 0f)
{
var settings = AudioVolumeSettings.Instance;
switch (type)
{
case PhxhSDK.EAudioType.Sound:
if (settings.GlobalVolume <= 0 || settings.SoundVolume <= 0)
return -1;
break;
case PhxhSDK.EAudioType.Speak:
if (settings.GlobalVolume <= 0 || settings.SpeakVolume <= 0)
return -1;
break;
}
switch (type)
{
case PhxhSDK.EAudioType.Music:
return _PlayMusic(key, pos, loop, isFade, fadeTime);
case PhxhSDK.EAudioType.Sound:
return _PlaySound(key, pos, loop, priority, isFade, fadeTime);
case PhxhSDK.EAudioType.Speak:
return _PlaySpeakSound(key, pos, isFade, fadeTime);
default:
return -1;
}
}
private int _PlayMusic(string key, GameObject posObj = null, bool isLoop = true, bool isFade = false, float fadeTime = 0f, float volume = 1f)
{
return _PlayMusicCore(key, isLoop, isFade, fadeTime, volume, posObj, null);
}
private int _PlayMusic(string key, Vector3 pos, bool isLoop, bool isFade = false, float fadeTime = 0f, float volume = 1f)
{
return _PlayMusicCore(key, isLoop, isFade, fadeTime, volume, null, pos);
}
private int _PlayMusicCore(string key, bool isLoop, bool isFade, float fadeTime, float volume,
GameObject followTarget, Vector3? staticPosition)
{
var path = GetFullPath(key);
var audio = _GetUsableAudio(PhxhSDK.EAudioType.Music, isLoop);
if (audio == null)
{
DebugUtil.LogError($"AudioManager, PlayMusic, Cant GetUsableAudio key:{key}");
return -1;
}
return ConfigureAndPlay(audio, path, key, isLoop, isFade, fadeTime, volume,
followTarget, staticPosition);
}
private int _PlaySound(string key, GameObject posObj = null, bool isLoop = false,
int priority = AudioConstant.DefaultPriority, bool isFade = false, float fadeTime = 0f, float volume = 1f)
{
return _PlaySoundCore(key, isLoop, priority, isFade, fadeTime, volume, posObj, null);
}
private int _PlaySound(string key, Vector3 pos, bool isLoop = false, int priority = AudioConstant.DefaultPriority,
bool isFade = false, float fadeTime = 0f, float volume = 1f)
{
return _PlaySoundCore(key, isLoop, priority, isFade, fadeTime, volume, null, pos);
}
private int _PlaySoundCore(string key, bool isLoop, int priority, bool isFade, float fadeTime, float volume,
GameObject followTarget, Vector3? staticPosition)
{
if (AudioVolumeSettings.Instance.SoundVolume < 0.01f)
{
return -1;
}
// 防重播检查低优先级音效100ms内防重
if (priority >= AudioConstant.LowPriority)
{
var curTimeStamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
if (_playSoundTimeStampDic.TryGetValue(key, out var timeStamp))
{
if (curTimeStamp - timeStamp < 100)
{
return -1;
}
_playSoundTimeStampDic[key] = curTimeStamp;
}
else
{
_playSoundTimeStampDic.TryAdd(key, curTimeStamp);
}
}
var audio = _GetUsableAudio(PhxhSDK.EAudioType.Sound, isLoop);
if (audio == null)
{
DebugUtil.LogError($"AudioManager, PlaySound, Cant GetUsableAudio key:{key}");
return -1;
}
var path = GetFullPath(key);
return ConfigureAndPlay(audio, path, key, isLoop, isFade, fadeTime, volume,
followTarget, staticPosition);
}
private int _PlaySpeakSound(string key, GameObject posObj = null, bool isFade = false, float fadeTime = 0f, float volume = 1f)
{
return _PlaySpeakSoundCore(key, isFade, fadeTime, volume, posObj, null);
}
private int _PlaySpeakSound(string key, Vector3 pos, bool isFade = false, float fadeTime = 0f, float volume = 1f)
{
return _PlaySpeakSoundCore(key, isFade, fadeTime, volume, null, pos);
}
private int _PlaySpeakSoundCore(string key, bool isFade, float fadeTime, float volume,
GameObject followTarget, Vector3? staticPosition)
{
if (AudioVolumeSettings.Instance.SpeakVolume < 0.01f)
{
return -1;
}
var audio = _GetUsableAudio(PhxhSDK.EAudioType.Speak, isLoop: false);
if (audio == null)
{
DebugUtil.LogError($"AudioManager, PlaySpeak, Cant GetUsableAudio key:{key}");
return -1;
}
var path = GetFullPath(key, isI18N: true);
return ConfigureAndPlay(audio, path, key, isLoop: false, isFade, fadeTime, volume,
followTarget, staticPosition);
}
/// <summary>
/// 统一的配置和播放核心方法
/// </summary>
private int ConfigureAndPlay(AudioPlayer audio, string path, string key, bool isLoop, bool isFade,
float fadeTime, float volume, GameObject followTarget, Vector3? staticPosition)
{
// 1. 设置音量缩放系数(来自配置表)
var volumeScale = _GetNormalizeVolume(key);
audio.SetVolumeScale(volumeScale);
// 2. 设置循环
audio.Loop = isLoop;
// 3. 设置位置3D音效
if (followTarget != null)
{
audio.SetFollowTarget(followTarget);
audio.SetIs3D(true);
}
else if (staticPosition.HasValue)
{
audio.SetPosition(staticPosition.Value);
audio.SetIs3D(true);
}
else
{
audio.SetIs3D(false);
}
// 4. 播放音频
if (isFade)
{
audio.Volume = 0;
audio.LoadAndPlayWithFade(path, fadeTime, fadeTime);
}
else
{
audio.Volume = volume * volumeScale;
audio.LoadAndPlay(path);
}
return audio.AudioId;
}
#endregion
#region Play API (Public)
/// <summary>
/// 播放音频 - GameObject位置
/// </summary>
public int PlayAudio(string key, PhxhSDK.EAudioType type = PhxhSDK.EAudioType.Sound,
GameObject posObj = null, bool loop = false, int priority = AudioConstant.DefaultPriority, bool isFade = false)
{
if (DisableAudio)
return -1;
if (!_CheckPath(key))
{
if (DebugUtil.LogEnable)
{
DebugUtil.LogG($"无法找到对应音频文件key:{key}");
}
return -1;
}
if (key == Constants.Sound.ERROR_UI || key == Constants.Sound.ERROR_UI_FIGHT)
{
EventManager.Instance.Send(EventManager.EventName.Audio_ErrorAudioPlay);
}
if (type == PhxhSDK.EAudioType.Music)
{
IsPlayingAlbum = false;
IsPlayingBGM = false;
}
return _PlayAudio(key, type, posObj, loop, priority, isFade);
}
/// <summary>
/// 播放音频 - Vector3位置
/// </summary>
public int PlayAudio(string key, Vector3 pos, PhxhSDK.EAudioType type = PhxhSDK.EAudioType.Sound, bool loop = false,
int priority = AudioConstant.DefaultPriority, bool isFade = false)
{
if (DisableAudio)
return -1;
if (!_CheckPath(key))
{
DebugUtil.LogG($"无法找到对应音频文件key:{key}");
return -1;
}
if (key == Constants.Sound.ERROR_UI || key == Constants.Sound.ERROR_UI_FIGHT)
{
EventManager.Instance.Send(EventManager.EventName.Audio_ErrorAudioPlay);
}
if (type == PhxhSDK.EAudioType.Music)
{
IsPlayingAlbum = false;
IsPlayingBGM = false;
}
return _PlayAudio(key, type, pos, loop, priority, isFade);
}
public void PlayByKey(int audioID, string key, bool isFade = false, float fadeOutTime = 4f)
{
if (_allAudioDic.TryGetValue(audioID, out var audio))
{
var fullPath = GetFullPath(key);
if (isFade)
{
audio.LoadAndPlayWithFade(fullPath, fadeOutTime);
}
else
{
audio.LoadAndPlay(fullPath);
}
}
}
public void PlayByKey(int audioID, string key, float fadeInTime, float targetVolume)
{
if (_allAudioDic.TryGetValue(audioID, out var audio))
{
var fullPath = GetFullPath(key);
audio.Volume = targetVolume;
audio.LoadAndPlayWithFade(fullPath, fadeInTime);
}
}
#endregion
#region Control API
public void InitLimit(int count0, int count1, int count2)
{
MusicPoolLimit = count0;
SoundPoolLimit = count1;
SpeakPoolLimit = count2;
}
/// <summary>
/// 根据音频ID获取AudioPlayer实例
/// 注意即使DisableAudio=true也会返回实例因为可能需要查询状态或停止播放
/// </summary>
public AudioPlayer GetAudioByID(int id)
{
return _allAudioDic.TryGetValue(id, out var audio) ? audio : null;
}
/// <summary>
/// 停止指定ID的音频
/// 注意即使DisableAudio=true也应该能停止音频
/// </summary>
public void StopByID(int audioID)
{
if (audioID >= 0 && _allAudioDic.TryGetValue(audioID, out var audio))
{
audio.Stop();
}
}
/// <summary>
/// 淡出停止指定ID的音频
/// 注意即使DisableAudio=true也应该能停止音频
/// </summary>
public void StopWithFadeByID(int id, float fadeOutTime = 1f)
{
if (id >= 0 && _allAudioDic.TryGetValue(id, out var audio))
{
audio.StopWithFadeOut(fadeOutTime);
}
}
public void PauseAllSound()
{
foreach (var kv in _allAudioDic)
{
if (kv.Value.AudioType == PhxhSDK.EAudioType.Sound)
{
kv.Value.Pause();
}
}
}
public void ResumeAllSound()
{
foreach (var kv in _allAudioDic)
{
if (kv.Value.AudioType == PhxhSDK.EAudioType.Sound)
{
kv.Value.Resume();
}
}
}
public void StopAllMusic()
{
IsPlayingAlbum = false;
IsPlayingBGM = false;
DebugUtil.LogG("StopAllMusic");
foreach (var kv in _allAudioDic)
{
if (kv.Value.AudioType == PhxhSDK.EAudioType.Music)
{
kv.Value.Stop();
}
}
}
public void StopAllSounds()
{
DebugUtil.LogG("StopAllSounds");
foreach (var kv in _allAudioDic)
{
if (kv.Value.AudioType == PhxhSDK.EAudioType.Sound)
{
kv.Value.Stop();
}
}
}
public void StopAllSpeaks()
{
DebugUtil.LogG("StopAllSpeaks");
foreach (var kv in _allAudioDic)
{
if (kv.Value.AudioType == PhxhSDK.EAudioType.Speak)
{
kv.Value.Stop();
}
}
}
public void StopAll()
{
IsPlayingAlbum = false;
IsPlayingBGM = false;
DebugUtil.LogG("StopAll");
foreach (var kv in _allAudioDic)
{
var audio = kv.Value;
audio.Stop();
}
}
public void ReleaseAllAudios()
{
foreach (var kv in _allAudioDic)
{
kv.Value.Dispose();
}
_allAudioDic.Clear();
_playingMusicList.Clear();
_playingSoundList.Clear();
_playingSpeakList.Clear();
AudioPlayer.ReleaseLoadedAssets();
}
#endregion
#region Special API
/// <summary>
/// 获取音频频谱数据(处理并归一化)
/// </summary>
public void GetSpectrumData(int id, float[] spectrumsShow, float[] spectrumsData)
{
AudioPlayer audioInst = GetAudioByID(id);
if (null == audioInst)
{
for (int i = 0; i < spectrumsShow.Length; ++i)
{
spectrumsShow[i] = 0f;
}
return;
}
audioInst.GetSpectrumData(spectrumsData);
int n = spectrumsData.Length / spectrumsShow.Length;
for (int i = 0; i < spectrumsShow.Length; ++i)
{
float value = 0f;
for (int j = 0; j < n; ++j)
{
value += spectrumsData[i * n + j];
}
value = Mathf.Log10(Mathf.Log(i + 2f, 2f) * value * (i * i + 1) * 9 + 1);
spectrumsShow[i] = value;
}
}
#endregion
#region Listener Management
private void _SetListenerPos()
{
if (AudioRoot && _isFollowCamera)
{
var camera = CameraManager.Instance.MainCamera;
if (camera)
{
AudioRoot.transform.position = camera.transform.position;
}
}
}
private void _InitListenerPos()
{
AudioRoot.transform.position = new Vector3(0, 0, 0);
}
public void EnterFollowState()
{
_isFollowCamera = true;
}
public void ExitFollowState()
{
_isFollowCamera = false;
_InitListenerPos();
}
public void SetGlobalListenerFollow(bool isActive)
{
if(isActive)
EnterFollowState();
else
ExitFollowState();
}
public void AfterSwitchMainCamera(Camera camera)
{
var listener = camera.GetComponent<AudioListener>();
if (listener)
{
Object.DestroyImmediate(listener);
}
}
#endregion
#region Events
private void _OnErrorAudioPlay()
{
foreach (var kv in _allAudioDic)
{
var sound = kv.Value;
if ((sound.AudioType == PhxhSDK.EAudioType.Sound || sound.AudioType == PhxhSDK.EAudioType.Speak) && sound.IsPlaying)
{
if (sound.Clip.name == Constants.Sound.COMMON_CLAIM || sound.Clip.name == Constants.Sound.COMMON_CLICK)
{
sound.Stop();
}
}
}
}
#endregion
}