using System; using System.Collections.Generic; using PhxhSDK; using UnityEngine; using PlayState = PhxhSDK.PlayState; /// /// 音频播放器 /// 功能:异步加载、播放控制、3D空间音效、淡入淡出效果 /// 实现接口:IAudioPlayer, IAudioSpatial, IAudioFade, IDisposable /// public class AudioPlayer : IAudioPlayer, IAudioSpatial, IAudioFade, IDisposable { #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; /// /// 当前播放的音频配置 key(用于停止池复用) /// 由 AudioManager 在播放前设置 /// public string CurrentPlayingKey { get; set; } // 音量控制 private float _targetVolume = 1f; private float _normalizeVolume = 1f; // 效果控制器 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; /// /// 音量(0-1),返回目标音量(非实际音量) /// public float Volume { get => _targetVolume; set => _targetVolume = Mathf.Clamp01(value); } /// /// 音频类型(Music/Sound/Speak),只读,在构造时确定 /// public EAudioType AudioType => _eAudioType; /// /// 是否循环播放 /// public bool Loop { get => _loop; set { _loop = value; if (_audioSource != null) _audioSource.loop = value; } } // 内部属性(仅供 Manager 层访问) internal GameObject RootGo => _rootGo; #endregion #region Constructor & Initialization /// /// 构造音频播放器 /// /// 音频类型 /// 挂载的 GameObject /// 可选的跟随目标(自动启用3D音效) public AudioPlayer(EAudioType eAudioType, GameObject rootGo, GameObject followTarget = null) { _audioID = GenAudioID(); _eAudioType = eAudioType; _rootGo = rootGo; _audioSource = GetOrAddAudioSource(rootGo); _audioSource.volume = AudioVolumeSettings.Instance.GlobalVolume; // 初始化效果控制器 _fadeEffect = new AudioFadeEffect( fadeInDuration: AudioConstant.DefaultFadeInDuration, fadeOutDuration: AudioConstant.DefaultFadeOutDuration); _spatialController = new AudioSpatialController(_rootGo.transform, _audioSource); // 如果指定了跟随对象,设置跟随 if (followTarget != null) { _spatialController.SetFollowTarget(followTarget); } } 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(); } } /// /// 判断播放是否已结束 /// 只有非循环音频在播放完成后才返回 true /// private bool IsPlaybackEnded() { if (_audioSource == null || _clip == null) return false; // 循环音频永远不会结束 if (_loop) return false; // 只有同时满足以下条件才算结束: // 1. 不在播放状态(!isPlaying) // 2. 播放时间已到末尾(time >= length) // 3. 状态不是 Paused(暂停不算结束) // 4. 状态不是 Loading(加载中不算结束) return !_audioSource.isPlaying && _audioSource.time >= _clip.length && _playState != PlayState.Paused && _playState != PlayState.Loading; } #endregion #region Playback Control /// /// 播放音频(需要先设置 Clip) /// public void Play() { if (_clip == null) { DebugUtil.LogError("AudioPlayer: Cannot play without clip"); return; } if (_audioSource == null) { DebugUtil.LogError("AudioPlayer: Cannot play: AudioSource is null"); 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(); // 停止 AudioSource(重复调用是安全的) if (_audioSource != null) { _audioSource.Stop(); } // 设置状态为停止 _playState = PlayState.Stopped; } /// /// 释放资源(销毁 GameObject) /// public void Dispose() { Stop(); if (_rootGo != null) { UnityEngine.Object.Destroy(_rootGo); } } /// /// 设置 GameObject 激活状态 /// 用于对象池优化:停止池中的音频 disable,复用时 enable /// /// 是否激活 public void SetActive(bool active) { if (_rootGo != null) { _rootGo.SetActive(active); } } #endregion #region Async Loading & Play /// /// 异步加载并播放音频(不带淡入淡出) /// /// 音频资源路径 public void LoadAndPlay(string path) { _playState = PlayState.Loading; LoadAndPlayInternal(path, useFade: false); } /// /// 异步加载并播放音频(带淡入淡出效果) /// /// 音频资源路径 /// 淡入时长(秒) /// 淡出时长(秒) public void LoadAndPlayWithFade(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. 判空检查(异步加载期间可能 GameObject 被销毁) if (_audioSource == null) { _playState = PlayState.Stopped; return; } // 5. 设置 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); } /// /// 通知外部播放已开始(触发事件) /// private void NotifyPlayStarted() { // 空方法,保留接口以避免破坏现有调用 } #endregion #region Volume & Configuration /// /// 设置音量缩放系数(来自配置表的音量调整值) /// /// 音量缩放系数,默认1.0,必须为非负数 public void SetVolumeScale(float scale = 1f) { _normalizeVolume = Mathf.Max(0f, scale); } /// /// 设置音频片段(仅供 Manager 层使用) /// internal 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 = this.AudioType switch { EAudioType.Music => settings.MusicVolume, EAudioType.Sound => settings.SoundVolume, EAudioType.Speak => settings.SpeakVolume, _ => 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 /// /// 获取当前播放时间(秒) /// public float GetPlaybackTime() => _audioSource ? _audioSource.time : -1f; /// /// 获取音频频谱数据(用于可视化) /// public void GetSpectrumData(float[] spectrums) { if (_audioSource != null) { _audioSource.GetSpectrumData(spectrums, 0, FFTWindow.Blackman); } } #endregion }