Workaround for the AudioSource.PlayClipAtPoint in Unity 5.x

Hello everyone! My name is Alan and I am following the course for some time. I was at the #87 lecture and I found a small issue with the AudioSource.PlayClipAtPoint. Why? Well, because I’m so stuborn that I used Unity 5 to make the game, instead following Ben’s advice.
For those who don’t know, in Unity 5 you can’t change the audio from 3D to 2D in the import settings tab. You need to have a Game Object created for that sound, and there you can change this property in a slider called “Spatial Blend”. By default, Unity 5 make the audio 3D and you can’t change this parameter for the PlayClipAtPoint method. So the result is an “unhearable” break sound event.
I’m not really sure if someone here have already proposed an workaround for this issue. And also I’m not really sure if my solution is a good solution, but for those who are so stuborn as I am, here is a script for make the job done.

public class SoundManager : MonoBehaviour {
    // Configuration of properties
    [Range(0.0f, 1.0f)]
    public float volume = 1.0f;

    [Range(0.0f, 1.0f)]
    public float spatialBlend = 0f;

    [Range(-3.0f, 3.0f)]
    public float pitch = 1.0f;

    [Range(-1.0f, 1.0f)]
    public float pan = 0f;

    // List of the created instances of One Shot Player
    private List<GameObject> soundPlayerChilds;

	// Use this for initialization
	void Start () {
        soundPlayerChilds = new List<GameObject>();
	}
	
	// Update is called once per frame
	void Update () {

        // Finds whose sounds are already done playing and kill'em'all
        for (int i = soundPlayerChilds.Count-1; i >= 0; i--)
        {
            if (!soundPlayerChilds[i].GetComponent<AudioSource>().isPlaying)
            {
                Destroy(soundPlayerChilds[i]);
                soundPlayerChilds.RemoveAt(i);

            }
        }

	}

    public void OneShotSound(AudioClip audioClip, Vector3 position)
    {
        // Add a new instance for the list of instances
        GameObject dummyGameObject = new GameObject("One Shot Sound");
        soundPlayerChilds.Add(dummyGameObject);
        int currentIndex = soundPlayerChilds.Count - 1;

        // Add the audio source component
        soundPlayerChilds[currentIndex].AddComponent<AudioSource>();
        soundPlayerChilds[currentIndex].transform.position = position;

        // Get the audio source component created
        AudioSource audioSource = soundPlayerChilds[currentIndex].GetComponent<AudioSource>();

        // Configure the audio source component
        audioSource.clip = audioClip;
        audioSource.volume = volume;
        audioSource.spatialBlend = spatialBlend;
        audioSource.pitch = pitch;
        audioSource.panStereo = pan;

        // Starts playing the sound
        audioSource.Play();
    }
}

You need to attach this script (I called it “SoundManager”) to a Game Object. When you select it in the hierarchy you can change some basic properties for the sound to play in the inspector tab.
After that you just need to retrieve the reference to the Game Object with something like:

soundManager = FindObjectOfType<SoundManager>();

And play the punctual sound with the OneShotSound method:

soundManager.OneShotSound(breakSound, transform.position);

The class will create a temporary object to play the sound and also will take care of the destruction of the objects that already finished playing the sound.
Well, I hope my contribuition is useful for someone and I’m really sorry for my bad english!

Just one more thing: Happy New Year for you all! =D

See ya!

2 Likes

Hey alanteruel, this is very similar to what i did as well. Just for reference though, you don’t have to do the loop in the update to kill off the spawned audio sources, you can just use the Destory() function after you tell the AudioSource to play.

The Destory() function allows you to input a float value t for “optional amount of time to delay before destroying the object”. With that, you can just put the value for t equal to the AudioClip’s length property.
https://docs.unity3d.com/ScriptReference/Object.Destroy.html

Here is the method I created for it for comparison. It’s part of my “AudioManager” class. I have the AudioManger setup as a singleton with all sound effects attached to it as public fields, and using the method is like such

AudioManager.instance.Play2DClipAtPoint(AudioManager.instance.sfxBallHitPaddle);

