Search Unity

C# Randomly assign audio to GameObjects - Play (if not playing) when MouseEnter

Discussion in 'Scripting' started by bigboybifdick, Oct 31, 2014.

  1. bigboybifdick

    bigboybifdick

    Joined:
    Feb 8, 2014
    Posts:
    60
    Hi,

    I've spent 2 hours trying to resolve this, and i feel very very dumb... This is really simple, and i'm sure there is an easy way ( iam not aware of -_- ) to do it.

    OnMouseEnter i play an audioclip. Obviously i want the clip to be played only if it's not already playing...

    I'v tried with bools (OnMousEnter/Exit) + a couroutine (Waitforseconds(myclip.length) but that doesn't work. Don't ask me why... I went crazy and erased all the code.

    The last thing i tried was :
    Code (CSharp):
    1. void OnMouseEnter (){
    2.         mySound = myCamera.GetComponent<GameManager> ().music4;
    3.         if(!audio.isPlaying){
    4.             audio.PlayOneShot (mySound);
    5.             Debug.Log("WTFFFFF");
    6.         }
    7.  
    What am i missing ?
     
  2. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    isPlaying checks if the clip is playing. PlayOneShot does not assign the clip. Assign the clip and then use Play instead.

    Code (csharp):
    1.  
    2. if (!audio.isPlaying)
    3. {
    4.     audio.clip = mySound;
    5.     audio.Play();
    6. }
    7.  
     
    bigboybifdick likes this.
  3. bigboybifdick

    bigboybifdick

    Joined:
    Feb 8, 2014
    Posts:
    60
    Hey,

    Thank you for the answer. I guess i get it...
    The problem i have now is that other obejcts are still able to play sound...

    So i guess my script says something like "If an audio is being played by the gameobject you are related, dont play it again". What i need is more 'If an object with the tag XX is already playing a sound, dont play audio'. Can i do that ?

    Thanks !
     
  4. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    If all of those objects have the same script on them then the easiest way to do it would be to have a private static boolean field that is flipped and then check that instead of audio.isPlaying

    Code (csharp):
    1.  
    2. private static bool isPlaying;
    3.  
    4. public void PlaySound()
    5. {
    6.     if (!isPlaying)
    7.     {
    8.         audio.clip = mySound;
    9.         audio.Play();
    10.         isPlaying = true;
    11.         StartCoroutine(WaitFlip());
    12.     }
    13. }
    14.  
    15. IEnumerator WaitFlip()
    16. {
    17.     yield return new WaitForSeconds(audio.clip.length);
    18.     isPlaying = true;
    19. }
    20.  
     
    bigboybifdick likes this.
  5. bigboybifdick

    bigboybifdick

    Joined:
    Feb 8, 2014
    Posts:
    60
    Hey,

    I guess you meant :
    Code (CSharp):
    1. IEnumerator WaitFlip()
    2. {
    3.     yield return new WaitForSeconds(audio.clip.length);
    4.     isPlaying = false;
    5. }
    6.  
    + I thought variables had to be public to be reachable from other scripts ?

    It seems that i cannot use GetComponent with FindGameObjectsWithTag.

    Code (CSharp):
    1. MySoundIsPlaying = GameObject.FindGameObjectsWithTag("Planets").get...
    Thanks a lot for your help
     
    Last edited: Nov 3, 2014
  6. Cpt Chuckles

    Cpt Chuckles

    Joined:
    Dec 31, 2012
    Posts:
    86
    no you can't use GetComponent with FindGameObjectsWithTag because the plural find function returns an enumeration of game objects
    you would just have to do something like this
    Code (csharp):
    1. foreach(GameObject go in FindGameObjectsWithTag("tag"))
    2. {
    3.     var thething = go.GetComponent<thing>();
    4. }
     
    bigboybifdick likes this.
  7. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    My bad :)

    It does need to be public if you want to get it from another script - my code doesn't require that. You would call PlaySound (which is public) which would automatically start the coroutine and flip it back to false after the clip was done playing. No need to do anything else. Making it static means the state is shared across every instance of that script.
     
  8. bigboybifdick

    bigboybifdick

    Joined:
    Feb 8, 2014
    Posts:
    60
    Hey,

    Thank you guys.

    @KelsoMRK : Ok i get it. Does that work even if the static variable is in different scripts, on different objects ? :p

    I'm pretty sure this is why it's not working. My objects don't use the same script:

    Object 1 = mysound_1.cs
    Object 2 = mysound_2.cs

    Sounds are randomly created in my GameManager script, then all my gameobjects (mysound1, mysound2 etc.) check that GameManager script to know which sound they should use.

    That's why they are not using the same mysound script... Maybe i could use the same script if my sounds were assigned to the objects from the GameManager script, right ?

    If not, do i have better solution than checking every single version of the script ?... (that's a problem cause i'll have more than 50 objects with audio...)
     
    Last edited: Nov 3, 2014
  9. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    If the sounds are created in a central location then why are you using different code to play them? Typically, when you end up with things like MyClass1 and MyClass2 it's a red flag that you've designed something poorly :)

    You could simply modify the PlaySound method to get the sound from GameManager or take an AudioClip as an argument.
     
    bigboybifdick likes this.
  10. bigboybifdick

    bigboybifdick

    Joined:
    Feb 8, 2014
    Posts:
    60
    Yep my code is a bit messy right now...
    I was to excited to get things working i guess :)

    Ok, so your advice is :

    1- GameManager.cs That creates sounds : Sound1, Sound2
    2 - Mysound.cs (same script on 2 gameobjects)

    I'm sorry but i can't see how i'm going to assign different sounds with the same script to different objects...

    Here is my simplified GameManager Script :

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class GameManager1 : MonoBehaviour {
    5.    
    6.     public AudioClip[] audioArray;
    7.     public AudioClip[] MusicArray;
    8.     public AudioClip music1;
    9.     public AudioClip music2;
    10.     public int SoundChoice1;
    11.     public int SoundChoice2;
    12.     public int PlanetChoice;
    13.  
    14.     // Use this for initialization
    15.     void Start () {
    16.      
    17.         // As i'm using arrays, this is the random range to define the arguments
    18.         // Sound1
    19.         SoundChoice1 = Random.Range (0, audioArray.Length);
    20.    
    21.         // Sound2
    22.         do{
    23.             SoundChoice2 = Random.Range (0, audioArray.Length);
    24.         }while (SoundChoice2 == SoundChoice1);
    25.  
    26.         // My AudioArray
    27.         audioArray =  new AudioClip[]{
    28.             (AudioClip)Resources.Load("sound1"),
    29.             (AudioClip)Resources.Load("sound2")
    30.            
    31.         };
    32.        
    33.         // initialize musics
    34.         music1 = audioArray [SoundChoice1];
    35.         music2 = audioArray [SoundChoice2];
    36.              
    37.     }
    38.    
    39. }
    So what should i do from the MySound.cs ?
    All my objects need to have a unique sound... I'm a bit lost here...
     
  11. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    If they just have to be unique and not random then you can just assign them via Inspector manually for each instance. Otherwise I would do something like this

    Code (csharp):
    1.  
    2. public class GameManager : MonoBehaviour
    3. {
    4.     // why are you using Resources.Load?
    5.     // just assign them all via the Inspector
    6.     [SerializeField]
    7.     private AudioClip[] audioClips;
    8.  
    9.     private List<int> freeClips = new List<int>();
    10.  
    11.     void Start()
    12.     {
    13.         for (int i = 0; i < audioClips.length; i++)
    14.         {
    15.             freeClips.Add(i);
    16.         }
    17.         // you could shuffle the list here to make it more "random"
    18.     }
    19.  
    20.     public AudioClip GetNewClip()
    21.     {
    22.         int rand = Random.Range(0, freeClips.Count);
    23.         AudioClip clip = audioClips[freeClips[rand]];
    24.         freeClips.RemoveAt(rand);
    25.         return clip;
    26.     }
    27. }
    28.  
    29. public class SoundPlayer : MonoBehaviour
    30. {
    31.     private static bool isPlaying;
    32.  
    33.     [SerializeField]
    34.     private GameManager gameManager;
    35.  
    36.     public void PlaySound()
    37.     {
    38.         if (!isPlaying)
    39.         {
    40.             if (audio.clip == null)
    41.                 audio.clip = gameManager.GetNewClip();
    42.              
    43.             audio.Play();
    44.             isPlaying = true;
    45.             StartCoroutine(Wait());
    46.         }
    47.     }
    48.  
    49.     IEnumerator Wait()
    50.     {
    51.         yield return new WaitForSeconds(audio.clip.length);
    52.         isPlaying = false;
    53.     }
    54. }
    55.  
     
    Last edited: Nov 3, 2014
    bigboybifdick likes this.
  12. bigboybifdick

    bigboybifdick

    Joined:
    Feb 8, 2014
    Posts:
    60
    Hi,

    Thanks a lot. I tried with your code but i have a OutOfRange error...
    I have debug.logged everything ...

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. // Adding .Generic (it seems that lists dont work without it)
    4. using System.Collections.Generic;
    5.  
    6. public class GameManager1 : MonoBehaviour
    7. {
    8.     [SerializeField]
    9.     public AudioClip[] audioClips;
    10.     private List<int> freeClips = new List<int>();
    11.    
    12.     void Start()
    13.     {
    14. //Start with i = 0 / Length = 2 //
    15.         for (int i = 0; i < audioClips.Length; i++)
    16.         {  
    17.  
    18.             freeClips.Add(i);
    19.        
    20.         }
    21.  
    22.         }
    23.    
    24.     public AudioClip GetNewClip()
    25.     {
    26. // First time freeClips.Count = 0 ? Then i = 1 ?
    27.         int rand = Random.Range(0, freeClips.Count);      
    28.         AudioClip clip = audioClips[rand];
    29.         freeClips.RemoveAt(rand);
    30.        
    31.         return clip;
    32.     }
    33.  
    34. }
    And my SoundPlayer script :

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4.  
    5. public class SoundPlayer : MonoBehaviour
    6. {[SerializeField]
    7.     private static bool isPlaying;
    8.  
    9.    
    10.     public void Start()
    11.     {
    12.  
    13.             if (audio.clip == null)
    14.                 audio.clip = GameObject.Find("Camera").GetComponent<GameManager1>().GetNewClip();
    15.    
    16.             audio.Play();
    17.             isPlaying = true;
    18.             StartCoroutine(Wait());
    19.        
    20.     }
    21.    
    22.     IEnumerator Wait()
    23.     {
    24.         yield return new WaitForSeconds(audio.clip.length);
    25.         isPlaying = false;
    26.     }
    27. }
    I'm definitely missing something... I've read it a lot of time now but cant see what is wrong.
     
  13. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    So you can't serialize a static field. I also don't understand why you're putting your play code in Start. You also missed the part where I took the value of freeClips at rand and not the value of audioClips at rand. freeClips is meant to store a list of indices into audioClips that haven't been used yet.

    Other than that, it should work assuming you populated your audio clip array correctly. That being said - I didn't test it :)
     
    bigboybifdick likes this.
  14. bigboybifdick

    bigboybifdick

    Joined:
    Feb 8, 2014
    Posts:
    60
    Oh sorry, my bad !
    The serialized static variable was a mistake... -_-

    Ok, so static variables are made for sharing the value of variables/classes/functions between different scripts. But not sure i see why i can't serialize a static field ?

    The code seems ok to me. But i still have this OutOfRange error again.
    For me the error comes from the GameManager script, and the rand int.

    I tried to Debug.Log (clip) and its null.

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4.  
    5. public class GameManager1 : MonoBehaviour
    6. {
    7.     private int rand;
    8.     [SerializeField]
    9.     public AudioClip[] audioClips;
    10.     private AudioClip clip;
    11.  
    12.     private List<int> freeClips = new List<int>();
    13.  
    14.     void Start()
    15.     {
    16.  
    17.  
    18.         // Debug.Log(audioClips.Length) = 2
    19.         for (int i = 0; i < audioClips.Length; i++)
    20.         {
    21.  
    22.             freeClips.Add(i);
    23.             //Debug.Log (freeClips.Count); = 2
    24.         }
    25.         //Debug.Log (freeClips[rand]);
    26.  
    27.         }
    28.  
    29.     public AudioClip GetNewClip()
    30.     {
    31.         int rand = Random.Range(0, freeClips.Count);
    32.  
    33.         AudioClip clip = audioClips[freeClips[rand]];
    34.         //Debug.Log (freeClips[rand]);
    35.  
    36.         freeClips.RemoveAt(rand);
    37.         return clip;
    38.  
    39.  
    40.     }
    41.  
    42.     void Update(){
    43.  
    44. //Debug.Log (freeClips.Count); = 2
    45. //Debug.Log (audioClips[freeClips[rand]]);
    46. //Debug.Log (freeClips[rand]); = 0
    47. //Debug.Log (clip); //= Null
    48. //Debug.Log (audioClips[1]); //-> Ok -> sound1
    49. //Debug.Log (audioClips[0]);// -> Ok -> sound0
    50.  
    51.     }
    52.  
    53.  
    54. }
    This is my problem :

    Code (CSharp):
    1. //Debug.Log (freeClips[rand]); = 0
    2. //Debug.Log (freeClips.Count); = 2
    3. //Debug.Log (audioClips[freeClips[rand]]);
    4. //Debug.Log (freeClips[rand]); = 0
    5. //Debug.Log (clip); //= Null
    6. //Debug.Log (audioClips[1]); //-> Ok -> sound1
    7. //Debug.Log (audioClips[0]);// -> Ok -> sound0
    8.  
    9. It seems that audioClips[freeClips[rand]] is not returning 1 or 0.
    10.  
    11. But Debug.Log (freeClips.Count); = 2
    12.  
    13. So rand should be 1 or 0...?
     
    Last edited: Nov 4, 2014
  15. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    If you call that method more than twice then freeClips will be empty because it removes an index once it's been used. The whole goal was to make each clip random and unique. If that's not a requirement then just return a random index in audioClips.

    Also - if you're still calling the Play in Start it's possible that method is running before the GameManager Start and therefore nothing is initialized properly.
     
  16. bigboybifdick

    bigboybifdick

    Joined:
    Feb 8, 2014
    Posts:
    60
    I finally found what was wrong !!

    My two functions started at the same time. So i guess my sounds weren't assigned yet and the SoundPlayer couldn't find a clip.

    Changing Start() by Awake() solved it.

    This code works :

    Script to randomly assigned audioclips
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4.  
    5. public class GameManager1 : MonoBehaviour
    6. {
    7.     public AudioClip clip;
    8.     private int rand;
    9.     [SerializeField]
    10.     public AudioClip[] audioClips;
    11.  
    12.     private List<int> freeClips = new List<int>();
    13.  
    14.     void Awake()
    15.     {
    16.         for (int i = 0; i < audioClips.Length; i++)
    17.         {
    18.             freeClips.Add(i);
    19.         }
    20.  
    21.  
    22.         }
    23.  
    24.     public AudioClip GetNewClip()
    25.     {
    26.         int rand = Random.Range(0, freeClips.Count);
    27.  
    28.         AudioClip clip = audioClips[freeClips[rand]];
    29.         freeClips.RemoveAt(rand);
    30.         return clip;
    31.  
    32.  
    33.     }
    34.  
    35.     void Update(){
    36.  
    37.  
    38.     }
    39.  
    40.  
    41. }
    Play that sound OnMouseEnter
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4.  
    5. public class SoundPlayer : MonoBehaviour
    6. {
    7.  
    8.     private static bool isPlaying;
    9.     [SerializeField]
    10.     private GameManager1 gameManager;
    11.  
    12.  
    13.     public void Start()
    14.     {
    15.        
    16.  
    17.             while (audio.clip == null)
    18.                 audio.clip = gameManager.GetNewClip();
    19.  
    20.        
    21.    
    22.     }
    23.  
    24.     IEnumerator Wait()
    25.     {
    26.         yield return new WaitForSeconds(audio.clip.length);
    27.         isPlaying = false;
    28.     }
    29.  
    30.     void Update(){
    31.         //Debug.Log (audio.clip);
    32.  
    33.     }
    34.  
    35.     void OnMouseEnter(){
    36.  
    37.         if(isPlaying == false & Input.GetKey("space")){
    38.         audio.Play ();
    39.         isPlaying = true;
    40.         StartCoroutine(Wait());
    41.         }
    42.     }
    43. }
    Awake() is called before Start(), roger.

    Thanks a lot for your help guys!!

    edit: yep the start vs awake was the problem... Thanks Kelso !
     
    Last edited: Nov 4, 2014