Still warming up to working in Unity, I’m slowly learning how to apply what I know about programming generally to a new language and framework. For example: Singletons, a single instance object that tends to work best as a utility.

When looking around for how to to implement audio programmatically I found the part of a tutorial I worked through years ago, the officially provided 2D RogueLike Tutorial. Specifically its Audio and Sound Manager, which happens to be a Singleton.

I’m sure this example is most useful for a 2D game, where you expect the sound or music to play without any sense of depth or location, but what re-discovering the tutorial reminded me is that it I can make use of familiar patterns in almost any language. Patterns are awesome!

SoundManager Component

Here is my version of the component:

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

public class SoundManager : MonoBehaviour
{
	// Audio players components.
	public AudioSource EffectsSource;
	public AudioSource MusicSource;

	// Random pitch adjustment range.
	public float LowPitchRange = .95f;
	public float HighPitchRange = 1.05f;

	// Singleton instance.
	public static SoundManager Instance = null;
	
	// Initialize the singleton instance.
	private void Awake()
	{
		// If there is not already an instance of SoundManager, set it to this.
		if (Instance == null)
		{
			Instance = this;
		}
		//If an instance already exists, destroy whatever this object is to enforce the singleton.
		else if (Instance != this)
		{
			Destroy(gameObject);
		}

		//Set SoundManager to DontDestroyOnLoad so that it won't be destroyed when reloading our scene.
		DontDestroyOnLoad (gameObject);
	}

	// Play a single clip through the sound effects source.
	public void Play(AudioClip clip)
	{
		EffectsSource.clip = clip;
		EffectsSource.Play();
	}

	// Play a single clip through the music source.
	public void PlayMusic(AudioClip clip)
	{
		MusicSource.clip = clip;
		MusicSource.Play();
	}

	// Play a random clip from an array, and randomize the pitch slightly.
	public void RandomSoundEffect(params AudioClip[] clips)
	{
		int randomIndex = Random.Range(0, clips.Length);
		float randomPitch = Random.Range(LowPitchRange, HighPitchRange);

		EffectsSource.pitch = randomPitch;
		EffectsSource.clip = clips[randomIndex];
		EffectsSource.Play();
	}
	
}

There is a bit going on here, so let’s break it down:

  1. It inherits MonoBehavior, so it can be assigned as a Component to any GameObject.
  2. It expects two AudioSource components be assigned to it. You can do this through the UI. We’re going to use one AudioSource as the game music, and the other to play the sound effects.
  3. It has a few methods.
    1. Play() simply plays whatever AudioClip is passed in through the sound effects AudioSource.
    2. PlayMusic() plays whatever AudioClip is passed in through the music AudioSource.
    3. RandomizeSoundEffects() plays one random item from an array of AudioClips, and randomizes the pitch of the clip slightly to provide some additional variation
  4. It is a Singleton, ensuring that only one instance of itself ever exists. It maintains the singleton pattern by having a static property where it stores itself. Setting this property to static tells the system to always look in the same memory location for its value. When the component Awake()s, it checks the value of the static instance and sets or destroys the value as appropriate.
  5. It persists between scenes by running the DontDestroyOnLoad() method on its GameObject.

In the game editor/UI, you would use this compent like so:

game editor ui showing a GameObject with 2 AudioSource components, and one SoundManager componment.
GameObject with 2 AudioSource components, and one SoundManager component.

To create this setup, follow these steps:

  1. Create an empty Game Object and name it “SoundManager”, or whatever you want to name it.
  2. Add two Audio Source components to the game object.
  3. Add our new Sounce Manager component to the game object.
  4. Click and drag the first Audio Source component, then drop it in the “Effects Souce” property of the Sound Manager component.
  5. Click and drag the second Audio Source component, then drop it in the “Music Source” property of the Sound Manager component.

Now you’re ready to use it!

Scripting with SoundManager Singleton

Since this object never destroyed between scenes and enforces only a single instance of itself ever exists we can use this object directly from anywhere in the rest of our game code. We just need to access the SoundManager.Instance object directly.

Here is how you might use it from another script to start playing whatever AudioClip is assigned to “BattleMusic”.

using UnityEngine;

public class GameThingy : MonoBehaviour
{
    public AudioClip BattleMusic;

    void Start() {
        SoundManager.Instance.PlayMusic(BattleMusic);
    }
}

Pretty straight foward!

And here is an example of how you might use it to play random sound effects:

using UnityEngine;

public class Character : MonoBehaviour
{
    public AudioClip[] AttackNoises;

    void Update() {
        if (Input.GetKeyDown(KeyCode.SPACE))
                SoundManager.Instance.RandomSoundEffect(AttackNoises);
    }
}

Conclusion

Finding that tutorial was great for a number of reasons, but my main takeaway was remembering that I know patterns (anti or otherwise) that are generally applicable to various languages and projects. Next time I need to solve a seemingly complex problem in Unity, I’ll think about what I already know and how it might be applied before searching. Seems like I have to re-learn this lesson in every new language… ¯\_(ツ)_/¯

About the Author

Jonathan Daggerhart

Long time Drupal and WordPress developer. I like to write modules and plugins, and I dabble in frontend and design.

Leave a Reply

Your email address will not be published. Required fields are marked *