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