Chapter 11. Sound and Music

Audio can be one of the most important parts of a game’s experience, and Unity has a wide range of tools for both simple and complex setups. In this chapter, we’ll discuss both the basics of how to get audio working in your scenes and more advanced ways to control how your game sounds.

Warning

Sound and music is far, far more important than you might suspect. Don’t leave out sound, music, and audio in your game.

As with other chapters, this chapter is far from an exhaustive collection of recipes covering literally everything you might ever need to do with audio in Unity. Instead, we try to cover the things that, as game developers, we find ourselves implementing over and over again, game after game.

11.1 Playing Sounds

Solution

To play an audio clip, you first need an audio file to play. If you already have one in mind, drag and drop it into your project.

Once you’ve added the audio asset to your project, configure how Unity imports it by selecting it and looking at the Inspector (Figure 11-1). Unity supports .aif, .wav, .mp3, and .ogg audio. It also supports the tracker formats .xm, .mod, .it, and .s3m. Unity will convert audio into the format that you specify.

ugdc 1101
Figure 11-1. The import settings for an AudioClip asset

Once you’ve imported your audio clip, it’s ready to be played. In Unity, audio is played from AudioSource components attached to game objects. To create one, follow these steps:

  1. Open the GameObject menu and choose Audio → Audio Source. A new game object will be created, with an AudioSource component attached.

    Note

    You can also add an AudioSource component to an existing object by selecting the game object, clicking the Add Component button at the bottom of the Inspector, and choosing Audio → Audio Source.

  2. Once you have an audio source, you attach the audio clip that you want to play by dragging the asset from the Project tab into the AudioClip slot (Figure 11-2).

ugdc 1102
Figure 11-2. The Inspector for an AudioSource component
  1. Play the game. The audio will begin playing.

Tip

You can configure whether an audio source begins playing when the object first becomes active by enabling the Play On Awake option.

11.2 Setting Up a Mixer

11.3 Using Audio Effects

Solution

You can modify the way that audio is processed by the audio system by adding audio effects, which can filter the frequency ranges of the sound, apply reverb, and more.

You can set up effects in three different places: an audio mixer group, an audio source, or an audio listener.

Effects on an audio source or an audio listener are components, just like other non-audio components such as renderers or physics colliders. To add an effect on an audio source, select the game object that contains the audio source you want to modify, and click the Add Component button. Click Audio, and select an effect you want to add. The component will be added, and any audio played by the audio source will be modified by the filter (Figure 11-4).

ugdc 1104
Figure 11-4. An audio effect component, attached to a game object that has an audio source

To add an effect on an audio listener, the process is identical, except that the effect will modify any audio received by the listener (see Figure 11-5).

ugdc 1105
Figure 11-5. An audio effect component, attached to a game object that has an audio listener
Tip

The ordering of the components is important, because it controls the order in which the effects are applied to the audio.

In addition to adding effects to audio sources and listeners, you can also add effects on groups in the mixer by following these steps:

  1. If you don’t already have one, create a mixer group by following the steps in Recipe 11.2.

  2. Underneath the mixer for your group, click Add and select an effect.

  3. Select the effect underneath the group.

Tip

The B button in a mixer group bypasses effects, temporarily disabling them.

11.4 Using Send and Receive Effects

Solution

Use Send and Receive effects on your audio groups:

  1. First, set up at least two mixer groups on an audio mixer, by following the steps in Recipe 11.2.

  2. Add a Receive effect on the mixer group that should receive the audio, by clicking the Add button at the bottom of the group.

  3. Add a Send effect on the mixer group that should send audio. Your groups should now look something like Figure 11-6. Once you’ve added a Send effect, you need to set up where it sends its audio to.

ugdc 1106
Figure 11-6. An audio mixer, with a Send effect on one group, and a Receive effect on another
  1. Select the Send effect, and in its Inspector, set the Receiver to the Receive effect you added (Figure 11-7).

ugdc 1107
Figure 11-7. Configuring a Send effect to send audio to the specified Receive effect on another group
  1. Play the game, and play some audio through the group that has the Send effect. The audio will also play through the group that has the Receive effect, and will be modified by any effects on the receiving group.

