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); } } }