Home and Learn: Games Programming Course
Adding an Audio Source component to game objects like players and enemies can cause a solo developer like yourself a lot of headaches. Take this common scenario. You've added an audio source to an enemy. The enemy has a script with a destroy line. You want the enemy to die after, say, 5 hits. The audio is supposed to play all five times. Except, it won't play all 5 times because you've destroyed the enemy game object. Creating a Sound Manger to handle sounds is a better and more professional solution. And can save you a lot of headaches. Let's see how they work.
Create a new Unity project. We'll keep this really simple. We'll play sounds when certain keys on the keyboard are pressed. And we'll create our own sound manager to handle the playing of the sounds.
In the Hierarchy on the left of Unity, right click and select Create Empty (a shortcut is CTRL + SHIFT + N). Rename your empty game object to Keyboard.
We do actually need an Audio Source. But we'll create a prefab to hold the audio source component. That way, we can attach it any game object that needs it.
So, create a new empty game object in the Hierarchy. Call it SoundFXPrefab:
With your SoundFXPrefab game object selected, in the Inspector on the right, click the Add Component button and add an Audio Source. Uncheck Play on Awake. Then make sure your Position values at the top are all 0. You don't need to add an audio resource. We just need the audio source component and the transform:
Now drag and drop your SoundFXPrefab game object from the Hierarchy into the project area at the bottom of Unity. In the image below, we're dragging and dropping into a folder called MyPrefabs.
And that's it - one prefab created!
You don't need the SoundFXPrefab game object in the Hierarchy anymore, so delete it from there. Now let's create our Sound Effects Manager.
Still in the Hierarchy, create a new empty game object. Name it SoundFXManager.
Make sure the transform position values in the Inspector are all 0. Click
the Add Component button and add a New Script. Call it SoundFXManager
again.
Double click your script to open it up in your editor. Delete all the default code and replace it with this:
using UnityEngine;
public class SoundFXManager : MonoBehaviour
{
public static SoundFXManager instance;
[SerializeField] private AudioSource soundFX_Prefab;
private void Awake()
{
if (instance == null)
{
instance = this;
}
}
public void PlaySingleSFXAudioClip(AudioClip audioClip, Transform sfxPosition)
{
AudioSource sfxAudio = Instantiate(soundFX_Prefab, sfxPosition.position, Quaternion.identity);
sfxAudio.clip = audioClip;
sfxAudio.Play();
float clipLen = sfxAudio.clip.length;
Destroy(sfxAudio.gameObject, clipLen);
}
}
It should look like this in your editor:
Save your work and go back to Unity (we'll go through the script later). Notice there is an empty slot under your script name:
Drag and drop your prefab from the projects area of Unity onto the empty slot:
The prefab will then be added to the Sound FX Manager.
The Audio Source prefab will be used in this line of our script, in the soundFX_Prefab variable:
[SerializeField] private AudioSource soundFX_Prefab;
Later, we'll add an array here, so we can play a random sound from a range of sounds
Now let's set the keyboard up so we can play a sound.
Select your Keyboard empty game object in the Hierarchy on the left of Unity. In the Inspector on the right, click the Add Component button and add a New Script. Call it KeyboardScript. Double click the script name to open it up in the code editor. Delete all the default code and copy and paste this in its place:
using UnityEngine;
public class KeyboardScript : MonoBehaviour
{
[SerializeField] private AudioClip singleAudioClip;
void Update()
{
if (Input.GetKeyDown(KeyCode.W))
{
SoundFXManager.instance.PlaySingleSFXAudioClip(singleAudioClip, transform);
}
}
}
And here's what it looks like in your editor:
Not much code! (We'll go through it soon.)
Save your work and go back to Unity. You should see an empty slot below your script:
Now you need some sounds. Download these four sounds (right click and Save As):
Once downloaded, drag and drop them into your Unity project. (If that doesn't work, create a new folder in the projects area of Unity. Right-click and select Import New Asset from the menu that appears. Navigate to where on your computer you saved the sounds above.) You'll have this:
Now drag and drop one of the audio clips onto the empty slot below your script name:
And we're ready to go. Play your game. When the game loads, press the W key on your keyboard. You should hear the sound play.
NOTE: If you get an error like this one,
"You are trying to read Input using the UnityEngine.Input class, but you have switched active Input handling to Input System package in Player Settings."
then stop your game. Click the Edit menu at the top of Unity. From the menu, select Project Settings. From the list on the left, select the Player item. Now locate the Configuration section and the Active Input Handling item. From the dropdown list, change to either the Old input manager or select Both:
When you select Both, Unity will prompt you to restart. Click Apply on
the message box. Play your game again and you should be fine. (If you
can't hear any sounds, obviously make sure you have a speaker plugged
in and that your sound setting are OK.)
The point here is that you're not adding an Audio Source component to the enemy. You're just passing in a clip that will be played by the Sound Manager. It will still play, even as the enemy is destroyed. That's because the Audio Source component is not being destroyed along with the enemy. (Instead of the script and the audio clip being attached to a keyboard press, you can attach it to a game object like an enemy, a grunt perhaps when the enemy takes a hit.)
Now let's add an array so we can play a random sound clip.
Open up your SoundFXManager script again. Copy and paste this new method into your script (just below the PlaySingleSFXAudioClip method will do):
public void PlayRandomSFXAudioClip(AudioClip[] audioClip, Transform sfxPosition)
{
AudioSource sfxAudio = Instantiate(soundFX_Prefab, sfxPosition.position, Quaternion.identity);
int rand = Random.Range(0, audioClip.Length);
sfxAudio.clip = audioClip[rand];
sfxAudio.Play();
float clipLen = sfxAudio.clip.length;
Destroy(sfxAudio.gameObject, clipLen);
}
Your editor should look like this:
Save your work and go back to the Keyboard script. Add this array to the top of the code, just below the singleAudioClip line:
[SerializeField] private AudioClip[] sfxClips;
(The square brackets after AudioClip means that sfxClips is now an array capable of holding more than one audio clip.)
In your Update method, add this:
if (Input.GetKeyDown(KeyCode.A)) {
SoundFXManager.instance.PlayRandomSFXAudioClip(sfxClips, transform);
}
Your code should look like this in the editor:
Save your work and go back to Unity. You should see this under your keyboard script.
The List the Inspector is talking about is that array, the sfxClips one. You can either click the plus button and add audio clips separately. Or you can try the following. Click the lock symbol at the top of the Inspector:
With the Inspector locked, drag and drop all four clips onto the list.
With all the clips loaded, play your game again. Press the A key on your keyboard. You should hear a random sound playing.
Now let's run through the script to see how it all works.
If you look at the top of the SoundFXManager script, you'll see this (as well as the soundFX_Prefab line):
public static SoundFXManager instance;
private void Awake()
{
if (instance == null)
{
instance = this;
}
}
This is something called a singleton, which is like a one-shot script. By creating a singleton, you can reference it in other scripts, like our keyboard one. In the singleton, we can just create methods and call them when needed. Here's the method that plays a single audio clip:
public void PlaySingleSFXAudioClip(AudioClip audioClip, Transform sfxPosition)
{
AudioSource sfxAudio = Instantiate(soundFX_Prefab, sfxPosition.position, Quaternion.identity);
sfxAudio.clip = audioClip;
sfxAudio.Play();
float clipLen = sfxAudio.clip.length;
Destroy(sfxAudio.gameObject, clipLen);
}
The method is called PlaySingleSFXAudioClip. Between its round brackets, we're passing in an Audio Clip and a Transform. The first line of the method Instantiates an Audio Source called sfxAudio:
AudioSource sfxAudio = Instantiate(soundFX_Prefab, sfxPosition.position, Quaternion.identity);
Here, we can use that Audio Source prefab we set up, the one called soundFX_Prefab at the top of the code. We need to say where we want to play the sound, which is just the transform (sfxPosition.position). The Quaternion.identity part just means we don't want any rotation.
The rest of the code for the method is self-explanatory: specify which clip we want to play, play it, get its length, then destroy it, once it has finished playing.
The other method, PlayRandomSFXAudioClip, is just about the same, except we pass in an array and generate a random number.
In the Keyboard script, we're using the old Unity input system just to grab which key was pressed on the keyboard. Then comes this line for the W key:
SoundFXManager.instance.PlaySingleSFXAudioClip(singleAudioClip, transform);
We are referencing our Sound Manager with this part:
SoundFXManager.instance
When we type a dot after instance, we can call any of the two methods we set up. If the W key is pressed, we pass in a single audio clip. When the A key is pressed, we pass in the array. (The transform part gets us the position of the keyboard game object, which is where we want the sound to be played. In your own games, this would typically be an enemy's position.)
And that's it - we have now created a Sound Manager. You can do a lot more here, like setting background music and mixers, rather than just sound effects. But we'll leave it there.
<--Back to the Unity 3D Course Contents Page
Email us: enquiry at homeandlearn.co.uk