using System; using System.Collections.Generic; using PhxhSDK; using UnityEngine; using PlayState = PhxhSDK.PlayState; using AudioType = PhxhSDK.AudioType; /// /// 音频播放器 /// 实现 IAudioPlayer(音频播放核心接口)、IAudioSpatial(3D音效)、IAudioFade(淡入淡出)接口 /// public class AudioPlayer : PhxhSDK.IAudioPlayer, PhxhSDK.IAudioSpatial, PhxhSDK.IAudioFade { #region Static Fields private static int _audioIDCount; private static long _curPlayID; private static Dictionary _loadedAssetPaths = new(); #endregion #region Core Fields // Unity 组件 private GameObject _rootGo; private AudioSource _audioSource; private AudioClip _clip; // 状态字段 private PlayState _playState; private int _audioID; private AudioType _audioType; private bool _loop; // 播放追踪 private long _playID = -1; private long _playTime; #endregion #region Volume Fields private float _targetVolume; private float _normalizeVolume = 1f; private bool _isSpeak; #endregion #region Effect Controllers private AudioFadeEffect _fadeEffect; private AudioSpatialController _spatialController; #endregion #region IAudioPlayer Interface Properties public int AudioId => _audioID; public PlayState State => _playState; public bool IsPlaying => _playState == PlayState.Playing; public AudioClip Clip => _clip; public float Volume => _audioSource?.volume ?? 0f; #endregion #region Public Properties public int audioID { get => _audioID; protected set => _audioID = value; } public AudioType audioType { get => _audioType; set => _audioType = value; } public bool loop { get => _loop; set => _loop = value; } public GameObject RootGo => _rootGo; public long PlayID => _playID; public long PlayTime => _playTime; public bool IsSpeak => _isSpeak; #endregion /// /// 判断播放是否已结束 /// private bool _isPlayEnd() { if (_audioSource == null || _clip == null) return false; return _audioSource.isPlaying ? _audioSource.time > _clip.length : true; } #region Static Methods /// /// 生成唯一的 AudioID /// public static int GenAudioID() { return ++_audioIDCount; } /// /// 重置 AudioID 计数器 /// public static void ReleaseAudioIDs() { _audioIDCount = 0; } /// /// 重置 PlayID 计数器 /// public static void ResetPlayID() { _curPlayID = 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 Constructor public AudioPlayer(AudioType audioType, GameObject rootGo, GameObject posGo, AudioInfo audioInfo, bool loop = false, bool is3D = false) { // 初始化 audioID(从 AudioBase 构造函数) audioID = GenAudioID(); this.audioType = audioType; _rootGo = rootGo; _audioSource = _getSource(); _audioSource.volume = AudioVolumeSettings.Instance.GlobalVolume; _targetVolume = 1f; _isSpeak = audioInfo.isSpeak; // 初始化淡入淡出效果 _fadeEffect = new AudioFadeEffect( fadeInDuration: AudioConstant.DefaultFadeInDuration, fadeOutDuration: AudioConstant.DefaultFadeOutDuration); // 初始化空间音效控制器 _spatialController = new AudioSpatialController(_rootGo.transform, _audioSource); // 如果构造时指定了跟随对象,设置跟随 if (posGo != null) { _spatialController.SetFollowTarget(posGo); } } private AudioSource _getSource() { var source = _rootGo.GetComponent(); if (!source) { source = _rootGo.AddComponent(); } return source; } #endregion #region Lifecycle /// /// 生成新的 AudioID(重新初始化时使用) /// public void GenNewID() { audioID = GenAudioID(); } #endregion #region Core Playback Methods public void Update(float dt) { if (!_audioSource || !_clip) { return; } // 更新淡入淡出效果 bool shouldStop = _fadeEffect.Update(dt); if (_playState == PlayState.Playing || _audioSource.isPlaying) { _setVolume(); } if (_playState == PlayState.Paused) { return; } // 更新空间位置跟随 if (_playState == PlayState.Playing) { _spatialController.Update(); } // 更新播放状态 // 检查是否应该停止(播放结束或淡出完成) if (_isPlayEnd() || shouldStop || _playState == PlayState.Stopped) { if (_playState == PlayState.Loading) return; Stop(); } } public void Pause() { _audioSource.Pause(); _playState = PlayState.Paused; } public void Resume() { _audioSource.UnPause(); _playState = PlayState.Playing; } public void Play() { _playState = PlayState.Playing; _fadeEffect.Stop(); // 停止任何淡入淡出效果 _targetVolume = 1f; _audioSource.Play(); _updatePlayInfo(); // 使用统一的更新方法 } public void Stop() { _fadeEffect.Stop(); // 停止淡入淡出效果 AudioManager.Instance.AudioMgrObj.RemovePlaying(audioType, audioID); if (_isPlayEnd()) { _playState = PlayState.Stopped; return; } if (_audioSource) { _audioSource.Stop(); } _playState = PlayState.Stopped; } #endregion #region Private Helper Methods /// /// 生成唯一的播放ID /// private long _genPlayID() => ++_curPlayID; /// /// 获取 AudioSource 组件 /// public AudioSource GetAudioSource() => _audioSource; /// /// 统一的音量设置方法,根据音频类型和是否为语音自动选择对应的音量系数 /// private void _setVolume() { var settings = AudioVolumeSettings.Instance; var fadeValue = _fadeEffect.FadeValue; // 根据音频类型和是否为语音选择对应的音量系数 float typeVolume = audioType switch { AudioType.Music => settings.MusicVolume, AudioType.Sound => _isSpeak ? settings.SpeakVolume : settings.SoundVolume, _ => 1f }; _audioSource.volume = fadeValue * typeVolume * settings.GlobalVolume * _targetVolume * _normalizeVolume; } #endregion #region Public API Methods /// /// 异步加载并播放音频(不带淡入淡出) /// 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) { // 淡入模式:从0音量开始,逐渐增加 _fadeEffect.FadeInDuration = fadeInTime; _fadeEffect.FadeOutDuration = fadeOutTime; _fadeEffect.StartFadeIn(); _audioSource.volume = 0f; } else { // 正常模式:停止淡入淡出效果 _fadeEffect.Stop(); } // 6. 开始播放 _audioSource.Play(); _playState = PlayState.Playing; // 7. 更新播放信息并通知Manager _updatePlayInfo(); } /// /// 跟踪已加载的资源(用于引用计数) /// private void _trackLoadedAsset(string path) { if (_loadedAssetPaths.ContainsKey(path)) ++_loadedAssetPaths[path]; else _loadedAssetPaths.TryAdd(path, 1); } /// /// 更新播放信息(生成PlayID、时间戳、通知Manager) /// private void _updatePlayInfo() { _playID = _genPlayID(); _playTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); AudioManager.Instance.AudioMgrObj.Add2Playing(audioType, audioID, _isSpeak); } /// /// 设置是否启用 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); } /// /// 设置音量(IAudioPlayer 接口实现) /// public void SetVolume(float volume) { _targetVolume = Mathf.Clamp01(volume); } /// /// 设置循环播放,同步更新 AudioSource /// public void SetLoop(bool isLoop) { loop = isLoop; if (_audioSource != null) _audioSource.loop = isLoop; } /// /// 设置是否为语音 /// public void SetIsSpeak(bool isSpeak) => _isSpeak = isSpeak; /// /// 设置归一化音量 /// public void SetNormalizeVolume(float vol = 1f) => _normalizeVolume = vol; /// /// 获取音频名称 /// public string GetCueSheetName() => _audioSource && _clip ? _clip.name : ""; /// /// 获取当前播放时间(秒)(IAudioPlayer 接口实现) /// public float GetPlaybackTime() => _audioSource ? _audioSource.time : -1f; /// /// 获取音频频谱数据(用于可视化) /// public void GetSpectrumData(float[] spectrums) { if (_audioSource != null) { _audioSource.GetSpectrumData(spectrums, 0, FFTWindow.Blackman); } } #endregion #region Interface Implementations // ======================================== // IAudioPlayer 接口完整实现 // AudioId, State, IsPlaying, Clip, Volume, Play/Pause/Resume/Stop, SetClip, SetVolume, SetLoop, GetPlaybackTime // ======================================== /// /// 设置音频片段(IAudioPlayer 接口实现) /// public void SetClip(AudioClip clip) { _clip = clip; if (_audioSource != null) _audioSource.clip = clip; } // 注:IAudioSpatial 的 SetPosition 和 SetFollowTarget 已在 API 区域实现 // ======================================== // IAudioFade 接口实现 // ======================================== /// /// 淡入播放(会自动调用Play) /// /// 淡入时长(秒) 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(); _updatePlayInfo(); // 使用统一的更新方法 } /// /// 淡出停止(淡出完成后自动调用Stop) /// /// 淡出时长(秒) public void StopWithFadeOut(float duration = AudioConstant.DefaultFadeOutDuration) { _fadeEffect.StartFadeOut(duration); } #endregion }