mvaganov

Noisy.cs

Feb 8th, 2018
325
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4.  
  5. // author: mvaganov@hotmail.com
  6. // license: Copyfree, public domain.
  7. // latest version at: https://pastebin.com/raw/hGU8et8s -- added: .Global.AllNoiseNames (2020/02/26)
  8. public class Noisy : MonoBehaviour {
  9.  
  10.     public Noise[] noises = new Noise[1];
  11.  
  12.     /// <summary>used for getting a random noise from a list of noises, without getting the one that was just played</summary>
  13.     /// <param name="minInclusive"></param>
  14.     /// <param name="maxExclusive"></param>
  15.     /// <param name="andNot"></param>
  16.     /// <returns></returns>
  17.     public static int RandomNumberThatIsnt(int minInclusive, int maxExclusive, int andNot = -1) {
  18.         int index = minInclusive;
  19.         if (maxExclusive - minInclusive > 1) {
  20.             if (andNot >= minInclusive) {
  21.                 index = Random.Range (minInclusive, maxExclusive-1);
  22.                 if (index >= andNot) {
  23.                     index++;
  24.                 }
  25.             }
  26.             else {
  27.                 index = Random.Range (minInclusive, maxExclusive);
  28.             }
  29.         }
  30.         return index;
  31.     }
  32.  
  33.     [System.Serializable]
  34.     public class Noise {
  35.         [Tooltip("you can reference the 'sounds' list of another Noise by using it's name and leaving this Noise's 'sounds' as length zero")]
  36.         public string name;
  37.         [Tooltip("any one of these sounds count as the-sound-to-play (will be randomized at runtime)")]
  38.         public AudioClip[] sounds = new AudioClip[1];
  39.         [Range(0,1),Tooltip("to keep max volume, leave this at 0 (changes during runtime are fine, OnValidate)")]
  40.         public float volumeReduce = 0f;
  41.         [Tooltip("set the background music to this? (if this is set, none of the other checkboxes below matter)")]
  42.         public bool backgroundMusic = false;
  43.         [ContextMenuItem("advanced keypress settings... (create component)", "CreateOnKeyPress"), Tooltip("play as soon as object starts? (eg: ambient sound/music or instantiated objects with 'birth' sounds)")]
  44.         public bool playOnStart = false;
  45.         [Tooltip("play at max volume and consistent pitch, regardless of distance? (eg: volume changes by distance, doppler effect)")]
  46.         public bool is2D = false;
  47.         [Tooltip("sound should attach to this object and follow it as it moves? (eg: ambient sound/music/dialog that follows a moving agent)")]
  48.         public bool followsObject = false;
  49.         [Tooltip("start the sound again right after it ends? (eg: ambient sound/music)")]
  50.         public bool loop = false;
  51.         [Tooltip("stop the previous-Noisy-with-the-same-Name before playing another one? (eg: character dialog, UI feedback sounds, background music)")]
  52.         public bool onePlayAtATime = false;
  53.         [ContextMenuItem("advanced collision settings... (create component)", "CreateOnCollision"), Tooltip("play when this object collides with something? (eg: adding audio output to Rigidbody collision)")]
  54.         public bool onCollision = false;
  55.         [ContextMenuItem("advanced trigger settings... (create component)", "CreateOnTrigger"), Tooltip("play when an object enters this trigger? (eg: ambient noise, reactions to movement through space)")]
  56.         public bool onTrigger = false;
  57.         [HideInInspector]
  58.         /// The last noise played, used to prevent duplicate repetition
  59.         public int lastNoisePlayed = -1;
  60.         [HideInInspector]
  61.         public AudioSource activeAudioSource;
  62.  
  63.         /// Plays the sound. Cannot have the sound follow the object because a position is given, not a transform
  64.         /// <returns>The sound.</returns>
  65.         /// <param name="p">where the noise is</param>
  66.         public AudioSource PlaySound(Vector3 p) {
  67.             if(!backgroundMusic) {
  68.                 activeAudioSource = Noisy.PlaySound(GetSoundToPlay(), p, !is2D, loop, onePlayAtATime ? name : null, 1 - volumeReduce);
  69.             } else {
  70.                 activeAudioSource = Noisy.PlayBackgroundMusic(GetSoundToPlay(), 1 - volumeReduce);
  71.             }
  72.             return activeAudioSource;
  73.         }
  74.  
  75.         public AudioSource PlaySound(Transform t) {
  76.             activeAudioSource = PlaySound (t.position);
  77.             if(followsObject) { activeAudioSource.transform.SetParent (t); }
  78.             return activeAudioSource;
  79.         }
  80.  
  81.         public AudioClip GetSoundToPlay() { return GetSoundToPlay (ref lastNoisePlayed); }
  82.  
  83.         public AudioClip GetSoundToPlay(ref int indexNotToPlayNext) {
  84.             if (sounds != null && sounds.Length > 0) {
  85.                 indexNotToPlayNext = RandomNumberThatIsnt(0, sounds.Length, indexNotToPlayNext);
  86.                 return sounds [indexNotToPlayNext];
  87.             }
  88.             return null;
  89.         }
  90.  
  91.         /// <summary>comparer, used to sort Noise objects into the list</summary>
  92.         public class Comparer : IComparer<Noise> {
  93.             public int Compare(Noise x, Noise y) { return x.name.CompareTo(y.name); }
  94.         }
  95.         public static Comparer compare = new Comparer();
  96.     }
  97.  
  98.     void Awake() {
  99.         // sort noises for faster access later
  100.         System.Array.Sort(noises, Noise.compare);
  101.         // add all named noises to a single static (global) listing, for easy scripted access later
  102.         for (int i = 0; i < noises.Length; ++i) {
  103.             if (noises [i].name != null && noises [i].name.Length > 0) {
  104.                 int index = Global.allNoises.BinarySearch (noises [i], Noise.compare);
  105.                 bool isAlreadyKnown = index >= 0;
  106.                 bool hasSoundsFilledOut = noises [i].sounds != null && noises [i].sounds.Length > 0;
  107.                 if (!isAlreadyKnown && hasSoundsFilledOut) {
  108.                     Global.allNoises.Insert (~index, noises [i]);
  109.                 }
  110.             }
  111.         }
  112.     }
  113.  
  114.     /// plays the first sound in the noises list
  115.     public void DoActivateTrigger() {
  116.         if(noises.Length > 0 && noises[0] != null) { noises[0].PlaySound(transform.position); }
  117.     }
  118.  
  119.     /// plays the first sound in the noises list
  120.     public void DoDeactivateTrigger() {
  121.         if(noises.Length > 0 && noises[0] != null && noises[0].activeAudioSource != null) {
  122.             noises[0].activeAudioSource.Stop();
  123.         }
  124.     }
  125.  
  126.     /// returns the Noise that was created someplace in the scene with the given name
  127.     /// <returns>The sound.</returns>
  128.     /// <param name="name">Name.</param>
  129.     public static Noise GetSound(string name) {
  130.         searched.name = name;
  131.         int i = Global.allNoises.BinarySearch (searched, Noise.compare);
  132.         if (i >= 0) { return Global.allNoises [i]; }
  133.         Debug.LogError("Could not find noise named \"" + name + "\". Valid names include:\n\""
  134.             + string.Join("\", \"", Global.AllNoiseNames)+"\"");
  135.         return null;
  136.     }
  137.  
  138.     void Start() {
  139.         Noise n;
  140.         for (int i = 0; i < noises.Length; ++i) {
  141.             n = noises [i];
  142.             // use the global noise catalog if this Noisy hasn't filled in it's named noise.
  143.             if (n.sounds == null || n.sounds.Length == 0) {
  144.                 Noise existing = GetSound(n.name);
  145.                 if(existing != null) { n.sounds = existing.sounds; }
  146.             }
  147.             if (n.playOnStart || n.backgroundMusic) {
  148.                 n.PlaySound (transform.position);
  149.             }
  150.             if (n.onCollision) {
  151.                 Noisy.OnCollisionAdvancedSettings oc = CreateHandler<OnCollisionAdvancedSettings> (n.name);
  152.                 oc.noise = n;
  153.             }
  154.             if (n.onTrigger) {
  155.                 Noisy.OnTriggerAdvancedSettings oc = CreateHandler<OnTriggerAdvancedSettings> (n.name);
  156.                 oc.noise = n;
  157.             }
  158.         }
  159.     }
  160.  
  161.     private static Noise searched = new Noise();
  162.     /// Plays the named sound (as a 2D sound, full volume)
  163.     public static AudioSource PlaySound(string name) {
  164.         Noise n = GetSound(name);
  165.         return (n != null) ? Noisy.PlaySound (n.GetSoundToPlay()) : null;
  166.     }
  167.  
  168.     public static AudioSource PlaySound(string name, Vector3 p) {
  169.         Noise n = GetSound(name);
  170.         return (n != null) ? n.PlaySound (p) : null;
  171.     }
  172.  
  173.     /// <param name="name"></param>
  174.     /// <param name="t">where to parent the noise (important for 3D sounds)</param>
  175.     /// <returns></returns>
  176.     public static AudioSource PlaySound(string name, Transform t) {
  177.         Noise n = GetSound(name);
  178.         return (n != null) ? n.PlaySound (t) : null;
  179.     }
  180.  
  181.     private static Dictionary<string, AudioSource> s_soundsByCategory = new Dictionary<string, AudioSource>();
  182.     /// Plays the sound.
  183.     /// <returns>Component where the sound is playing from.</returns>
  184.     /// <param name="noise">Noise. returns early if <c>null</c></param>
  185.     /// <param name="p">P. the location to play from. If is3D is false, this parameter is pretty useless.</param>
  186.     /// <param name="is3D">If set to false, sound plays without considering 3D-ness (full volume from anywhere).</param>
  187.     /// <param name="isLooped">If set to <c>true</c> is looped.</param>
  188.     /// <param name="soundCategory">If non-null, prevents multiple sounds with the same soundCategory from playing simultaneously. If null, each instance of the sound will be independent.</param>
  189.     /// <param name="volume"></param>
  190.     public static AudioSource PlaySound(AudioClip noise,  Vector3 p = default(Vector3), bool is3D = false, bool isLooped = false, string soundCategory = null, float volume = 1f) {
  191.         if (noise == null) return null;
  192.         AudioSource asrc = null;
  193.         if (soundCategory != null && soundCategory.Length > 0) {
  194.             s_soundsByCategory.TryGetValue(soundCategory, out asrc);
  195.         }
  196.         if (asrc == null) {
  197.             string noiseName = (soundCategory != null) ? "("+soundCategory+")" : "<Noise: "+noise.name+">";
  198.             GameObject go = new GameObject (noiseName);
  199.             asrc = go.AddComponent<AudioSource> ();
  200.             if (soundCategory != null) {
  201.                 s_soundsByCategory[soundCategory] = asrc;
  202.             }
  203.             asrc.transform.SetParent (Global.Instance ().transform);
  204.         }
  205.         else {
  206.             asrc.Stop ();
  207.         }
  208.         asrc.clip = noise;
  209.         asrc.spatialBlend = is3D ? 1 : 0;
  210.         asrc.transform.position = p;
  211.         if (soundCategory == null && !isLooped) {
  212.             Destroy (asrc.gameObject, noise.length); // destroy the noise after it is done playing if not looped
  213.         }
  214.         asrc.loop = isLooped;
  215.         if(volume != asrc.volume) { asrc.volume = volume; }
  216.         asrc.Play ();
  217.         return asrc;
  218.     }
  219.  
  220.     /// convenience method to play background music
  221.     /// <returns>The background music's AudioSource.</returns>
  222.     /// <param name="song">Song.</param>
  223.     /// <param name="volume">Volume.</param>
  224.     public static AudioSource PlayBackgroundMusic(AudioClip song, float volume) {
  225.         AudioSource bgMusicPlayer = PlaySound (song, Vector3.zero, false, true, "{background music}", volume);
  226.         return bgMusicPlayer;
  227.     }
  228.  
  229.     /// creates an accessible listing to all sounds being used by Noisy, visible in the hierarchy & inspector. also handles some static logic.
  230.     public class Global : MonoBehaviour {
  231.         /// All Noise objects with unique names and actual data in the 'sounds' array.
  232.         public static List<Noise> allNoises = new List<Noise> ();
  233.  
  234.         private static Noisy.Global instance;
  235.         public static Noisy.Global Instance() {
  236.             if(instance == null) {
  237.                 if((instance = FindObjectOfType(typeof(Noisy.Global)) as Noisy.Global) == null) {
  238.                     GameObject g = new GameObject("<" + typeof(Noisy.Global).Name + ">");
  239.                     instance = g.AddComponent<Noisy.Global>();
  240.                 }
  241.             }
  242.             return instance;
  243.         }
  244.         /// <summary>local members alias showing all noises (can be seen in the inspector, static members cannot)</summary>
  245.         public List<Noise> allTheNoises;
  246.  
  247.         /// <summary>the names of all of the noises in a List</summary>
  248.         public static List<string> AllNoiseNames { get { return allNoises.ConvertAll(n => n.name); } }
  249.  
  250.         void Start() { allTheNoises = allNoises; }
  251.     }
  252.  
  253.     TYPE CreateHandler<TYPE>(string nameOfNoise) where TYPE : NoisyHandler {
  254.         if (name != null) {
  255.             TYPE[] triggers = GetComponents<TYPE> ();
  256.             for (int i = 0; i < triggers.Length; ++i) {
  257.                 if (triggers [i].advancedNoiseOverride == nameOfNoise)
  258.                     return triggers[i];
  259.             }
  260.         }
  261.         return gameObject.AddComponent<TYPE> ();
  262.     }
  263.  
  264.     void CreateOnTrigger() { CreateHandler<OnTriggerAdvancedSettings> (null); }
  265.     void CreateOnCollision() { CreateHandler<OnCollisionAdvancedSettings> (null); }
  266.     void CreateOnKeyPress() { CreateHandler<OnKeyPressAdvancedSettings> (null); }
  267.  
  268.     public class NoisyHandler : MonoBehaviour {
  269.         [Tooltip("if this is set, Noise (below) will be overwritten at runtime by a Noise with this name")]
  270.         public string advancedNoiseOverride;
  271.         public Noise noise;
  272.         [Tooltip("remove this handler after playing the sound once. (eg: one-time acknowledgement)")]
  273.         public bool justOnce;
  274.         protected void NoisyHandlerStart() {
  275.             if (advancedNoiseOverride != null && advancedNoiseOverride.Length > 0) {
  276.                 noise = Noisy.GetSound (advancedNoiseOverride);
  277.             }
  278.         }
  279.         void Start(){ NoisyHandlerStart (); }
  280.     }
  281.  
  282.     public class NoisyObjectInteractHandler : NoisyHandler {
  283.         [Tooltip("identify which GameObjects can trigger this. (eg: only player, only certain item)")]
  284.         public string triggersOnlyByTag;
  285.         public bool IsValidTrigger(GameObject go) {
  286.             return triggersOnlyByTag == null || triggersOnlyByTag.Length == 0 || go.tag == triggersOnlyByTag;
  287.         }
  288.     }
  289.  
  290.     public class OnTriggerAdvancedSettings : NoisyObjectInteractHandler {
  291.         void OnTriggerEnter(Collider c) {
  292.             if (!IsValidTrigger (c.gameObject)) return;
  293.             if (noise.followsObject) {
  294.                 noise.PlaySound (transform);
  295.                 if(justOnce) Destroy (this);
  296.             }
  297.             else {
  298.                 noise.PlaySound (c.transform.position);
  299.             }
  300.         }
  301.     }
  302.  
  303.     public class OnCollisionAdvancedSettings : NoisyObjectInteractHandler {
  304.         void OnCollisionEnter(Collision c) {
  305.             if (!IsValidTrigger (c.gameObject)) return;
  306.             if (noise.followsObject) {
  307.                 noise.PlaySound (transform);
  308.                 if(justOnce) Destroy (this);
  309.             }
  310.             else {
  311.                 noise.PlaySound (c.contacts[0].point);
  312.             }
  313.         }
  314.     }
  315.  
  316.     public class OnKeyPressAdvancedSettings : NoisyHandler {
  317.         public KeyCode key = KeyCode.None;
  318.         public enum KeyEvent {press, release, hold};
  319.         public KeyEvent eventType = KeyEvent.press;
  320.         public bool IsTriggered() {
  321.             switch (eventType) {
  322.             case KeyEvent.press:   return Input.GetKeyDown (key);
  323.             case KeyEvent.release: return Input.GetKeyUp (key);
  324.             case KeyEvent.hold:    return Input.GetKey (key);
  325.             }
  326.             return false;
  327.         }
  328.         void Start() {
  329.             NoisyHandlerStart ();
  330.             if (noise.sounds == null || noise.sounds.Length == 0) {
  331.                 Noisy n = GetComponent<Noisy> ();
  332.                 if (n != null && n.noises != null && n.noises.Length > 0) {
  333.                     this.noise = n.noises [0];
  334.                 }
  335.             }
  336.         }
  337.         void Update() {
  338.             if (IsTriggered()) {
  339.                 noise.PlaySound (transform);
  340.                 if(justOnce) Destroy (this);
  341.             }
  342.         }
  343.     }
  344.  
  345. #if UNITY_EDITOR
  346.     private void OnValidate() {
  347.         if(noises == null) return;
  348.         for(int i = 0; i < noises.Length; ++i) {
  349.             Noise n = noises[i];
  350.             if(n != null && n.activeAudioSource != null) { n.activeAudioSource.volume = 1 - n.volumeReduce; }
  351.         }
  352.     }
  353. #endif
  354. }
RAW Paste Data