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

498 lines
12 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 PhxhSDK;
using UnityEngine;
using PlayState = PhxhSDK.PlayState;
/// <summary>
/// 音频播放器
/// 功能异步加载、播放控制、3D空间音效、淡入淡出效果
/// 实现接口IAudioPlayer, IAudioSpatial, IAudioFade
/// </summary>
public class AudioPlayer : IAudioPlayer, IAudioSpatial, IAudioFade
{
#region Static - ID Generation & Resource Tracking
private static int _audioIDCounter;
private static Dictionary<string, int> _loadedAssetPaths = new();
/// <summary>
/// 生成唯一的 AudioID
/// </summary>
public static int GenAudioID() => ++_audioIDCounter;
/// <summary>
/// 重置 AudioID 计数器
/// </summary>
public static void ReleaseAudioIDs() => _audioIDCounter = 0;
/// <summary>
/// 释放所有已加载的资源
/// </summary>
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<AudioSource>();
if (!source)
{
source = go.AddComponent<AudioSource>();
}
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();
}
}
/// <summary>
/// 判断播放是否已结束
/// </summary>
private bool IsPlaybackEnded()
{
if (_audioSource == null || _clip == null)
return false;
return _audioSource.isPlaying
? _audioSource.time > _clip.length
: true;
}
#endregion
#region Playback Control
/// <summary>
/// 播放音频(需要先设置 Clip
/// </summary>
public void Play()
{
if (_clip == null)
{
DebugUtil.LogError("AudioPlayer: Cannot play without clip");
return;
}
_playState = PlayState.Playing;
_fadeEffect.Stop();
_targetVolume = 1f;
_audioSource.Play();
NotifyPlayStarted();
}
/// <summary>
/// 暂停播放
/// </summary>
public void Pause()
{
if (_audioSource != null)
_audioSource.Pause();
_playState = PlayState.Paused;
}
/// <summary>
/// 恢复播放
/// </summary>
public void Resume()
{
if (_audioSource != null)
_audioSource.UnPause();
_playState = PlayState.Playing;
}
/// <summary>
/// 停止播放
/// </summary>
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
/// <summary>
/// 异步加载并播放音频(不带淡入淡出)
/// </summary>
public void Play(string path)
{
_playState = PlayState.Loading;
LoadAndPlayInternal(path, useFade: false);
}
/// <summary>
/// 异步加载并播放音频(带淡入淡出)
/// </summary>
public void PlayWithFade(string path,
float fadeInTime = AudioConstant.DefaultFadeInDuration,
float fadeOutTime = AudioConstant.DefaultFadeOutDuration)
{
_playState = PlayState.Loading;
LoadAndPlayInternal(path, useFade: true, fadeInTime, fadeOutTime);
}
/// <summary>
/// 统一的异步加载和播放逻辑
/// </summary>
private async void LoadAndPlayInternal(string path, bool useFade,
float fadeInTime = AudioConstant.DefaultFadeInDuration,
float fadeOutTime = AudioConstant.DefaultFadeOutDuration)
{
// 1. 异步加载音频资源
_clip = await AssetManager.Instance.LoadAssetAsync<AudioClip>(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();
}
/// <summary>
/// 跟踪已加载的资源(用于引用计数)
/// </summary>
private void TrackLoadedAsset(string path)
{
if (_loadedAssetPaths.ContainsKey(path))
++_loadedAssetPaths[path];
else
_loadedAssetPaths.TryAdd(path, 1);
}
/// <summary>
/// 通知 Manager 播放已开始
/// </summary>
private void NotifyPlayStarted()
{
AudioManager.Instance.AudioMgrObj.Add2Playing(_audioID);
}
#endregion
#region Volume & Configuration
/// <summary>
/// 设置音量0-1
/// </summary>
public void SetVolume(float volume)
{
_targetVolume = Mathf.Clamp01(volume);
}
/// <summary>
/// 设置归一化音量(配置表中的音量缩放)
/// </summary>
public void SetNormalizeVolume(float vol = 1f)
{
_normalizeVolume = vol;
}
/// <summary>
/// 设置是否为语音
/// </summary>
public void SetIsSpeak(bool isSpeak)
{
_isSpeak = isSpeak;
}
/// <summary>
/// 设置循环播放
/// </summary>
public void SetLoop(bool isLoop)
{
_loop = isLoop;
if (_audioSource != null)
_audioSource.loop = isLoop;
}
/// <summary>
/// 设置音频片段
/// </summary>
public void SetClip(AudioClip clip)
{
_clip = clip;
if (_audioSource != null)
_audioSource.clip = clip;
}
/// <summary>
/// 统一的音量更新方法
/// 最终音量 = 淡入淡出系数 × 类型音量 × 全局音量 × 目标音量 × 归一化音量
/// </summary>
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)
/// <summary>
/// 设置是否启用 3D 空间音效
/// </summary>
public void SetIs3D(bool is3D = false)
{
if (is3D)
_spatialController.Enable3D();
else
_spatialController.Disable3D();
}
/// <summary>
/// 设置跟随对象(自动启用 3D
/// </summary>
public void SetFollowTarget(GameObject target = null)
{
_spatialController.SetFollowTarget(target);
}
/// <summary>
/// 设置静态位置(自动启用 3D
/// </summary>
public void SetPosition(Vector3 position)
{
_spatialController.SetStaticPosition(position, enable3D: true);
}
#endregion
#region Fade Effects
/// <summary>
/// 淡入播放(需要先设置 Clip
/// </summary>
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();
}
/// <summary>
/// 淡出停止(淡出完成后自动调用 Stop
/// </summary>
public void StopWithFadeOut(float duration = AudioConstant.DefaultFadeOutDuration)
{
_fadeEffect.StartFadeOut(duration);
}
#endregion
#region Query & Debug
/// <summary>
/// 获取 AudioSource 组件
/// </summary>
public AudioSource GetAudioSource() => _audioSource;
/// <summary>
/// 获取当前播放时间(秒)
/// </summary>
public float GetPlaybackTime() => _audioSource ? _audioSource.time : -1f;
/// <summary>
/// 获取音频频谱数据(用于可视化)
/// </summary>
public void GetSpectrumData(float[] spectrums)
{
if (_audioSource != null)
{
_audioSource.GetSpectrumData(spectrums, 0, FFTWindow.Blackman);
}
}
#endregion
}