using System; using System.Collections.Generic; using PhxhSDK; using UnityEngine; using UnityEngine.Networking; public class AudioPlayer: AudioBase { // 静态字段已迁移到 AudioVolumeSettings 类 // 为了向后兼容,保留静态属性(委托到 AudioVolumeSettings) public static float GlobalVolume { get => AudioVolumeSettings.Instance.GlobalVolume; set => AudioVolumeSettings.Instance.GlobalVolume = value; } public static float GlobalMusicVolume { get => AudioVolumeSettings.Instance.MusicVolume; set => AudioVolumeSettings.Instance.MusicVolume = value; } public static float GlobalSoundVolume { get => AudioVolumeSettings.Instance.SoundVolume; set => AudioVolumeSettings.Instance.SoundVolume = value; } public static float GlobalSpeakVolume { get => AudioVolumeSettings.Instance.SpeakVolume; set => AudioVolumeSettings.Instance.SpeakVolume = value; } private float normalizeVolume = 1f; protected AudioSource _audioSource; private bool _isSpeak; private static long _curPlayID; private long _playID = -1; //播放ID,越小越早 private AudioClip _clip; private long _playTime; //最近一次播放的时间戳 public static Dictionary LoadedPath = new(); public long playTime => _playTime; public AudioClip Clip => _clip; public long PlayID => _playID; public bool IsSpeak => _isSpeak; public float Volume => _audioSource.volume; // 淡入淡出效果(使用独立的效果类) private AudioFadeEffect _fadeEffect; // 空间音效控制器(使用独立的控制器类) private AudioSpatialController _spatialController; private bool _isPlayEnd { get { if (!_audioSource || !_clip) { return false; } if (_audioSource.isPlaying) return _audioSource.time > _clip.length; else return true; } } #region Construct public AudioPlayer(AudioType audioType, GameObject rootGo, GameObject posGo, AudioInfo audioInfo, bool loop = false, bool is3D = false) { this.audioType = audioType; _rootGo = rootGo; _audioSource = _GetSource(); _audioSource.volume = AudioVolumeSettings.Instance.GlobalVolume; targetVolume = 1f; _isSpeak = audioInfo.isSpeak; // 初始化淡入淡出效果 _fadeEffect = new AudioFadeEffect(fadeInDuration: 4f, fadeOutDuration: 4f); // 初始化空间音效控制器 _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 Inherit public override void Update(float dt) { if (!_audioSource || !_clip) { return; } // 更新淡入淡出效果 bool shouldStop = _fadeEffect.Update(dt); if (playState == PlayState.Playing || _audioSource.isPlaying) { switch (audioType) { case (AudioType.Music): { _SetMusicVolume(); break; } case (AudioType.Sound): { if (_isSpeak) { _SetSpeakVolume(); } else { _SetSoundVolume(); } break; } default: break; } } if (playState == PlayState.Paused) { return; } // 更新空间位置跟随 if (playState == PlayState.Playing) { _spatialController.Update(); } // 更新播放状态 // 检查是否应该停止(播放结束或淡出完成) if (_isPlayEnd || shouldStop || playState == PlayState.Stopped) { if (playState == PlayState.Loading) return; Stop(); } } public override void Pause() { _audioSource.Pause(); playState = PlayState.Paused; } public override void Resume() { _audioSource.UnPause(); playState = PlayState.Playing; } public override void Play(float volume = 1f) { playState = PlayState.Playing; _fadeEffect.Stop(); // 停止任何淡入淡出效果 targetVolume = 1f; _audioSource.Play(); _playID = _GenPlayID(); _playTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); AudioManager.Instance.AudioMgrObj.Add2Playing(audioType, audioID, _isSpeak); } public void PlayWithFade(float volume = 1f) { playState = PlayState.Playing; _audioSource.Play(); _playID = _GenPlayID(); AudioManager.Instance.AudioMgrObj.RemovePlaying(audioType, audioID); } public new void Stop() { _fadeEffect.Stop(); // 停止淡入淡出效果 AudioManager.Instance.AudioMgrObj.RemovePlaying(audioType, audioID); if (_isPlayEnd) { playState = PlayState.Stopped; return; } if (_audioSource) { _audioSource.Stop(); } base.Stop(); } public void StopWithFade(float fadeOutTime = 1f) { _fadeEffect.StartFadeOut(fadeOutTime); } #endregion #region Utils private long _GenPlayID() { return ++_curPlayID; } public static void ResetPlayID() { _curPlayID = 0; } public AudioSource GetAudioSource() { return _audioSource; } private void _SetMusicVolume() { var settings = AudioVolumeSettings.Instance; var fadeValue = _fadeEffect.FadeValue; _audioSource.volume = fadeValue * settings.MusicVolume * settings.GlobalVolume * targetVolume * normalizeVolume; } private void _SetSoundVolume() { var settings = AudioVolumeSettings.Instance; var fadeValue = _fadeEffect.FadeValue; _audioSource.volume = fadeValue * settings.SoundVolume * settings.GlobalVolume * targetVolume * normalizeVolume; } private void _SetSpeakVolume() { var settings = AudioVolumeSettings.Instance; var fadeValue = _fadeEffect.FadeValue; _audioSource.volume = fadeValue * settings.SpeakVolume * settings.GlobalVolume * targetVolume * normalizeVolume; } #endregion #region API public void Play(string cueName, AudioInfo audioInfo, bool isLoop = false) { targetVolume = 1f; _fadeEffect.Stop(); // 停止任何淡入淡出效果 playState = PlayState.Playing; _playID = _GenPlayID(); AudioManager.Instance.AudioMgrObj.Add2Playing(audioType, audioID, _isSpeak); } public void Play(string path) { playState = PlayState.Loading; PlayAsync(path); } private async void PlayAsync(string path) { _clip = await AssetManager.Instance.LoadAssetAsync(path, null, true); if (LoadedPath.ContainsKey(path)) { ++LoadedPath[path]; } else { LoadedPath.TryAdd(path, 1); } if (!_clip) { playState = PlayState.Stopped; return; } _audioSource.clip = _clip; _fadeEffect.Stop(); // 停止任何淡入淡出效果 targetVolume = 1f; _playID = _GenPlayID(); AudioManager.Instance.AudioMgrObj.Add2Playing(audioType, audioID, _isSpeak); _audioSource.Play(); playState = PlayState.Playing; } public async void PlayWithFade(string path, float fadeInTime = 4f, float fadeOutTime = 4f) { _clip = await AssetManager.Instance.LoadAssetAsync(path, null, true); if (LoadedPath.ContainsKey(path)) { ++LoadedPath[path]; } else { LoadedPath.TryAdd(path, 1); } if (!_clip) { return; } _audioSource.clip = _clip; // 设置淡入淡出时长并启动淡入 _fadeEffect.FadeInDuration = fadeInTime; _fadeEffect.FadeOutDuration = fadeOutTime; _fadeEffect.StartFadeIn(); targetVolume = 1f; _audioSource.volume = 0f; playState = PlayState.Playing; _playID = _GenPlayID(); AudioManager.Instance.AudioMgrObj.Add2Playing(audioType, audioID, _isSpeak); _audioSource.Play(); } /// /// 设置是否启用 3D 空间音效 /// public void SetIs3D(bool is3D = false) { if (is3D) { _spatialController.Enable3D(); } else { _spatialController.Disable3D(); } } /// /// 设置跟随对象(自动启用 3D) /// public void SetPosObj(GameObject go = null) { _spatialController.SetFollowTarget(go); } /// /// 设置静态位置 /// public void SetPos(Vector3 position) { _spatialController.SetStaticPosition(position, enable3D: true); } public void SetIsLoop(bool isLoop) { loop = isLoop; _audioSource.loop = isLoop; } public void SetIsSpeak(bool isSpeak) { _isSpeak = isSpeak; } public void SetNormalizeVolume(float vol = 1f) { normalizeVolume = vol; } /// /// 启动淡入效果(已弃用,建议直接使用 PlayWithFade) /// [System.Obsolete("Use PlayWithFade instead")] public void SetIsFadeIn(bool isFadeIn) { if (isFadeIn) { _fadeEffect.StartFadeIn(); } else { _fadeEffect.Stop(); } } public string GetCueSheetName() { if (_audioSource) { return _clip.name; } return ""; } public float GetCurPlayLength() { if (_audioSource) { return _audioSource.time; } return -1; } public float GetTime() { if (!_audioSource) return -1; return _audioSource.time; } public static void ReleaseLoadedAssets() { foreach (var kv in LoadedPath) { var key = kv.Key; var count = kv.Value; while (count > 0) { AssetManager.Instance.Unload(key, false, true); --count; } } } /// /// 获取音频频谱数据(用于可视化) /// public void GetSpectrumData(float[] spectrums) { if (_audioSource != null) { _audioSource.GetSpectrumData(spectrums, 0, FFTWindow.Blackman); } } #endregion } public class AudioConstant { /// /// 最大音乐播放数 /// public const int MaxMusicCount = 1; /// /// 最大音频播放数 /// public const int MaxSoundCount = 100; /// /// 最大语音播放数 /// public const int MaxSpeakCount = 1; /// /// 优先级 /// public const int MaxPriority = 99; public const int MinPriority = 0; public const int DefaultPriority = 29; public const int LowPriority = 30; }