503 lines
13 KiB
C#
503 lines
13 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using PhxhSDK;
|
||
using UnityEngine;
|
||
using UnityEngine.Networking;
|
||
|
||
/// <summary>
|
||
/// 音频播放器
|
||
/// 实现 IAudioPlayer(核心功能)、IAudioSpatial(3D音效)、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;
|
||
|
||
} |