This is the first Unity post in a series where we’ll share some of our experience and methods for working with the Unity3D game engine.
This is going to be a post about sound design and programming, by David Larsen and Mathias Soeholm. We will show you how we handle audio inside Unity, and make sure that repetitive sounds, sound good. Let’s talk about the latter first.
_____
David Larsen
When we use sound effects in Unity3D, we often use a principle called Round Robin, or anti-repetition. Basically, Round Robin is when you use multiple variations of the same sound effect, so when the sound effect is triggered, one of these variations is played at random. The purpose of this is to get rid of the so-called “machinegun-effect” – when a single audio file (for example footsteps, or hit impacts) is repeated in quick succession, it sounds very unnatural and unrealistic.
Here’s an example of a series of footsteps using the same audio file:
Hardly sounds convincing, huh? It’s very obvious that it’s just the same sound effect played over and over again. Here’s how you fix it, using the Round Robin principle:
Let’s say you have a sound effect called “Explosion”. Now imagine you have 3 or more variations of that same explosion, called Exp_1.mp3, Exp_2.mp3 and Exp_3.mp3.
When the “Explosion” sound effect is triggered, have a script randomly select one of these sounds. First time it’s triggered, it might use Exp_2.mp3. Next Exp_1.mp3, then Exp_3.mp3, and then perhaps Exp_1.mp3 again. This gets rid of the machinegun effect, and it makes the sound effect feel much more dynamic and realistic.
Here is a set of footsteps that are triggered the same way as with the previous example, but now with a script that is randomly choosing between a selection of 5 variations of the footsteps. The difference should be very clear:
At a minimum, you want 3 sound variations for this to work, but the more the better. If I can get away with it, I do 4-6 variations of each sound.
Something you want to be aware of, is that true randomization won’t necessarily work better. You don’t want to play back the same sound variation twice. Our solution was to exclude the audio files that had already been played from the selection. So if Exp_1.mp3 had just been used, only Exp_2.mp3 and Exp_3.mp3 would be available.
The biggest downside to using round Robin is of course that you need to come up with a lot of variations of each sound. While it’s easy enough to get a lot of sounds for footsteps and such, you might not be able to find multiple sounds for effects like explosions, impacts, etc. But remember, even very subtle things like a tiny pitchshift, a bit of EQ’ing, and maybe timestretching will get you very far – even if you don’t have the opportunity to get multiple recordings or design variations of a specific sound.
Mathias Soeholm, our gameplay programmer, made a really useful script for this. It’s called AudioHandler, and it manages all the audio in our games. It’s responsible for playing music and sound effects, and it has a Round Robin engine build right in.
Feel free to use this however you want. Below is the entire script, and documentation on how to use it, written by Mathias.
_______
Mathias Soeholm
The AudioHandler script, utilizes one of multiple audio sources in the scene, and selects one of them at random. I use multiple audio sources, because a single audio source can only play one sound at a time. This is a great system if you are using 2D sounds and you’re not dependent on where your audio sources are located in the scene.
AudioHandler.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[System.Serializable]
public class AudioCollection
{
public int repeatInterval;
public AudioClip[] audioClips;
[HideInInspector]
public List<int> recentlyPlayedSounds = new List<int>();
}
public class AudioHandler : MonoBehaviour
{
enum MusicState
{
FullVolume,
FadingIn,
FadingOut,
Muted
}
private const float FadeFactor = 0.2f;
// Audio clips - Example variables
public AudioClip deliverAtSafeZoneSound;
public AudioCollection joinShoalSounds;
private static MusicState musicState = MusicState.Muted;
private static int nextAudioSource;
private static List<AudioSource> audioSources = new List<AudioSource>();
private static AudioCollection collectionToPlay;
// Static reference to singleton object
public static AudioHandler instance;
// Methods
void Start()
{
audioSources.Clear();
instance = gameObject.GetComponent<AudioHandler>();
// Trigger FadeIn
musicState = MusicState.FadingIn;
// Get references to all audio sources in the scene
foreach (var audioSource in GameObject.FindGameObjectsWithTag("AudioSourceObject"))
{
audioSources.Add(audioSource.GetComponent<AudioSource>());
}
}
void Update()
{
HandleMusicFade();
}
void HandleMusicFade()
{
switch (musicState)
{
case MusicState.FadingIn:
audio.volume = Mathf.Min(audio.volume + FadeFactor*Time.deltaTime, 1);
if (audio.volume >= 1)
musicState = MusicState.FullVolume;
break;
case MusicState.FadingOut:
audio.volume = Mathf.Max(audio.volume - FadeFactor*Time.deltaTime, 0);
if (audio.volume <= 0)
musicState = MusicState.Muted;
break;
}
}
public void PlayCipFromCollection(ref AudioCollection collection)
{
collectionToPlay = collection;
StartCoroutine("PlayCollectionClip");
}
IEnumerator PlayCollectionClip()
{
yield return 0;
int clipToPlay;
// The use of coroutines enables us to use a while loop
do
{
clipToPlay = Random.Range(0, collectionToPlay.audioClips.Length);
} while (collectionToPlay.recentlyPlayedSounds.Contains(clipToPlay));
collectionToPlay.recentlyPlayedSounds.Add(clipToPlay);
if (collectionToPlay.recentlyPlayedSounds.Count > collectionToPlay.repeatInterval)
collectionToPlay.recentlyPlayedSounds.RemoveAt(0);
PlaySound(collectionToPlay.audioClips[clipToPlay]);
}
public static void PlaySound(AudioClip clip)
{
// Get index for the next audio source to use
nextAudioSource = (nextAudioSource + 1) % audioSources.Count;
if (audioSources[nextAudioSource].isPlaying) return;
// Assign the clip to the selected audio source
audioSources[nextAudioSource].clip = clip;
// Play the clip with the selected audio source
audioSources[nextAudioSource].PlayOneShot(clip);
}
}
The picture below shows how the audio sources are set up on individual gameobjects, all with the tag “AudioSourceObject”, which is important for the script to be able to find and utilize the audio sources.

Sounds are added in the inspector, either as a single sound or a collection. The AudioHandler script is applied on a gameobject, which also contains an audio source for the music.
I’ve set up an example of each in the script:
// Audio clips - Example variables
public AudioClip deliverAtSafeZoneSound;
public AudioCollection joinShoalSounds;
Here’s how it looks in the inspector:

Each sound collection contains a variable called ‘repeatInterval’. This is how many other sounds, which must be played before repeating a previously played sound. The repeat interval must always be below the amount of sounds in a collection, otherwise you’ll end up in an infinite while loop, and that’s bad!
To play a sound from a collection, from other scripts, you use the static instance of the AudioHandler class, which references the AudioHandler component on the gameobject. Here’s an example:
// Play sound
AudioHandler.instance.PlayCipFromCollection(ref AudioHandler.instance.joinShoalSounds);
The ‘ref’ keyword is used to pass by reference and not as a copy.
To play a single sound that is not part of a collection, you simply call the static PlaySound() method. Here’s an example:
// Play sound
AudioHandler.PlaySound(AudioHandler.instance.deliverAtSafeZoneSound);
If you have any trouble, I’ll be glad to help you out just leave a comment below. And if you make any improvements, please share.
/Mathias
Comments