mvaganov

Noisy.cs

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