using System; using System.Collections.Generic; using PhxhSDK; using UnityEngine; using PlayState = PhxhSDK.PlayState; /// /// 音频播放器 /// 功能:异步加载、播放控制、3D空间音效、淡入淡出效果 /// 实现接口:IAudioPlayer, IAudioSpatial, IAudioFade /// public class AudioPlayer : IAudioPlayer, IAudioSpatial, IAudioFade { #region Static - ID Generation & Resource Tracking private static int _audioIDCounter; private static Dictionary _loadedAssetPaths = new(); /// /// 生成唯一的 AudioID /// public static int GenAudioID() => ++_audioIDCounter; /// /// 重置 AudioID 计数器 /// public static void ReleaseAudioIDs() => _audioIDCounter = 0; /// /// 释放所有已加载的资源 /// public static void ReleaseLoadedAssets() { foreach (var kv in _loadedAssetPaths) { var key = kv.Key; var count = kv.Value; while (count > 0) { AssetManager.Instance.Unload(key, false, true); --count; } } _loadedAssetPaths.Clear(); } #endregion #region Fields // Unity 组件 private readonly GameObject _rootGo; private readonly AudioSource _audioSource; private AudioClip _clip; // 状态 private PlayState _playState; private readonly int _audioID; private EAudioType _eAudioType; private bool _loop; // 音量控制 private float _targetVolume = 1f; private float _normalizeVolume = 1f; private bool _isSpeak; // 效果控制器 private readonly AudioFadeEffect _fadeEffect; private readonly AudioSpatialController _spatialController; #endregion #region Properties // IAudioPlayer 接口属性 public int AudioId => _audioID; public PlayState State => _playState; public bool IsPlaying => _playState == PlayState.Playing; public AudioClip Clip => _clip; public float Volume => _audioSource?.volume ?? 0f; // 公开属性 public EAudioType EAudioType { get => _eAudioType; set => _eAudioType = value; } public bool loop { get => _loop; set => _loop = value; } // 其他公开属性 public GameObject RootGo => _rootGo; public bool IsSpeak => _isSpeak; #endregion #region Constructor & Initialization public AudioPlayer(EAudioType eAudioType, GameObject rootGo, GameObject posGo, AudioInfo audioInfo, bool loop = false, bool is3D = false) { _audioID = GenAudioID(); _eAudioType = eAudioType; _rootGo = rootGo; _audioSource = GetOrAddAudioSource(rootGo); _audioSource.volume = AudioVolumeSettings.Instance.GlobalVolume; _isSpeak = audioInfo.isSpeak; _loop = loop; // 初始化效果控制器 _fadeEffect = new AudioFadeEffect( fadeInDuration: AudioConstant.DefaultFadeInDuration, fadeOutDuration: AudioConstant.DefaultFadeOutDuration); _spatialController = new AudioSpatialController(_rootGo.transform, _audioSource); // 如果构造时指定了跟随对象,设置跟随 if (posGo != null) { _spatialController.SetFollowTarget(posGo); } } private AudioSource GetOrAddAudioSource(GameObject go) { var source = go.GetComponent(); if (!source) { source = go.AddComponent(); } return source; } #endregion #region Lifecycle & Update public void Update(float dt) { if (_audioSource == null || _clip == null) return; // 更新淡入淡出效果 bool shouldStop = _fadeEffect.Update(dt); // 更新音量 if (_playState == PlayState.Playing || _audioSource.isPlaying) { UpdateVolume(); } // 暂停状态不更新 if (_playState == PlayState.Paused) return; // 更新空间位置跟随 if (_playState == PlayState.Playing) { _spatialController.Update(); } // 检查是否应该停止(播放结束或淡出完成) if (IsPlaybackEnded() || shouldStop || _playState == PlayState.Stopped) { if (_playState == PlayState.Loading) return; Stop(); } } /// /// 判断播放是否已结束 /// private bool IsPlaybackEnded() { if (_audioSource == null || _clip == null) return false; return _audioSource.isPlaying ? _audioSource.time > _clip.length : true; } #endregion #region Playback Control /// /// 播放音频(需要先设置 Clip) /// public void Play() { if (_clip == null) { DebugUtil.LogError("AudioPlayer: Cannot play without clip"); return; } _playState = PlayState.Playing; _fadeEffect.Stop(); _targetVolume = 1f; _audioSource.Play(); NotifyPlayStarted(); } /// /// 暂停播放 /// public void Pause() { if (_audioSource != null) _audioSource.Pause(); _playState = PlayState.Paused; } /// /// 恢复播放 /// public void Resume() { if (_audioSource != null) _audioSource.UnPause(); _playState = PlayState.Playing; } /// /// 停止播放 /// public void Stop() { _fadeEffect.Stop(); AudioManager.Instance.AudioMgrObj.RemovePlaying(_audioID); if (IsPlaybackEnded()) { _playState = PlayState.Stopped; return; } if (_audioSource != null) { _audioSource.Stop(); } _playState = PlayState.Stopped; } #endregion #region Async Loading & Play /// /// 异步加载并播放音频(不带淡入淡出) /// public void Play(string path) { _playState = PlayState.Loading; LoadAndPlayInternal(path, useFade: false); } /// /// 异步加载并播放音频(带淡入淡出) /// public void PlayWithFade(string path, float fadeInTime = AudioConstant.DefaultFadeInDuration, float fadeOutTime = AudioConstant.DefaultFadeOutDuration) { _playState = PlayState.Loading; LoadAndPlayInternal(path, useFade: true, fadeInTime, fadeOutTime); } /// /// 统一的异步加载和播放逻辑 /// private async void LoadAndPlayInternal(string path, bool useFade, float fadeInTime = AudioConstant.DefaultFadeInDuration, float fadeOutTime = AudioConstant.DefaultFadeOutDuration) { // 1. 异步加载音频资源 _clip = await AssetManager.Instance.LoadAssetAsync(path, null, true); // 2. 跟踪已加载的资源(用于后续卸载) TrackLoadedAsset(path); // 3. 加载失败处理 if (_clip == null) { _playState = PlayState.Stopped; return; } // 4. 设置 AudioSource _audioSource.clip = _clip; _targetVolume = 1f; // 5. 根据是否需要淡入淡出设置音量 if (useFade) { _fadeEffect.FadeInDuration = fadeInTime; _fadeEffect.FadeOutDuration = fadeOutTime; _fadeEffect.StartFadeIn(); _audioSource.volume = 0f; } else { _fadeEffect.Stop(); } // 6. 开始播放 _audioSource.Play(); _playState = PlayState.Playing; // 7. 通知 Manager NotifyPlayStarted(); } /// /// 跟踪已加载的资源(用于引用计数) /// private void TrackLoadedAsset(string path) { if (_loadedAssetPaths.ContainsKey(path)) ++_loadedAssetPaths[path]; else _loadedAssetPaths.TryAdd(path, 1); } /// /// 通知 Manager 播放已开始 /// private void NotifyPlayStarted() { AudioManager.Instance.AudioMgrObj.Add2Playing(_audioID); } #endregion #region Volume & Configuration /// /// 设置音量(0-1) /// public void SetVolume(float volume) { _targetVolume = Mathf.Clamp01(volume); } /// /// 设置归一化音量(配置表中的音量缩放) /// public void SetNormalizeVolume(float vol = 1f) { _normalizeVolume = vol; } /// /// 设置是否为语音 /// public void SetIsSpeak(bool isSpeak) { _isSpeak = isSpeak; } /// /// 设置循环播放 /// public void SetLoop(bool isLoop) { _loop = isLoop; if (_audioSource != null) _audioSource.loop = isLoop; } /// /// 设置音频片段 /// public void SetClip(AudioClip clip) { _clip = clip; if (_audioSource != null) _audioSource.clip = clip; } /// /// 统一的音量更新方法 /// 最终音量 = 淡入淡出系数 × 类型音量 × 全局音量 × 目标音量 × 归一化音量 /// private void UpdateVolume() { var settings = AudioVolumeSettings.Instance; var fadeValue = _fadeEffect.FadeValue; // 根据音频类型和是否为语音选择对应的音量系数 float typeVolume = _eAudioType switch { EAudioType.Music => settings.MusicVolume, EAudioType.Sound => _isSpeak ? settings.SpeakVolume : settings.SoundVolume, _ => 1f }; _audioSource.volume = fadeValue * typeVolume * settings.GlobalVolume * _targetVolume * _normalizeVolume; } #endregion #region Spatial Audio (3D) /// /// 设置是否启用 3D 空间音效 /// public void SetIs3D(bool is3D = false) { if (is3D) _spatialController.Enable3D(); else _spatialController.Disable3D(); } /// /// 设置跟随对象(自动启用 3D) /// public void SetFollowTarget(GameObject target = null) { _spatialController.SetFollowTarget(target); } /// /// 设置静态位置(自动启用 3D) /// public void SetPosition(Vector3 position) { _spatialController.SetStaticPosition(position, enable3D: true); } #endregion #region Fade Effects /// /// 淡入播放(需要先设置 Clip) /// public void PlayWithFadeIn(float duration = AudioConstant.DefaultFadeInDuration) { if (_clip == null) { DebugUtil.LogError("AudioPlayer: Cannot PlayWithFadeIn without clip"); return; } _fadeEffect.FadeInDuration = duration; _fadeEffect.StartFadeIn(); _playState = PlayState.Playing; _audioSource.volume = 0f; _audioSource.Play(); NotifyPlayStarted(); } /// /// 淡出停止(淡出完成后自动调用 Stop) /// public void StopWithFadeOut(float duration = AudioConstant.DefaultFadeOutDuration) { _fadeEffect.StartFadeOut(duration); } #endregion #region Query & Debug /// /// 获取 AudioSource 组件 /// public AudioSource GetAudioSource() => _audioSource; /// /// 获取当前播放时间(秒) /// public float GetPlaybackTime() => _audioSource ? _audioSource.time : -1f; /// /// 获取音频频谱数据(用于可视化) /// public void GetSpectrumData(float[] spectrums) { if (_audioSource != null) { _audioSource.GetSpectrumData(spectrums, 0, FFTWindow.Blackman); } } #endregion }