Note

By default, the Send effect is set to the lowest level. You’ll need to increase the send level in order for any audio to be sent to the receiver.

Discussion

Send and Receive effects aren’t hugely useful on their own, since all they do is duplicate an audio signal. They become useful when you want to have finer control over how audio is routed, and when you want to apply audio effects that make use of multiple audio signals at the same time.

Some effects act as their own receivers—you don’t need to add a Receive effect on them as well. For example, the Duck Volume effect (discussed in Recipe 11.5) can act as a receiver.

11.5 Ducking

Solution

A “ducking” effect is one in which the level of one signal causes the level of another to lower. To implement this, you add a Duck Volume effect on the group that should lower its volume, and add Send effects to the groups that should trigger the volume lowering.

For example, imagine that you’ve got a mixer group that plays music and another that plays speech, and you want to automatically lower the music volume when speech is playing:

  1. Set up at least two mixer groups on an audio mixer by following the steps in Recipe 11.2. Name one mixer Voice, and the other mixer Music.

  2. Set up two audio sources; make one send to the Voice group, and the other send to the Music group. Attach whatever audio clip you like to each source; make the source that’s sending to Music loop.

  3. Select the Music group, which should lower its volume when Voice is playing, and add a new Duck Volume effect.

  4. Select the Voice group, which should cause the Music group to lower its volume, and add a Send effect.

  5. Select the Send effect, and set its Receiver to the Duck Volume effect.

  6. Increase the send level to maximum (that is, 0 dB).

You’re now ready to test:

  1. Play the game. You won’t hear much of an effect yet; you’ll need to tune the setting to figure out what values to set.

  2. Make the Voice source play its sound; for example, you can use a script to control the audio source (see Recipe 11.7).

  3. Select the Duck Volume effect, and click the “Edit in Playmode” button at the top of the Inspector.

  4. As it’s playing, adjust the Threshold slider to control at what point the ducking takes effect.

Discussion

You can also configure the Ratio and Knee parameters to adjust how the Duck Volume effect modifies its volume; additionally, the Attack Time and Release Time settings give you control over how quickly the effect is applied, and how long it takes for it to go away.

11.6 Using Multiple Audio Zones

Solution

Use a reverb zone to create areas of your game that apply different reverb settings to your audio listener. Reverb zones distort the sound heard by a listener depending on where the listener is within the zone, and are used when you want to gradually apply an ambient effect to a place as the player enters it.

To create one, follow these steps:

  1. Create a new reverb zone by opening the GameObject menu and choosing Audio → Audio Reverb Zone.

  2. Position the new zone in the area where you want the reverb to apply.

  3. Click and drag the points on the two spheres to set the minimum and maximum radius of the effect (Figure 11-8).

ugdc 1108
Figure 11-8. The Audio Reverb Zone in the scene
  1. In the Inspector (Figure 11-9), select what kind of effect you want to apply. There’s a wide range of presets, including concert halls, caves, padded rooms, and everything in between.

  2. Play the game and move the camera into the reverb zone. As you enter the zone, the effect will begin to apply to any audio being received by the audio listener.

ugdc 1109
Figure 11-9. The Inspector for an Audio Reverb Zone

Discussion

If you set the Reverb Preset to Custom, you can modify the individual reverb settings, and create a reverb effect that more precisely suits your needs.

11.8 Using a Sound Manager

Solution

Create a system that allows other parts of your code to request that a sound effect be played. The code given in this class is a simple example, but it’s designed to be a base from which you can add the specific features you need for your game.

This system works by defining an asset of the SoundEffect as a ScriptableObject class that contains references to audio clips. The AudioManager, which is a singleton, has a list of these SoundEffect assets, and can be asked to play one of them by passing the name of the effect. A random clip that’s attached to the SoundEffect asset will be selected, allowing for some nice variation. The effect can be played either at the same position as the listener, or a position in the world can be passed as a parameter, and the selected audio clip will be played there.

Note

This script makes use of a singleton class, which we discuss in Recipe 2.7. Follow the steps in that recipe before you continue with this one.

