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

503 lines
13 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 UnityEngine.Networking;
/// <summary>
/// 音频播放器
/// 实现 IAudioPlayer核心功能、IAudioSpatial3D音效、IAudioFade淡入淡出接口
/// </summary>
public class AudioPlayer: AudioBase, IAudioPlayer, IAudioSpatial, IAudioFade
{
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; //最近一次播放的时间戳
private static Dictionary<string, int> _loadedAssetPaths = 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<AudioSource>();
if (!source)
{
source = _rootGo.AddComponent<AudioSource>();
}
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)
{
_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 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
/// <summary>
/// 生成唯一的播放ID
/// </summary>
private long _GenPlayID() => ++_curPlayID;
/// <summary>
/// 重置PlayID计数器
/// </summary>
public static void ResetPlayID() => _curPlayID = 0;
/// <summary>
/// 获取AudioSource组件
/// </summary>
public AudioSource GetAudioSource() => _audioSource;
/// <summary>
/// 统一的音量设置方法,根据音频类型和是否为语音自动选择对应的音量系数
/// </summary>
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 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<AudioClip>(path, null, true);
if (_loadedAssetPaths.ContainsKey(path))
{
++_loadedAssetPaths[path];
}
else
{
_loadedAssetPaths.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<AudioClip>(path, null, true);
if (_loadedAssetPaths.ContainsKey(path))
{
++_loadedAssetPaths[path];
}
else
{
_loadedAssetPaths.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();
}
/// <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);
}
public void SetIsLoop(bool isLoop)
{
loop = isLoop;
_audioSource.loop = isLoop;
}
/// <summary>
/// 设置是否为语音
/// </summary>
public void SetIsSpeak(bool isSpeak) => _isSpeak = isSpeak;
/// <summary>
/// 设置归一化音量
/// </summary>
public void SetNormalizeVolume(float vol = 1f) => _normalizeVolume = vol;
/// <summary>
/// 获取音频名称
/// </summary>
public string GetCueSheetName() => _audioSource && _clip ? _clip.name : "";
/// <summary>
/// 获取当前播放时间(秒)
/// </summary>
public float GetPlaybackTime() => _audioSource ? _audioSource.time : -1f;
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();
}
/// <summary>
/// 获取音频频谱数据(用于可视化)
/// </summary>
public void GetSpectrumData(float[] spectrums)
{
if (_audioSource != null)
{
_audioSource.GetSpectrumData(spectrums, 0, FFTWindow.Blackman);
}
}
#endregion
#region Interface Implementation - Public直接实现
// ========================================
// 接口属性实现IAudioPlayer
// ========================================
/// <summary>
/// IAudioPlayer.AudioId - 实例ID
/// </summary>
public int AudioId => audioID;
/// <summary>
/// IAudioPlayer.State - 播放状态
/// </summary>
public AudioBase.PlayState State => playState;
// 注IsPlaying 继承自 AudioBase
// 注Pause/Resume/Stop 继承自 AudioBase
// ========================================
// 接口方法实现直接public无需显式实现
// ========================================
/// <summary>
/// IAudioPlayer.Play - 无参数播放调用Play(1f)
/// </summary>
public void Play() => Play(1f);
/// <summary>
/// IAudioPlayer.SetClip - 设置音频片段
/// </summary>
public void SetClip(AudioClip clip)
{
_clip = clip;
if (_audioSource != null)
_audioSource.clip = clip;
}
/// <summary>
/// IAudioPlayer.SetVolume - 设置音量
/// </summary>
public void SetVolume(float volume) => SetTargetVolume(volume);
/// <summary>
/// IAudioPlayer.SetLoop - 设置循环
/// </summary>
public void SetLoop(bool loop) => SetIsLoop(loop);
// 注IAudioSpatial 的 SetPosition 和 SetFollowTarget 已在 API 区域实现
// ========================================
// IAudioFade 接口实现
// ========================================
/// <summary>
/// 淡入播放会自动调用Play
/// </summary>
public void PlayWithFadeIn(float duration)
{
if (_clip == null)
{
DebugUtil.LogError("AudioPlayer: Cannot PlayWithFadeIn without clip");
return;
}
_fadeEffect.FadeInDuration = duration;
_fadeEffect.StartFadeIn();
playState = PlayState.Playing;
_audioSource.volume = 0f;
_audioSource.Play();
_playID = _GenPlayID();
_playTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
AudioManager.Instance.AudioMgrObj.Add2Playing(audioType, audioID, _isSpeak);
}
/// <summary>
/// 淡入播放默认2秒
/// </summary>
public void PlayWithFadeIn() => PlayWithFadeIn(2f);
/// <summary>
/// 淡出停止
/// </summary>
public void StopWithFadeOut(float duration) => StopWithFade(duration);
/// <summary>
/// 淡出停止默认2秒
/// </summary>
public void StopWithFadeOut() => StopWithFadeOut(2f);
#endregion
}
public class AudioConstant
{
/// <summary>
/// 最大音乐播放数
/// </summary>
public const int MaxMusicCount = 1;
/// <summary>
/// 最大音频播放数
/// </summary>
public const int MaxSoundCount = 100;
/// <summary>
/// 最大语音播放数
/// </summary>
public const int MaxSpeakCount = 1;
/// <summary>
/// 优先级
/// </summary>
public const int MaxPriority = 99;
public const int MinPriority = 0;
public const int DefaultPriority = 29;
public const int LowPriority = 30;
}