using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.Profiling; using Wwise.Wooduan.Components; using Wwise.Wooduan.Tools; using Object = UnityEngine.Object; namespace Wwise.Wooduan.Core { public static class EmitterManager { #region LifeCycle private static bool IsInitialized; internal static void Initialize() { if (IsInitialized) return; IsInitialized = true; CreateGlobalAudioEmitter(); InitDefaultEmitters(); InitEmitterPool(); InitGameSync(); AkSoundEngineController.Instance.Update += Update; } internal static void Terminate() { if (!IsInitialized) return; IsInitialized = false; TerminateEmitterPool(); TerminateGameSync(); TerminateDefaultEmitters(); RemoveGlobalAudioEmitter(); AkSoundEngineController.Instance.Update -= Update; } private static void Update() { if (!IsInitialized) return; Profiler.BeginSample("EmitterManager.Update"); RecyclePooledEmitters(); TickEmitters(); Profiler.EndSample(); } #endregion LifeCycle #region DefaultEmitters private static Queue _defaultEmitters; private static void InitDefaultEmitters() { _defaultEmitters = new Queue(); } private static void TerminateDefaultEmitters() { _defaultEmitters = null; } internal static void RegisterDefaultEmitter(AkGameObj emitter) { if (IsInitialized) _defaultEmitters.Enqueue(emitter); } #endregion DefaultEmitters #region UpdatePosition private const int MaxEmitterUpdatesPerFrame = 32; private const int MaxAlwaysUpdateDistance = 20; private const float DistancePerFrameDrop = 10f; private static void TickEmitters() { if (_defaultEmitters.Count == 0) return; var emittersUpdatedThisFrame = 0; var emitterCheckedThisFrame = 0; while (emittersUpdatedThisFrame < MaxEmitterUpdatesPerFrame && emitterCheckedThisFrame < _defaultEmitters.Count) { var emitter = _defaultEmitters.Dequeue(); emitterCheckedThisFrame++; if (!emitter || !emitter.isActiveAndEnabled) continue; if (emitter.ShouldUpdateThisFrame()) { emitter.UpdatePosition(); emittersUpdatedThisFrame++; } _defaultEmitters.Enqueue(emitter); } } internal static int CalculateUpdateFrequency(Vector3 emitterPosition) { var distanceToListener = Vector3.Distance(emitterPosition, ListenerManager.GetListenerLocation(0)); if (distanceToListener < MaxAlwaysUpdateDistance) return 1; var frames = 1 + Mathf.CeilToInt((distanceToListener - MaxAlwaysUpdateDistance) / DistancePerFrameDrop); return Mathf.Min(frames, 30); } internal static uint GetActiveVoiceCount(GameObject emitter) { var numOfEvents = 0U; AkSoundEngine.GetPlayingIDsFromGameObject(emitter, ref numOfEvents, null); return numOfEvents; } #endregion UpdatePosition #region GlobalEmitter private static void CreateGlobalAudioEmitter() { AkSoundEnginePINVOKE.CSharp_RegisterGameObjInternal_WithName(AkSoundEngine.AK_GLOBAL_GAME_OBJECT, "Unity Global"); AkSoundEnginePINVOKE.CSharp_SetListeners(AkSoundEngine.AK_GLOBAL_GAME_OBJECT, new ulong [] {AkSoundEngine.AK_GLOBAL_GAME_OBJECT}, 1); } private static void RemoveGlobalAudioEmitter() { AkSoundEnginePINVOKE.CSharp_UnregisterGameObjInternal(AkSoundEngine.AK_GLOBAL_GAME_OBJECT); } #endregion #region EmitterPool private static Queue _availableEmitterQueue, _occupiedEmitterQueue; private static uint _emitterPoolSize; private static GameObject _emitterRoot; private const int MaxEmitterPoolSize = 32; private static void InitEmitterPool() { if (!_emitterRoot) { _emitterRoot = new GameObject("AudioEmitterPool"); UnityEngine.Object.DontDestroyOnLoad(_emitterRoot); } _emitterPoolSize = 0; _availableEmitterQueue = new Queue(); _occupiedEmitterQueue = new Queue(); } private static void TerminateEmitterPool() { _availableEmitterQueue = null; _occupiedEmitterQueue = null; _emitterPoolSize = 0; UnityEngine.Object.DestroyImmediate(_emitterRoot); } public static PooledAkGameObj GetIdleEmitter() { PooledAkGameObj emitter = null; if (_availableEmitterQueue.Count > 0) emitter = _availableEmitterQueue.Dequeue(); if (!emitter) emitter = _emitterPoolSize < MaxEmitterPoolSize ? CreateNewEmitter() : ForceRecycleEmitter(); _occupiedEmitterQueue.Enqueue(emitter); emitter.Activate(); return emitter; } private static PooledAkGameObj CreateNewEmitter() { if (!_emitterRoot) InitEmitterPool(); var newEmitter = new GameObject("Emitter_" + _emitterPoolSize); newEmitter.transform.SetParent(_emitterRoot.transform); var akGameObj = newEmitter.AddComponent(); _emitterPoolSize++; return akGameObj; } private static void RecyclePooledEmitters() { if (_occupiedEmitterQueue.Count == 0) return; var emitter = _occupiedEmitterQueue.Dequeue(); if (!emitter) return; if (emitter.CanRecycle()) RecycleEmitter(emitter); else _occupiedEmitterQueue.Enqueue(emitter); } internal static void OnPooledEventEnd(object emitter, AkCallbackType callbackType, AkCallbackInfo callbackInfo) { var akGameObj = (PooledAkGameObj) emitter; if (akGameObj) akGameObj.FinishEvent(); } private static void RecycleEmitter(PooledAkGameObj emitter) { emitter.Unregister(); _availableEmitterQueue.Enqueue(emitter); } private static PooledAkGameObj ForceRecycleEmitter() { var attempts = 0; while (_occupiedEmitterQueue.Count > 0) { var emitter = _occupiedEmitterQueue.Dequeue(); if (!emitter) continue; if (emitter.IsLocked && attempts < MaxEmitterPoolSize) { _occupiedEmitterQueue.Enqueue(emitter); attempts++; } else { emitter.Unregister(); return emitter; } } return CreateNewEmitter(); } #endregion EmitterPool #region GameSync private class CachedGameSyncData { internal readonly Dictionary RTPCData = new Dictionary(); internal readonly Dictionary SwitchData = new Dictionary(); } private static Dictionary EmitterGameSyncDatas; private static Dictionary GlobalRTPCData; private static Dictionary GlobalStateData; private static void InitGameSync() { EmitterGameSyncDatas = new Dictionary(); GlobalRTPCData = new Dictionary(); GlobalStateData = new Dictionary(); } private static void TerminateGameSync() { EmitterGameSyncDatas = null; GlobalRTPCData = null; GlobalStateData = null; } internal static void UnregisterGameSync(GameObject emitter) { EmitterGameSyncDatas.Remove(emitter); } internal static bool CanUpdateRTPC(GameObject emitter, string RTPCName, float value, float tolerance) { if (!IsInitialized) return false; // global RTPC if (!emitter) { if (GlobalRTPCData.ContainsKey(RTPCName) && Math.Abs(GlobalRTPCData[RTPCName] - value) < tolerance) return false; GlobalRTPCData[RTPCName] = value; } // emitter RTPC else { if (!EmitterGameSyncDatas.ContainsKey(emitter)) EmitterGameSyncDatas.Add(emitter, new CachedGameSyncData()); var rtpcData = EmitterGameSyncDatas[emitter].RTPCData; if (rtpcData.ContainsKey(RTPCName) && Math.Abs(rtpcData[RTPCName] - value) < tolerance) return false; rtpcData[RTPCName] = value; } return true; } internal static float GetRTPC(GameObject emitter, string RTPCName) { if (!IsInitialized) return 0; if (!emitter) return GlobalRTPCData.ContainsKey(RTPCName) ? GlobalRTPCData[RTPCName] : GetRTPCInternal(emitter, RTPCName, 1); if (!EmitterGameSyncDatas.ContainsKey(emitter)) return GetRTPCInternal(emitter, RTPCName, 2); var rtpcData = EmitterGameSyncDatas[emitter].RTPCData; return rtpcData.ContainsKey(RTPCName) ? rtpcData[RTPCName] : GetRTPCInternal(emitter, RTPCName, 2); } private static float GetRTPCInternal(GameObject emitter, string RTPCName, int rtpcQueryType) { AkSoundEngine.GetRTPCValue(RTPCName, emitter, AkSoundEngine.AK_INVALID_PLAYING_ID, out var value, ref rtpcQueryType); return value; } internal static bool CanUpdateSwitch(GameObject emitter, string switchGroupName, string switchName) { if (!emitter) return true; if (!IsInitialized) return false; if (!EmitterGameSyncDatas.ContainsKey(emitter)) EmitterGameSyncDatas.Add(emitter, new CachedGameSyncData()); var switchData = EmitterGameSyncDatas[emitter].SwitchData; if (switchData.ContainsKey(switchGroupName) && switchData[switchGroupName] == switchName) return false; switchData[switchGroupName] = switchName; return true; } internal static string GetSwitch(GameObject emitter, string switchGroupName) { if (!IsInitialized || !EmitterGameSyncDatas.ContainsKey(emitter)) return string.Empty; var switchData = EmitterGameSyncDatas[emitter].SwitchData; if (switchData.ContainsKey(switchGroupName)) return switchData[switchGroupName]; return string.Empty; } internal static bool CanUpdateState(string stateGroupName, string stateName) { if (!IsInitialized) return false; if (GlobalStateData.ContainsKey(stateGroupName) && GlobalStateData[stateGroupName] == stateName) return false; GlobalStateData[stateGroupName] = stateName; return true; } internal static string GetState(string stateGroupName) { if (!IsInitialized) return "None"; if (GlobalStateData.ContainsKey(stateGroupName)) return GlobalStateData[stateGroupName]; return "None"; } #endregion GameSync } }