using System; using System.Collections.Generic; using UnityEngine; using Random = UnityEngine.Random; #if UNITY_EDITOR using UnityEditor; #endif namespace Gaia { [System.Serializable] public class GaiaAudioZoneItem { public AudioClip m_audioClip; public float m_volume = 1f; public float m_fadeInTime = 5f; public float m_fadeOutTime = 5f; } /// /// This class will play back a set of audio clip in a random order over time. Clips will never play twice in a row, unless there is only 1 clip. /// Audio zones are assumed to be non movable objects. If you move it then you will need to update its m_position variable. /// [RequireComponent(typeof(AudioSource))] [ExecuteInEditMode] [Serializable] public class GaiaAudioZone : MonoBehaviour { /// /// Audiozone states /// public enum AudioZoneState { Active, BecomingInactive, Inactive } /// /// Current zone volume taking into account fade in and out /// public float m_currentZoneVolume = 1; /// /// This is the list of audio clips we can play /// public List m_audioList = new List(); /// /// Currently playing audio item /// [NonSerialized] public GaiaAudioZoneItem m_currentAudioItem = null; /// /// Global audio source, or trigger based /// public bool m_isGlobalAudioSource = false; /// /// Minimum time in seconds to take a break between tracks /// public float m_minimumBreakTime = 5f; /// /// Maximum time in seconds to take a break between tracks /// public float m_maximumBreakTime = 10f; /// /// Set when the audio zone is active - in range of player /// [NonSerialized] public AudioZoneState m_audioZoneState = AudioZoneState.Inactive; /// /// Audio source for this component /// [NonSerialized] private AudioSource m_audioSource = null; /// /// Whether or not audio is currently playing /// [NonSerialized] public bool m_audioIsPlaying = false; /// /// Currently selected track index /// [NonSerialized] public int m_selectedTrackIndex = -1; /// /// Whether or not to draw gizmos /// [NonSerialized] public bool m_showGizmos = false; /// /// Gizmo color /// public Color m_gizmoColor = new Color(1f, 0.137112f, 0f, 0.4f); /// /// What the zone radius is for this audio zone (if its not a global audio zone) /// public float m_zoneRadius = 30f; /// /// What the zone radius squared is for this audio zone (if its not a global audio zone) /// private float m_zoneRadiusSqr = 30f * 30f; /// /// The time at which a play state change happened /// private float m_timeWhenTrackStarted = 0f; private float m_timeWhenFadeInEnds = 0f; private float m_timeWhenFadeOutBegins = 0f; private float m_timeWhenFadeOutEnds = 0f; private float m_timeWhenNextTrackStarts = 0f; private float m_timeWhenDeactivating = 0f; /// /// Set to true if viewing debug messages /// private bool m_showDebug = false; /// /// Amount of time it takes to deactivate a zone in seconds /// private float m_deactivationTime = 10f; #region Unity Methods /// /// Draw gizmo when object is selected /// private void OnDrawGizmosSelected() { if (m_showGizmos) { if (Application.isPlaying) { if (m_audioZoneState == AudioZoneState.Active) { DrawGizmos(); } } else { DrawGizmos(); } } } /// /// Draw gizmo when object is selected /// private void OnDrawGizmos() { OnDrawGizmosSelected(); } /// /// Draw gizmos /// private void DrawGizmos() { Color oldColor = Gizmos.color; Gizmos.color = m_gizmoColor; if (m_isGlobalAudioSource) { Gizmos.DrawSphere(gameObject.transform.position, 50f); } else { Gizmos.DrawSphere(gameObject.transform.position, m_zoneRadius); } Gizmos.color = oldColor; } /// /// On enable /// private void OnEnable() { //Setup audio source m_audioSource = GetComponent(); //Remove sphere collider if present - this is a hangover from the original implementation, added for backwards compatibility. SphereCollider sc = GetComponent(); if (sc != null) { if (Application.isPlaying) { GameObject.Destroy(sc); } else { GameObject.DestroyImmediate(sc); } } } /// /// When the audio zone is enabled /// private void Start() { //Initialize settings to stopped / inactive Initialize(); //Register this audiozone for potential activation if (GaiaAudioManager.Instance != null) { GaiaAudioManager.Instance.RegisterAudioZone(this); } } /// /// Called when audiozone is destroyed /// private void OnDestroy() { if (GaiaAudioManager.Instance != null) { GaiaAudioManager.Instance.DeRegisterAudioZone(this); } } #endregion #region Audio Zone Methods /// /// Initialize the audio zone /// public void Initialize() { //Set sqr for sqr distance check m_zoneRadiusSqr = m_zoneRadius * m_zoneRadius; //Set state to inactive m_audioZoneState = AudioZoneState.Inactive; //Make sure everything is shut down if (m_audioSource == null) { m_audioSource = GetComponent(); } m_audioSource.Stop(); m_audioSource.volume = 0f; m_audioIsPlaying = false; //Set up audio source settings if (!m_isGlobalAudioSource) { m_audioSource.spatialBlend = 1f; m_audioSource.maxDistance = m_zoneRadius; m_audioSource.rolloffMode = AudioRolloffMode.Linear; } else { m_audioSource.spatialBlend = 0f; } //Set up time until next track m_timeWhenNextTrackStarts = Time.time; } /// /// Called by GaiaAudioManager to handle zone updates - only called when its active /// public void ProcessActiveUpdate(float masterVolume, bool showGizmos) { //Set gizmo state m_showGizmos = showGizmos; float currentTime = Time.time; if (currentTime > m_timeWhenNextTrackStarts) { //Otherwise choose a new track PlayNextRandomTrack(); } //Now update the volume if (currentTime < m_timeWhenFadeInEnds) { float deltaTime = m_timeWhenFadeInEnds - m_timeWhenTrackStarted; if (deltaTime > 0f) { m_currentZoneVolume = Mathf.Lerp(m_currentAudioItem.m_volume, 0f, (m_timeWhenFadeInEnds - currentTime) / deltaTime); } else { m_currentZoneVolume = m_currentAudioItem.m_volume; } if (m_currentZoneVolume > masterVolume) { m_currentZoneVolume = masterVolume; } m_audioSource.volume = m_currentZoneVolume; } else if (currentTime < m_timeWhenFadeOutBegins) { m_currentZoneVolume = m_currentAudioItem.m_volume; if (m_currentZoneVolume > masterVolume) { m_currentZoneVolume = masterVolume; } m_audioSource.volume = m_currentZoneVolume; } else if (currentTime < m_timeWhenFadeOutEnds) { float deltaTime = m_timeWhenFadeOutEnds - m_timeWhenFadeOutBegins; if (deltaTime > 0f) { m_currentZoneVolume = Mathf.Lerp(0f, m_currentAudioItem.m_volume, (m_timeWhenFadeOutEnds - currentTime) / deltaTime); } else { m_currentZoneVolume = 0f; } if (m_currentZoneVolume > masterVolume) { m_currentZoneVolume = masterVolume; } m_audioSource.volume = m_currentZoneVolume; } //Or stop else if (m_audioIsPlaying) { Stop(); } } /// /// Plays the next random track from the list, but make sure the same track doesn't play twice in a row /// if at all possible. /// public void PlayNextRandomTrack() { // This will pick a random sound from the list, but make sure the same track doesnt play twice in a row int newTrackIdx = UnityEngine.Random.Range(0, m_audioList.Count); if (newTrackIdx == m_selectedTrackIndex) { newTrackIdx = m_selectedTrackIndex + 1; if (newTrackIdx >= m_audioList.Count) { newTrackIdx = 0; } } PlayTrack(newTrackIdx); } /// /// Plays the selected track /// public void PlayTrack(int trackIdx) { if (m_audioList.Count == 0) { Debug.LogWarning("GaiaAudioZone : Cannot play selected track, no tracks have been added!"); return; } m_selectedTrackIndex = trackIdx; if (m_selectedTrackIndex >= m_audioList.Count) { Debug.LogWarning("GaiaAudioZone : Invalid track index selected, selecting first track instead!"); m_selectedTrackIndex = 0; } //Set current clip m_currentAudioItem = m_audioList[m_selectedTrackIndex]; m_audioSource.clip = m_currentAudioItem.m_audioClip; //Setup times for fades, tracks etc m_timeWhenTrackStarted = Time.time; m_timeWhenFadeInEnds = m_timeWhenTrackStarted + m_currentAudioItem.m_fadeInTime; m_timeWhenFadeOutBegins = m_timeWhenTrackStarted + m_currentAudioItem.m_audioClip.length - m_currentAudioItem.m_fadeOutTime; m_timeWhenFadeOutEnds = m_timeWhenTrackStarted + m_currentAudioItem.m_audioClip.length; m_timeWhenNextTrackStarts = m_timeWhenFadeOutEnds + Random.Range(m_minimumBreakTime, m_maximumBreakTime); //Set initial volume if (m_currentAudioItem.m_fadeInTime == 0) { m_currentZoneVolume = m_currentAudioItem.m_volume; } else { m_currentZoneVolume = 0f; } //Start playing m_audioSource.Play(); m_audioIsPlaying = true; //Tell world if (m_showDebug) { Debug.Log("Playing " + m_audioSource.clip + " playstate " + m_audioSource.isPlaying); } } /// /// Check to see if the zone is in range of the position given - if in range then it should become active. /// Uses sqr magnitude for efficiency. /// /// Position to check /// True if the zone is deemed to be in range. public bool InRange(Vector3 position) { if (m_isGlobalAudioSource) { return true; } if ((transform.position - position).sqrMagnitude <= m_zoneRadiusSqr) { return true; } else { return false; } } /// /// Stop the currently playing track /// public void Stop() { if (m_audioSource != null) { m_audioSource.Stop(); m_audioSource.volume = 0f; } m_currentZoneVolume = 0f; m_audioIsPlaying = false; } public void Activate() { if (m_audioZoneState == AudioZoneState.Inactive) { m_audioZoneState = AudioZoneState.Active; PlayNextRandomTrack(); } else if (m_audioZoneState == AudioZoneState.BecomingInactive) { m_audioZoneState = AudioZoneState.Active; m_timeWhenDeactivating = 0f; } } /// /// Flag the audiozone for deactivation in 10 seconds - we use a delay for when people are on the border of a zone /// to stop weird sound crossovers /// public void FlagForDeactivation() { m_audioZoneState = AudioZoneState.BecomingInactive; m_timeWhenDeactivating = Time.time + m_deactivationTime; } /// /// Whether or not it can be deactivated now /// /// public bool CanDeactivateNow() { if (Time.time > m_timeWhenDeactivating) { return true; } return false; } /// /// Deactivate this audio zone /// public void Deactivate() { m_audioZoneState = AudioZoneState.Inactive; Stop(); } #endregion } }