重构音频模块 1. 高频、大量音频反复调用时,单帧 CPU 开销与 GC 最优 2. AudioClip / AudioSource 的加载、缓存淘汰、卸载形成完整闭环,避免线性遍历 3. AudioSource 对象池 + 播放请求 struct 全部池化覆盖所有分配点 4. 支持3D环境音并具备距离衰减、遮挡等空间属性 5. 新增音频类型(BGM/SFX/Voice/Ambient) 6. 可调式监控Debug信息 及时跟踪音频缓存 处理 句柄状态
287 lines
8.4 KiB
C#
287 lines
8.4 KiB
C#
using AlicizaX;
|
|
using UnityEngine;
|
|
|
|
namespace AlicizaX.Audio.Runtime
|
|
{
|
|
[DisallowMultipleComponent]
|
|
[AddComponentMenu("Game Framework/Audio/Audio Emitter")]
|
|
public sealed class AudioEmitter : MonoBehaviour
|
|
{
|
|
private enum AudioEmitterClipMode
|
|
{
|
|
Address = 0,
|
|
Clip = 1
|
|
}
|
|
|
|
[Header("Playback")]
|
|
[SerializeField] private AudioType m_AudioType = AudioType.Ambient;
|
|
[SerializeField] private AudioEmitterClipMode m_ClipMode = AudioEmitterClipMode.Address;
|
|
[SerializeField] private string m_Address = string.Empty;
|
|
[SerializeField] private AudioClip m_Clip;
|
|
[SerializeField] private bool m_PlayOnEnable = true;
|
|
[SerializeField] private bool m_Loop = true;
|
|
[SerializeField, Range(0f, 1f)] private float m_Volume = 1f;
|
|
[SerializeField] private bool m_Async = true;
|
|
[SerializeField] private bool m_CacheClip = true;
|
|
[SerializeField] private bool m_StopWithFadeout = true;
|
|
|
|
[Header("Spatial")]
|
|
[SerializeField] private bool m_FollowSelf = true;
|
|
[SerializeField] private Vector3 m_FollowOffset = Vector3.zero;
|
|
[SerializeField, Range(0f, 1f)] private float m_SpatialBlend = 1f;
|
|
[SerializeField] private AudioRolloffMode m_RolloffMode = AudioRolloffMode.Logarithmic;
|
|
[SerializeField, Min(0f)] private float m_MinDistance = 2f;
|
|
[SerializeField, Min(0f)] private float m_MaxDistance = 30f;
|
|
|
|
[Header("Trigger")]
|
|
[SerializeField] private bool m_UseTriggerRange = false;
|
|
[SerializeField, Min(0f)] private float m_TriggerRange = 10f;
|
|
[SerializeField, Min(0f)] private float m_TriggerHysteresis = 0.5f;
|
|
|
|
[Header("Gizmos")]
|
|
[SerializeField] private bool m_DrawGizmos = true;
|
|
[SerializeField] private bool m_DrawOnlyWhenSelected = true;
|
|
[SerializeField] private Color m_TriggerColor = new Color(0.2f, 0.9f, 1f, 0.9f);
|
|
[SerializeField] private Color m_MinDistanceColor = new Color(1f, 0.9f, 0.2f, 0.9f);
|
|
[SerializeField] private Color m_MaxDistanceColor = new Color(1f, 0.45f, 0.05f, 0.9f);
|
|
|
|
private IAudioService _audioService;
|
|
private ulong _handle;
|
|
private bool _isPlaying;
|
|
private bool _insideTriggerRange;
|
|
|
|
public ulong Handle => _handle;
|
|
public bool IsPlaying => _isPlaying;
|
|
|
|
private void OnEnable()
|
|
{
|
|
TryBindService();
|
|
|
|
if (m_PlayOnEnable && !m_UseTriggerRange)
|
|
{
|
|
StartPlayback();
|
|
}
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if (!m_UseTriggerRange)
|
|
{
|
|
RefreshPlaybackState();
|
|
return;
|
|
}
|
|
|
|
if (_audioService == null && !TryBindService())
|
|
{
|
|
return;
|
|
}
|
|
|
|
Transform listener = _audioService.ListenerTransform;
|
|
if (listener == null)
|
|
{
|
|
_insideTriggerRange = false;
|
|
StopPlayback();
|
|
return;
|
|
}
|
|
|
|
Vector3 offset = listener.position - transform.position;
|
|
float range = _isPlaying ? m_TriggerRange + m_TriggerHysteresis : m_TriggerRange;
|
|
float sqrRange = range * range;
|
|
|
|
if (offset.sqrMagnitude <= sqrRange)
|
|
{
|
|
RefreshPlaybackState();
|
|
if (!_insideTriggerRange || (m_Loop && !_isPlaying))
|
|
{
|
|
_insideTriggerRange = true;
|
|
StartPlayback();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_insideTriggerRange = false;
|
|
StopPlayback();
|
|
}
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
StopPlayback();
|
|
_audioService = null;
|
|
_insideTriggerRange = false;
|
|
}
|
|
|
|
public void Play()
|
|
{
|
|
if (_audioService == null)
|
|
{
|
|
TryBindService();
|
|
}
|
|
|
|
StartPlayback();
|
|
}
|
|
|
|
public void Stop()
|
|
{
|
|
StopPlayback();
|
|
}
|
|
|
|
private bool TryBindService()
|
|
{
|
|
return AppServices.TryGet(out _audioService);
|
|
}
|
|
|
|
private void StartPlayback()
|
|
{
|
|
if (_audioService == null || _isPlaying || !HasPlayableAsset())
|
|
{
|
|
return;
|
|
}
|
|
|
|
float maxDistance = m_MaxDistance >= m_MinDistance ? m_MaxDistance : m_MinDistance;
|
|
if (m_FollowSelf)
|
|
{
|
|
_handle = m_ClipMode == AudioEmitterClipMode.Clip
|
|
? _audioService.PlayFollow(
|
|
m_AudioType,
|
|
m_Clip,
|
|
transform,
|
|
m_FollowOffset,
|
|
m_MinDistance,
|
|
maxDistance,
|
|
m_RolloffMode,
|
|
m_SpatialBlend,
|
|
m_Loop,
|
|
m_Volume)
|
|
: _audioService.PlayFollow(
|
|
m_AudioType,
|
|
m_Address,
|
|
transform,
|
|
m_FollowOffset,
|
|
m_MinDistance,
|
|
maxDistance,
|
|
m_RolloffMode,
|
|
m_SpatialBlend,
|
|
m_Loop,
|
|
m_Volume,
|
|
m_Async,
|
|
m_CacheClip);
|
|
}
|
|
else
|
|
{
|
|
Vector3 position = transform.position;
|
|
_handle = m_ClipMode == AudioEmitterClipMode.Clip
|
|
? _audioService.Play3D(
|
|
m_AudioType,
|
|
m_Clip,
|
|
position,
|
|
m_MinDistance,
|
|
maxDistance,
|
|
m_RolloffMode,
|
|
m_SpatialBlend,
|
|
m_Loop,
|
|
m_Volume)
|
|
: _audioService.Play3D(
|
|
m_AudioType,
|
|
m_Address,
|
|
position,
|
|
m_MinDistance,
|
|
maxDistance,
|
|
m_RolloffMode,
|
|
m_SpatialBlend,
|
|
m_Loop,
|
|
m_Volume,
|
|
m_Async,
|
|
m_CacheClip);
|
|
}
|
|
|
|
_isPlaying = _handle != 0UL;
|
|
}
|
|
|
|
private void StopPlayback()
|
|
{
|
|
if (!_isPlaying)
|
|
{
|
|
_handle = 0UL;
|
|
return;
|
|
}
|
|
|
|
if (_audioService != null && _handle != 0UL)
|
|
{
|
|
_audioService.Stop(_handle, m_StopWithFadeout);
|
|
}
|
|
|
|
_handle = 0UL;
|
|
_isPlaying = false;
|
|
}
|
|
|
|
private bool HasPlayableAsset()
|
|
{
|
|
return m_ClipMode == AudioEmitterClipMode.Clip
|
|
? m_Clip != null
|
|
: !string.IsNullOrEmpty(m_Address);
|
|
}
|
|
|
|
private void RefreshPlaybackState()
|
|
{
|
|
if (!_isPlaying || _audioService == null || _handle == 0UL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (_audioService.IsPlaying(_handle))
|
|
{
|
|
return;
|
|
}
|
|
|
|
_handle = 0UL;
|
|
_isPlaying = false;
|
|
}
|
|
|
|
private void OnValidate()
|
|
{
|
|
if (m_MaxDistance < m_MinDistance)
|
|
{
|
|
m_MaxDistance = m_MinDistance;
|
|
}
|
|
}
|
|
|
|
private void OnDrawGizmos()
|
|
{
|
|
if (!m_DrawOnlyWhenSelected)
|
|
{
|
|
DrawEmitterGizmos();
|
|
}
|
|
}
|
|
|
|
private void OnDrawGizmosSelected()
|
|
{
|
|
if (m_DrawOnlyWhenSelected)
|
|
{
|
|
DrawEmitterGizmos();
|
|
}
|
|
}
|
|
|
|
private void DrawEmitterGizmos()
|
|
{
|
|
if (!m_DrawGizmos)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Vector3 position = transform.position;
|
|
if (m_UseTriggerRange)
|
|
{
|
|
Gizmos.color = m_TriggerColor;
|
|
Gizmos.DrawWireSphere(position, m_TriggerRange);
|
|
}
|
|
|
|
Gizmos.color = m_MinDistanceColor;
|
|
Gizmos.DrawWireSphere(position, m_MinDistance);
|
|
|
|
Gizmos.color = m_MaxDistanceColor;
|
|
Gizmos.DrawWireSphere(position, m_MaxDistance);
|
|
}
|
|
}
|
|
}
|