public void Play2DClipAtPoint(AudioClip clip)
{
    //  Create a temporary audio source object
    GameObject tempAudioSource = new GameObject("TempAudio");

    //  Add an audio source
    AudioSource audioSource = tempAudioSource.AddComponent<AudioSource>();

    //  Add the clip to the audio source
    audioSource.clip = clip;

    //  Set the volume
    audioSource.volume = this.sfxVolume;

    //  Set properties so it's 2D sound
    audioSource.spatialBlend = 0.0f;

    //  Play the audio
    audioSource.Play();

    //  Set it to self destroy
    Destroy(tempAudioSource, clip.length);

}
2 Likes

I’m confused. I know I shouldn’t have created this project in Unity 5, but I did. I am confused as to how this temporary gameObject is being created. Was the… AudioSource.PlayClipAtPoint(clack, transform.position); …removed completely? I see @Chris_Whitley created… GameObject tempAudioSource = new GameObject(“TempAudio”); …but where was the script placed? I know @alanteruel said it was new… Can you guys start where Ben left off?

In bricks script he said write:

void OnCollisionEnter2D(Collision2D collision)
{
AudioSource.PlayClipAtPoint(clack, transform.position);}

I know you changed this but to what?

I understand a new gameObject is created but is this a permanent gameObject or did you put something in prefabs? Is this something you drag and drop into each new scene?

It’s nice that you came up with your own solution, by avoiding the use of the Unity provided PlayClipAtPoint.

However, there’s a simple solution to make the one shot clip sound like in 2D:

AudioSource.PlayClipAtPoint(clip, Camera.main.transform.position);

Bear in mind that this works only if the camera isn’t moving, though, but here in Block Breaker the camera is fixed, so you can go with this simple solution.

1 Like

This worked for me. Thank you! @Galandil

Hey guys, thanks for the feedback!

Man, I completely forgot about that optional argument for the method Destroy()! I’ll update my code with the new one, as that way seems to consume a little less processing than mine.[quote=“Chris_Whitley, post:2, topic:15554”]
I have the AudioManger setup as a singleton with all sound effects attached to it as public fields, and using the method is like such

AudioManager.instance.Play2DClipAtPoint(AudioManager.instance.sfxBallHitPaddle);
[/quote]

That is what I have in mind too. That’s why I called that class “SoundManager”. I just didn’t had time to work in that yet because I am solving some bugs with other scripts.

No, it still there. What changed is that now you can’t set the audio as a 2D audio without linking that sound to a AudioSource component in a GameObject. Which means that when you call the PlayClipAtPoint method, the sound will be reproduced with the default Unity sound configurations, that is, with the 3D sound activated. That can lead to weird sound levels, depending of your situation.

The script have to be attached to a GameObject, and it can be a prefab. And the script itself create temporary GameObjects which appears at the define position, play the sound and are immediately destroyed.

Yeap, that probably is the simplest way to handle that, and it will work in most cases. In my case is not very handful as I am using small levels for Spatial Blend, to make the sound more surrounding to the player.

See ya guys!

Hi all,

Thanks for the info provided here! For those like me who like the simplest solutions that are flexible, another way you can solve this is by avoiding PlayClipAtPoint and instead just using Play(), disabling the collider and sprite renderer, and destroying the brick after the length of the audio clip:

		myAudioSource.clip = myCrackSound;
		GetComponent<BoxCollider2D>().enabled = false;
		GetComponent<SpriteRenderer>().enabled = false;
		Destroy(gameObject, myAudioSource.clip.length);

This way, you get to use and tweak the settings from the (wonderfully GUI) AudioSource component. I’d imagine that this should work for other situations like if the camera is moving. :relaxed:

I was hopping to see what the final script looked like in it’s entirety. I’m a bit of a novice so I got lost on the separate sound manager script.How exactly are you having the other scripts communicate with the sound manager? And what does the inspector look like? Are all the clips loaded into the sound manager or are they loaded into the dedicated object scripts?

one last thing, I am trying to get an array to work. I got it to work with the initial tutorial lesson but how would I implement it with a sound manager? (for reference I really want an array for the bricks cracking sound)

Where I am getting most confused is the unique naming. My naming (and conventions differ slightly and being able to see the header of code helps me see how your conventions work)

Hi William,

Are you asking me as the most recent poster, or are you asking about the first post in this thread?

@Anchronos @alanteruel I’ll take any help I can get. The thing that is especially buggy that I can’t get is the final crack sound before the brick object is destroyed.

Privacy & Terms