|
@@ -0,0 +1,218 @@
|
|
|
|
+using System.Collections;
|
|
|
|
+using System.Collections.Generic;
|
|
|
|
+using UnityEngine;
|
|
|
|
+using UnityEngine.Audio;
|
|
|
|
+using QFSW.MOP2;
|
|
|
|
+using UnityEngine.SceneManagement;
|
|
|
|
+using Sirenix.OdinInspector;
|
|
|
|
+
|
|
|
|
+namespace KairoEngine.SFX
|
|
|
|
+{
|
|
|
|
+
|
|
|
|
+ // Todo: Remove playing SFX on scene change
|
|
|
|
+
|
|
|
|
+ public class SoundEmitter : MonoBehaviour
|
|
|
|
+ {
|
|
|
|
+ public SphereCollider trigger;
|
|
|
|
+ public AudioSource audioSource;
|
|
|
|
+
|
|
|
|
+ [ReadOnly]
|
|
|
|
+ public AudioClip audioClip;
|
|
|
|
+ public SFXClip sfx;
|
|
|
|
+
|
|
|
|
+ public string soundEmitterPrefabPool = "";
|
|
|
|
+
|
|
|
|
+ private float fadeInTime;
|
|
|
|
+ private float fadeOutTime;
|
|
|
|
+ private Coroutine fadeOutCoroutine;
|
|
|
|
+
|
|
|
|
+ private bool done = false;
|
|
|
|
+
|
|
|
|
+ /// <summary>
|
|
|
|
+ /// play a sound at a location in the world using an Audio Clip.
|
|
|
|
+ /// </summary>
|
|
|
|
+ /// <param name="audioClip">The audio clip to be played</param>
|
|
|
|
+ /// <param name="location">The Vector3 position to play the sound in the world</param>
|
|
|
|
+ /// <param name="volume">Audio clip volume</param>
|
|
|
|
+ /// <param name="radius">Radius for SoundSensors</param>
|
|
|
|
+ /// <param name="audioMixerGroup">Audio Mixer Channel</param>
|
|
|
|
+ /// <param name="world">Trigger SoundSensors in world?</param>
|
|
|
|
+ public void Initialize(AudioClip audioClip, Vector3 location, float volume, float radius, AudioMixerGroup audioMixerGroup, bool world = true)
|
|
|
|
+ {
|
|
|
|
+ this.audioClip = audioClip;
|
|
|
|
+ this.trigger.radius = radius;
|
|
|
|
+ transform.position = location;
|
|
|
|
+ this.audioSource.volume = volume;
|
|
|
|
+ this.audioSource.outputAudioMixerGroup = audioMixerGroup;
|
|
|
|
+ this.audioSource.clip = this.audioClip;
|
|
|
|
+ this.done = false;
|
|
|
|
+ if(world == false)
|
|
|
|
+ {
|
|
|
|
+ trigger.enabled = false;
|
|
|
|
+ }
|
|
|
|
+ this.audioSource.Play();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /// <summary>
|
|
|
|
+ /// Play a sound at a location in the world using a SFX Clip.
|
|
|
|
+ /// </summary>
|
|
|
|
+ /// <param name="sfx">SFX Clip with audio clip and parameters.</param>
|
|
|
|
+ /// <param name="location">Vector3 representing the location to play the sound.</param>
|
|
|
|
+ public void Initialize(SFXClip sfx, Vector3 location)
|
|
|
|
+ {
|
|
|
|
+ this.sfx = sfx;
|
|
|
|
+ this.done = false;
|
|
|
|
+ // Audio Clip
|
|
|
|
+ if(sfx.sfxType == SFXClipType.Single)
|
|
|
|
+ {
|
|
|
|
+ // Single
|
|
|
|
+ this.audioClip = sfx.clip;
|
|
|
|
+ }
|
|
|
|
+ else if(sfx.sfxType == SFXClipType.RandomList)
|
|
|
|
+ {
|
|
|
|
+ // Random from list
|
|
|
|
+ this.audioClip = sfx.clips[Random.Range(0, sfx.clips.Count)];
|
|
|
|
+ }
|
|
|
|
+ this.audioSource.clip = this.audioClip;
|
|
|
|
+ // Sound Location
|
|
|
|
+ transform.position = location;
|
|
|
|
+ // Audio Mixer Channel
|
|
|
|
+ this.audioSource.outputAudioMixerGroup = sfx.audioMixerChannel;
|
|
|
|
+ // Loop
|
|
|
|
+ if(sfx.sfxType == SFXClipType.Single) this.audioSource.loop = sfx.loop;
|
|
|
|
+ // Volume
|
|
|
|
+ this.audioSource.volume = sfx.volume + Random.Range(-sfx.volumeVariation, sfx.volumeVariation);
|
|
|
|
+ // Pitch
|
|
|
|
+ this.audioSource.pitch = sfx.pitch + Random.Range(-sfx.pitchVariation, sfx.pitchVariation);
|
|
|
|
+ // Spatial or stereo audio
|
|
|
|
+ if(sfx.soundSpace != SFXSpace.Stereo)
|
|
|
|
+ {
|
|
|
|
+ this.audioSource.spatialize = true;
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ this.audioSource.spatialize = false;
|
|
|
|
+ this.audioSource.spatialBlend = 0;
|
|
|
|
+ }
|
|
|
|
+ // Pan
|
|
|
|
+ if(sfx.soundSpace != SFXSpace.GameWorld)
|
|
|
|
+ {
|
|
|
|
+ // Stereo audio Pan
|
|
|
|
+ this.audioSource.panStereo = sfx.pan + Random.Range(-sfx.panVariation, sfx.panVariation);
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ // 3D audio has no Pan
|
|
|
|
+ this.audioSource.panStereo = 0;
|
|
|
|
+ }
|
|
|
|
+ // Spatial Blend
|
|
|
|
+ if(sfx.soundSpace == SFXSpace.Mix)
|
|
|
|
+ {
|
|
|
|
+ this.audioSource.spatialBlend = sfx.spatialBlend + Random.Range(-sfx.spatialBlendVariation, sfx.spatialBlendVariation);
|
|
|
|
+ }
|
|
|
|
+ // Set Distance
|
|
|
|
+ if(sfx.soundSpace == SFXSpace.GameWorld || sfx.soundSpace == SFXSpace.Mix)
|
|
|
|
+ {
|
|
|
|
+ this.audioSource.minDistance = sfx.minDistance;
|
|
|
|
+ this.audioSource.maxDistance = sfx.maxDistance;
|
|
|
|
+ }
|
|
|
|
+ // Use world sensor
|
|
|
|
+ if(sfx.sensorDetection == true)
|
|
|
|
+ {
|
|
|
|
+ trigger.enabled = true;
|
|
|
|
+ this.trigger.radius = sfx.soundRadius;
|
|
|
|
+ }
|
|
|
|
+ else trigger.enabled = true;
|
|
|
|
+ // Set Fade In
|
|
|
|
+ if(sfx.fadeIn)
|
|
|
|
+ {
|
|
|
|
+ this.audioSource.volume = 0f;
|
|
|
|
+ this.fadeInTime = 0f;
|
|
|
|
+ StartCoroutine(FadeIn());
|
|
|
|
+ }
|
|
|
|
+ // Set Fade out
|
|
|
|
+ if(sfx.fadeOut)
|
|
|
|
+ {
|
|
|
|
+ fadeOutTime = 0f;
|
|
|
|
+ fadeOutCoroutine = StartCoroutine(FadeOut());
|
|
|
|
+ }
|
|
|
|
+ // Set Remove on scene change
|
|
|
|
+ // ! Turn this on when dependencies are solved
|
|
|
|
+ //if(sfx.removeOnSceneChange) TransitionEvents.OnChangeScene += OnSceneChanged;
|
|
|
|
+ // Trim and Play the audio clip
|
|
|
|
+ if(sfx.trim) this.audioSource.time = sfx.trimStartDuration;
|
|
|
|
+ this.audioSource.Play();
|
|
|
|
+ if(sfx.trim) this.audioSource.SetScheduledEndTime(AudioSettings.dspTime +(sfx.trimEndDuration - sfx.trimStartDuration));
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ void Update()
|
|
|
|
+ {
|
|
|
|
+ if(done == true) return;
|
|
|
|
+ // Destroy this Gameobject when the sound is done playing
|
|
|
|
+ if(audioSource.isPlaying == false)
|
|
|
|
+ {
|
|
|
|
+ if(sfx.loop && sfx.sfxType == SFXClipType.RandomList)
|
|
|
|
+ {
|
|
|
|
+ Initialize(sfx, transform.position);
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ Remove();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private void Remove()
|
|
|
|
+ {
|
|
|
|
+ SoundController.soundEmitters.Remove(this);
|
|
|
|
+ // ! Enable when dependencies are resolved
|
|
|
|
+ //if(sfx.removeOnSceneChange) TransitionEvents.OnChangeScene -= OnSceneChanged;
|
|
|
|
+ sfx = null;
|
|
|
|
+ if(MasterObjectPooler.Instance != null) MasterObjectPooler.Instance.GetPool(soundEmitterPrefabPool).Release(this.gameObject);
|
|
|
|
+ else Destroy(this.gameObject);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ IEnumerator FadeIn()
|
|
|
|
+ {
|
|
|
|
+ float start = Time.realtimeSinceStartup;
|
|
|
|
+ while(Time.realtimeSinceStartup <= (start + (sfx.fadeInDuration)))
|
|
|
|
+ {
|
|
|
|
+ fadeInTime = (Time.realtimeSinceStartup - start);
|
|
|
|
+ this.audioSource.volume = Mathf.Lerp(0, 1, fadeInTime/sfx.fadeInDuration);
|
|
|
|
+ yield return null;
|
|
|
|
+ }
|
|
|
|
+ this.audioSource.volume = 1;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ IEnumerator FadeOut(bool wait = true, bool remove = false, float time = 0)
|
|
|
|
+ {
|
|
|
|
+ fadeOutTime = 0f;
|
|
|
|
+ if(time == 0) time = sfx.fadeOutDuration;
|
|
|
|
+ if(wait) yield return new WaitForSecondsRealtime((this.audioSource.clip.length - this.audioSource.time) - time);
|
|
|
|
+ if(sfx == null) yield return 0;
|
|
|
|
+ float start = Time.realtimeSinceStartup;
|
|
|
|
+ while(Time.realtimeSinceStartup < (start + (time)))
|
|
|
|
+ {
|
|
|
|
+ fadeOutTime = (Time.realtimeSinceStartup - start);
|
|
|
|
+ this.audioSource.volume = Mathf.Lerp(1, 0, fadeOutTime/time);
|
|
|
|
+ yield return null;
|
|
|
|
+ }
|
|
|
|
+ this.audioSource.volume = 0;
|
|
|
|
+ if(remove) Remove();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // ! Enable when dependencies are solved
|
|
|
|
+ /*
|
|
|
|
+ private void OnSceneChanged(SceneChangeData sceneData, float time)
|
|
|
|
+ {
|
|
|
|
+ StopCoroutine(fadeOutCoroutine);
|
|
|
|
+ done = true;
|
|
|
|
+ if(sfx == null) return;
|
|
|
|
+ if(sfx.fadeOut) StartCoroutine(FadeOut(false, true, time));
|
|
|
|
+ else Remove();
|
|
|
|
+ }
|
|
|
|
+ */
|
|
|
|
+ }
|
|
|
|
+}
|