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

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 :

void OnMouseEnter (){
        mySound = myCamera.GetComponent<GameManager> ().music4;
        if(!audio.isPlaying){
            audio.PlayOneShot (mySound);
            Debug.Log("WTFFFFF");
        }

What am i missing ?

isPlaying checks if the clip is playing. PlayOneShot does not assign the clip. Assign the clip and then use Play instead.

if (!audio.isPlaying)
{
    audio.clip = mySound;
    audio.Play();
}
1 Like

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 !

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

private static bool isPlaying;

public void PlaySound()
{
    if (!isPlaying)
    {
        audio.clip = mySound;
        audio.Play();
        isPlaying = true;
        StartCoroutine(WaitFlip());
    }
}

IEnumerator WaitFlip()
{
    yield return new WaitForSeconds(audio.clip.length);
    isPlaying = true;
}
1 Like

Hey,

I guess you meant :

IEnumerator WaitFlip()
{
    yield return new WaitForSeconds(audio.clip.length);
    isPlaying = false;
}
  • I thought variables had to be public to be reachable from other scripts ?

It seems that i cannot use GetComponent with FindGameObjectsWithTag.

MySoundIsPlaying = GameObject.FindGameObjectsWithTag("Planets").get...

Thanks a lot for your help

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

foreach(GameObject go in FindGameObjectsWithTag("tag"))
{
    var thething = go.GetComponent<thing>();
}
1 Like

My bad :slight_smile:

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.

Hey,

Thank you guys.

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

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…)

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 :slight_smile:

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

1 Like

Yep my code is a bit messy right now…
I was to excited to get things working i guess :slight_smile:

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 :

using UnityEngine;
using System.Collections;

public class GameManager1 : MonoBehaviour {
   
    public AudioClip[] audioArray;
    public AudioClip[] MusicArray;
    public AudioClip music1;
    public AudioClip music2;
    public int SoundChoice1;
    public int SoundChoice2;
    public int PlanetChoice;
 
    // Use this for initialization
    void Start () {
      
        // As i'm using arrays, this is the random range to define the arguments
        // Sound1
        SoundChoice1 = Random.Range (0, audioArray.Length);
    
        // Sound2
        do{
            SoundChoice2 = Random.Range (0, audioArray.Length);
        }while (SoundChoice2 == SoundChoice1);

        // My AudioArray
        audioArray =  new AudioClip[]{
            (AudioClip)Resources.Load("sound1"),
            (AudioClip)Resources.Load("sound2")
           
        };
       
        // initialize musics
        music1 = audioArray [SoundChoice1];
        music2 = audioArray [SoundChoice2];
             
    }
   
}

So what should i do from the MySound.cs ?
All my objects need to have a unique sound… I’m a bit lost here…

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

public class GameManager : MonoBehaviour
{
    // why are you using Resources.Load?
    // just assign them all via the Inspector
    [SerializeField]
    private AudioClip[] audioClips;
 
    private List<int> freeClips = new List<int>();
 
    void Start()
    {
        for (int i = 0; i < audioClips.length; i++)
        {
            freeClips.Add(i);
        }
        // you could shuffle the list here to make it more "random"
    }
 
    public AudioClip GetNewClip()
    {
        int rand = Random.Range(0, freeClips.Count);
        AudioClip clip = audioClips[freeClips[rand]];
        freeClips.RemoveAt(rand);
        return clip;
    }
}

public class SoundPlayer : MonoBehaviour
{
    private static bool isPlaying;

    [SerializeField]
    private GameManager gameManager;
 
    public void PlaySound()
    {
        if (!isPlaying)
        {
            if (audio.clip == null)
                audio.clip = gameManager.GetNewClip();
             
            audio.Play();
            isPlaying = true;
            StartCoroutine(Wait());
        }
    }
 
    IEnumerator Wait()
    {
        yield return new WaitForSeconds(audio.clip.length);
        isPlaying = false;
    }
}
1 Like

Hi,

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

using UnityEngine;
using System.Collections;
// Adding .Generic (it seems that lists dont work without it)
using System.Collections.Generic; 

public class GameManager1 : MonoBehaviour
{
    [SerializeField]
    public AudioClip[] audioClips;
    private List<int> freeClips = new List<int>();
   