To begin building this, we’ll start by defining the SoundEffect asset:

  1. Create a new C# script called SoundEffect.cs, and put the following code in it:

    // An asset that contains a collection of audio clips.
    [CreateAssetMenu]
    public class SoundEffect : ScriptableObject {
    
        // The list of AudioClips that might be played when
        // this sound effect is played.
        public AudioClip[] clips;
    
        // Randomly selects an AudioClip from the 'clips' array,
        // if one is available.
        public AudioClip GetRandomClip() {
            if (clips.Length == 0) {
                return null;
            }
            return clips[Random.Range(0, clips.Length)];
        }
    }
    Tip

    In a real game, you might want to exclude the most recently playing random sound from the next result, just to make the game feel like it’s more interesting than it actually is!

  2. Create SoundEffect assets by opening the Assets menu and choosing Create → Sound Effect. Name the new asset whatever you like.

  3. Select the new asset, and in the Inspector, drag and drop the audio assets you’d like to use for this sound effect (see Figure 11-10).

ugdc 1110
Figure 11-10. The Inspector for a SoundEffect asset, with a collection of audio clips added to its list

Next, we’ll create the AudioManager class itself:

  1. Create a new C# script called AudioManager.cs, and add the following code to it:

public class AudioManager : Singleton<AudioManager> {

    // The list of references to SoundEffect assets.
    public SoundEffect[] effects;

    // A dictionary that maps the names of SoundEffects to the objects
    // themselves, to make it faster to look them up.
    private Dictionary<string, SoundEffect> _effectDictionary;

    // A reference to the current audio listener, which we use to place
    // audio clips.
    private AudioListener _listener;

    private void Awake() {
        // When the manager wakes up, build a dictionary of named sounds, so
        // that we can quickly access them when needed
        _effectDictionary = new Dictionary<string, SoundEffect>();
        foreach (var effect in effects) {
            Debug.LogFormat("registered effect {0}", effect.name);
            _effectDictionary[effect.name] = effect;
        }

    }

    // Plays a sound effect by name, at the same position as the audio
    // listener.
    public void PlayEffect(string effectName) {
        // If we don't currently have a listener (or the reference we had
        // was destroyed), find one to use
        if (_listener == null) {
            _listener = FindObjectOfType<AudioListener>();
        }

        // Play the effect at the listener's position
        PlayEffect(effectName, _listener.transform.position);

    }

    // Plays a sound effect by name, at a specified position in the world
    public void PlayEffect(string effectName, Vector3 worldPosition) {

        // Does the sound effect exist?
        if (_effectDictionary.ContainsKey(effectName) == false) {
            Debug.LogWarningFormat(
                "Effect {0} is not registered.", effectName);
            return;
        }

        // Get a clip from the effect
        var clip = _effectDictionary[effectName].GetRandomClip();

        // Make sure it wasn't null
        if (clip == null) {
            Debug.LogWarningFormat(
                "Effect {0} has no clips to play.", effectName);
            return;
        }

        // Play the selected clip at the specified point
        AudioSource.PlayClipAtPoint(clip, worldPosition);

    }

}

Finally, we’ll create the singleton instance, and add the audio clips we want to be available:

  1. Create a new game object by opening the GameObject menu and choosing Create Empty. Name the new object “Audio Manager.”

  2. Select the new object.

  3. Drag and drop the AudioManager.cs script onto the Inspector.

  4. Add the SoundEffect asset that you created into the Effects list (Figure 11-11).

ugdc 1111
Figure 11-11. The inspector for an audio manager, with a SoundEffect asset added to its list

You’re now ready to trigger sound effects from your code. For example, if you created a SoundEffect named “Laser,” you’d trigger it like so:

// Play a sound called "laser" at the same place as the listener
AudioManager.instance.PlayEffect("laser");

// Play the same sound at the origin
AudioManager.instance.PlayEffect("laser", Vector3.zero);

Discussion

As with any singleton, you’re making a tradeoff between ease of initial development and increased software complexity. See the Discussion of Recipe 2.7 for more details.