using System; using System.Collections.Generic; using UnityEngine; using Wwise.Wooduan.Components; using Wwise.Wooduan.Core; using Wwise.Wooduan.Tools; public enum AmbientEmitterPositionMode { Single, Large, MultiPosition, Linear, Polygon, Volumetric } public enum AudioVerticalRange { Flat, Limited, Infinity } public class AmbientAkGameObj : AkGameObj { private AkAmbient _akAmbient; private float _timeOfLastCullCheck; protected override void Awake() { _akAmbient = GetComponent(); base.Awake(); } public override Vector3 GetPosition() { if (_akAmbient) return _akAmbient.GetPosition(); return base.GetPosition(); } public override bool ShouldUpdateThisFrame() { if (_akAmbient && _akAmbient.cullDistance > 0) { if (Time.time - _timeOfLastCullCheck > 0.5f) { _timeOfLastCullCheck = Time.time; var listenerPosition = ListenerManager.GetListenerLocation(); _akAmbient.TryCull(listenerPosition); } } return base.ShouldUpdateThisFrame(); } } public partial class AkAmbient : AkComponent, ICullableAudioObject { public AK.Wwise.Event data = new AK.Wwise.Event(); public AmbientEmitterPositionMode positionMode; public float heightOrRadius; public float cullDistance = 0f; public bool followInside = true; public bool invertInside; public AudioVerticalRange audioVerticalRange; public AkAmbientLargeModePositioner[] LargeModePositions = new AkAmbientLargeModePositioner[0]; private Vector3 _emitterPosition; private bool _isInsideRegion2D; private bool _isInsideRegion3D; private Collider _collider; private AmbientAkGameObj _akGameObj; private uint _playingID; #region LifeCycle private void Awake() { var akGameObj = GetComponent(); if (akGameObj && !(akGameObj is AmbientAkGameObj)) DestroyImmediate(akGameObj); _akGameObj = AkWooduanHelper.GetOrAddComponent(gameObject); } private void OnEnable() { UpdatePosition(); PlaySound(); } private void Start() { UpdatePosition(); } private void OnDisable() { StopSound(); } public void PlaySound() { if (!IsValid()) return; if (positionMode == AmbientEmitterPositionMode.MultiPosition) { var multiPositionSoundEmitter = multiPosEventTree[data.Id]; if (!multiPositionSoundEmitter.eventIsPlaying) { multiPositionSoundEmitter.eventIsPlaying = true; var emitter = multiPositionSoundEmitter.list[0].gameObject; _playingID = data.Post(emitter, AudioTriggerSource.AkAmbient, (uint) AkCallbackType.AK_EndOfEvent, multiPositionSoundEmitter.FinishedPlaying); } } else _playingID = AudioManager.PlaySound(data.Name, gameObject, AudioTriggerSource.AkAmbient); } private void StopSound() { if (!IsValid()) return; if (positionMode == AmbientEmitterPositionMode.MultiPosition) { var eventPosList = multiPosEventTree[data.Id]; if (eventPosList.list.Count == 1) { var emitter = eventPosList.list[0].gameObject; AkSoundEngine.StopPlayingID(_playingID); multiPosEventTree.Remove(data.Id); } else // set position to remaining emitters { eventPosList.list.Remove(this); var positionArray = BuildMultiDirectionArray(eventPosList); AkSoundEngine.SetMultiplePositions(eventPosList.list[0].gameObject, positionArray, (ushort) positionArray.Count, AkMultiPositionType.MultiPositionType_MultiSources); } } else AkSoundEngine.StopPlayingID(_playingID); } #endregion LifeCycle #region Position private void UpdatePosition() { if (!_akGameObj) return; switch (positionMode) { case AmbientEmitterPositionMode.Single: _akGameObj.autoUpdatePosition = false; _akGameObj.InitPosition(); break; case AmbientEmitterPositionMode.Large: _akGameObj.enabled = false; _akGameObj.autoUpdatePosition = false; var positionArray = BuildAkPositionArray(); AkSoundEngine.SetMultiplePositions(gameObject, positionArray, (ushort)positionArray.Count, AkMultiPositionType.MultiPositionType_MultiDirections); break; case AmbientEmitterPositionMode.MultiPosition: _akGameObj.autoUpdatePosition = false; AkMultiPosEvent eventPosList; if (multiPosEventTree.TryGetValue(data.Id, out eventPosList)) { if (!eventPosList.list.Contains(this)) eventPosList.list.Add(this); } else { eventPosList = new AkMultiPosEvent(); eventPosList.list.Add(this); multiPosEventTree.Add(data.Id, eventPosList); } positionArray = BuildMultiDirectionArray(eventPosList); AkSoundEngine.SetMultiplePositions(eventPosList.list[0].gameObject, positionArray, (ushort) positionArray.Count, AkMultiPositionType.MultiPositionType_MultiSources); break; case AmbientEmitterPositionMode.Volumetric: case AmbientEmitterPositionMode.Linear: case AmbientEmitterPositionMode.Polygon: _akGameObj.autoUpdatePosition = true; _collider = GetComponent(); break; } } #endregion #region NewModes public Vector3 GetPosition() { var listenerPosition = ListenerManager.GetListenerLocation(); _emitterPosition = FindEmitterPoint(listenerPosition); // var inside3D = IsListenerWithinRegion3D(listenerPosition); // if (inside3D != _isInsideRegion3D) // { // _isInsideRegion3D = inside3D; // OnListenerEnterExit(); // } return _emitterPosition; } private static Vector3 FindNearestPointOnLineSegment(Vector3 origin, Vector3 end, Vector3 point) { //Get heading Vector3 heading = (end - origin); float magnitudeMax = heading.magnitude; heading.Normalize(); //Do projection from the point but clamp it Vector3 lhs = point - origin; float dotP = Vector3.Dot(lhs, heading); dotP = Mathf.Clamp(dotP, 0f, magnitudeMax); return origin + heading * dotP; } private Vector3 FindEmitterPoint(Vector3 listenerPosition) { switch (positionMode) { case AmbientEmitterPositionMode.Volumetric: return _collider.ClosestPoint(listenerPosition); case AmbientEmitterPositionMode.Linear: case AmbientEmitterPositionMode.Polygon: var nearestDistance = 9999f; var nearestPointOnLine = listenerPosition; for (var i = 0; i < LargeModePositions.Length; i++) { var thisPoint = LargeModePositions[i]; var nextPoint = FindNextVertex(i); if (nextPoint) { var pointOnLine = FindNearestPointOnLineSegment(thisPoint.Position, nextPoint.Position, listenerPosition); var distance = Vector3.Distance(listenerPosition, pointOnLine); if (distance < nearestDistance) { nearestDistance = distance; nearestPointOnLine = pointOnLine; } } } return nearestPointOnLine; default: return transform.position; } } private AkAmbientLargeModePositioner FindNextVertex(int currentIndex) { AkAmbientLargeModePositioner nextPoint = null; if (currentIndex == LargeModePositions.Length - 1) { if (positionMode == AmbientEmitterPositionMode.Linear) return null; nextPoint = LargeModePositions[0]; } else nextPoint = LargeModePositions[currentIndex + 1]; return nextPoint; } private bool IsListenerWithinRegion3D(Vector3 listenerPosition) { switch (positionMode) { case AmbientEmitterPositionMode.Linear: { var isInsideTube = IsListenerInsideTube(listenerPosition); if (isInsideTube) _emitterPosition = listenerPosition; return isInsideTube; } case AmbientEmitterPositionMode.Volumetric: return _emitterPosition == listenerPosition; case AmbientEmitterPositionMode.Polygon: { _isInsideRegion2D = IsListenerInsidePolygon(listenerPosition); if (!_isInsideRegion2D) return false; if (followInside) { _emitterPosition.x = listenerPosition.x; _emitterPosition.z = listenerPosition.z; } switch (audioVerticalRange) { case AudioVerticalRange.Flat: _emitterPosition.y = LargeModePositions[0].transform.position.y; return false; case AudioVerticalRange.Limited: var minHeight = LargeModePositions[0].transform.position.y; var maxHeight = minHeight + heightOrRadius; if (listenerPosition.y > maxHeight) { _emitterPosition.y = maxHeight; return false; } if (listenerPosition.y < minHeight) { _emitterPosition.y = maxHeight; return false; } break; } _emitterPosition.y = listenerPosition.y; break; } } return true; } private bool IsListenerInsidePolygon(Vector3 listenerPosition) { var inside = false; for (var i = 0; i < LargeModePositions.Length; i++) { var thisPoint = LargeModePositions[i].Position; var nextPoint = i == LargeModePositions.Length - 1 ? LargeModePositions[0].Position : LargeModePositions[i+1].Position; var intersect = thisPoint.z > listenerPosition.z != nextPoint.z > listenerPosition.z && listenerPosition.x < (nextPoint.x - thisPoint.x) * (listenerPosition.z - thisPoint.z) / (nextPoint.z - thisPoint.z) + thisPoint.x; if (intersect) inside = !inside; } return invertInside ? !inside : inside; } private bool IsListenerInsideTube(Vector3 listenerPosition) { var distance = Vector3.Distance(_emitterPosition, listenerPosition); return distance < heightOrRadius; } private void OnListenerEnterExit() { // if (_isInsideRegion3D) // ListenerManager.SetListenerToSelf(gameObject); // else // ListenerManager.ResetListenerToDefault(gameObject); } #endregion public override bool IsValid() { return data.IsValid(); } #region ICullableAudioObject public bool IsCulled { get; set; } public void TryCull(Vector3 listenerPosition) { if (cullDistance <= 0) return; var distance = Vector3.Distance(listenerPosition, _emitterPosition); if (distance > cullDistance * 1.1f) OnCull(); else if (distance < cullDistance * 0.9f) OnReveal(); } public void OnCull() { if (!IsCulled) { StopSound(); IsCulled = true; } } public void OnReveal() { if (IsCulled) { UpdatePosition(); PlaySound(); IsCulled = false; } } #endregion ICullableAudioObject #region Gizmos #if UNITY_EDITOR private void OnDrawGizmos() { var iconPosition = transform.position; switch (positionMode) { case AmbientEmitterPositionMode.Linear: case AmbientEmitterPositionMode.Polygon: case AmbientEmitterPositionMode.Volumetric: iconPosition = _emitterPosition; break; } UnityEngine.Gizmos.DrawIcon(iconPosition, "WwiseAudioSpeaker.png", AkWwiseEditorSettings.Instance.iconScaling); } public void OnDrawGizmosSelected() { switch (positionMode) { case AmbientEmitterPositionMode.Large: case AmbientEmitterPositionMode.Polygon: case AmbientEmitterPositionMode.Linear: { foreach (var entry in LargeModePositions) { if (entry != null) { UnityEngine.Gizmos.color = UnityEngine.Color.green; UnityEngine.Gizmos.DrawSphere(entry.transform.position, 0.1f); UnityEditor.Handles.Label(entry.transform.position, entry.name); } } break; } } } public void DrawConnectingLines() { if (LargeModePositions.Length < 2) return; UnityEditor.Handles.color = Color.green; for (var i = 0; i < LargeModePositions.Length; i++) { var thisPoint = LargeModePositions[i]; var nextPoint = FindNextVertex(i); if (nextPoint) { UnityEditor.Handles.DrawLine(thisPoint.Position, nextPoint.Position); if (positionMode == AmbientEmitterPositionMode.Polygon) { switch (audioVerticalRange) { case AudioVerticalRange.Limited when heightOrRadius > 0: { var thisPointUp = thisPoint.Position + Vector3.up * heightOrRadius; UnityEditor.Handles.DrawLine(thisPoint.Position, thisPointUp); UnityEditor.Handles.DrawLine(thisPointUp, nextPoint.Position + Vector3.up * heightOrRadius); break; } case AudioVerticalRange.Infinity: UnityEditor.Handles.DrawDottedLine(thisPoint.Position + Vector3.down, thisPoint.Position + Vector3.up, 5); break; } } } } } #endif #endregion Gizmos }