    void Start()
    {
//Start with i = 0 / Length = 2 // 
        for (int i = 0; i < audioClips.Length; i++)
        {   

            freeClips.Add(i);
       
        }

        }
   
    public AudioClip GetNewClip()
    {
// First time freeClips.Count = 0 ? Then i = 1 ?
        int rand = Random.Range(0, freeClips.Count);       
        AudioClip clip = audioClips[rand];
        freeClips.RemoveAt(rand);
       
        return clip;
    }

}

And my SoundPlayer script :

using UnityEngine;
using System.Collections;


public class SoundPlayer : MonoBehaviour
{[SerializeField]
    private static bool isPlaying;

   
    public void Start()
    {

            if (audio.clip == null)
                audio.clip = GameObject.Find("Camera").GetComponent<GameManager1>().GetNewClip();
   
            audio.Play();
            isPlaying = true;
            StartCoroutine(Wait());
       
    }
   
    IEnumerator Wait()
    {
        yield return new WaitForSeconds(audio.clip.length);
        isPlaying = false;
    }
}

I’m definitely missing something… I’ve read it a lot of time now but cant see what is wrong.

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 :slight_smile:

1 Like

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.

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class GameManager1 : MonoBehaviour
{
    private int rand;
    [SerializeField]
    public AudioClip[] audioClips;
    private AudioClip clip;

    private List<int> freeClips = new List<int>();

    void Start()
    {


        // Debug.Log(audioClips.Length) = 2
        for (int i = 0; i < audioClips.Length; i++)
        {

            freeClips.Add(i);
            //Debug.Log (freeClips.Count); = 2
        }
        //Debug.Log (freeClips[rand]);

        }

    public AudioClip GetNewClip()
    {
        int rand = Random.Range(0, freeClips.Count);

        AudioClip clip = audioClips[freeClips[rand]];
        //Debug.Log (freeClips[rand]);

        freeClips.RemoveAt(rand);
        return clip;


    }

    void Update(){

//Debug.Log (freeClips.Count); = 2
//Debug.Log (audioClips[freeClips[rand]]);
//Debug.Log (freeClips[rand]); = 0
//Debug.Log (clip); //= Null
//Debug.Log (audioClips[1]); //-> Ok -> sound1
//Debug.Log (audioClips[0]);// -> Ok -> sound0

    }


}

This is my problem :

//Debug.Log (freeClips[rand]); = 0
//Debug.Log (freeClips.Count); = 2
//Debug.Log (audioClips[freeClips[rand]]);
//Debug.Log (freeClips[rand]); = 0
//Debug.Log (clip); //= Null
//Debug.Log (audioClips[1]); //-> Ok -> sound1
//Debug.Log (audioClips[0]);// -> Ok -> sound0

It seems that audioClips[freeClips[rand]] is not returning 1 or 0.

But Debug.Log (freeClips.Count); = 2

So rand should be 1 or 0...?

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.

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

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class GameManager1 : MonoBehaviour
{
    public AudioClip clip;
    private int rand;
    [SerializeField]
    public AudioClip[] audioClips;

    private List<int> freeClips = new List<int>();

    void Awake()
    {
        for (int i = 0; i < audioClips.Length; i++)
        {
            freeClips.Add(i);
        }


        }

    public AudioClip GetNewClip()
    {
        int rand = Random.Range(0, freeClips.Count);

        AudioClip clip = audioClips[freeClips[rand]];
        freeClips.RemoveAt(rand);
        return clip;


    }

    void Update(){


    }


}

Play that sound OnMouseEnter

using UnityEngine;
using System.Collections;


public class SoundPlayer : MonoBehaviour
{

    private static bool isPlaying;
    [SerializeField]
    private GameManager1 gameManager;


    public void Start()
    {
        

            while (audio.clip == null)
                audio.clip = gameManager.GetNewClip();

        
    
    }

    IEnumerator Wait()
    {
        yield return new WaitForSeconds(audio.clip.length);
        isPlaying = false;
    }

    void Update(){
        //Debug.Log (audio.clip);

    }

    void OnMouseEnter(){

        if(isPlaying == false & Input.GetKey("space")){
        audio.Play ();
        isPlaying = true;
        StartCoroutine(Wait());
        }
    }
}

Awake() is called before Start(), roger.

Thanks a lot for your help guys!!

edit: yep the start vs awake was the problem… Thanks Kelso !