Quick and Simple Audio Player for Playing Audio Clips Easy!

Implementing Sound inside a Game is  mandatory for its Success. People love Sound Feedback in all Ways. Its exciting, thrilling, frightening or motivating.  The reason for Sounds are various and to express Feelings and Emotions inside a Game.

Try our Free Unity Audio Asset, Easy Audio Scene. It let’s you quickly implement a full Audio Controller with adjustable Volumes and Audio Player that Pools Audio Sources in your entire Unity Game Project.

Creating a Audio Player with Audio Source Pooling

Play a AudioClip after Time

First we need to create a timer class that can be used by any point in our assembly, and then we will add a manager that we will use to initialize our systems.

Create a Timer Class

This class is handy in many cases and can be used for many different aspects. We will create a Timer class and wrap a MonoBehaviour to perform the update method.

This is a basic version of a timer class in Unity, this timer class can be extended with an object pool and other useful assets to achieve better performance.

Call a Timer

				
					        //A Common Use would be to Turn off a Object  after a period of Time
        MyTimer.CreateTimer(() => GameObject.SetActive(false), 1);
				
			

A timer can be created within the assembly by calling:

MyTimer.CreateTimer( () => Action, Time).

After the time has expired, the MonoBehaviour hook we created will destroy the GameObject by itself.

This method creates an overhead on the CPU. You can avoid this by creating a pool and list of timers that can be reused.

You should keep this in mind if you use the Timer class very often.

public class MyTimer

Create  MyTimer.cs

				
					/// <summary>
/// Create a Timer on a MonoBehaviour GameObject
/// After Time runs out a Action can be called.
/// </summary>
public class MyTimer 
{
    /// <summary>
    /// We Call this Function from other Scripts to Create a Timer at Runtime
    /// </summary>
    /// <param name="action">The Action that gets Performed after the Time runs out.</param>
    /// <param name="timer">The amount of Time that has to pass until the Timer gets Triggered</param>
    /// <returns></returns>
    public static MyTimer CreateTimer(Action action, float timer)
    {
        GameObject obj = new GameObject("TimerObject", typeof(MonoBehaviourHook));
        MyTimer stTimer = new MyTimer(obj, action, timer);
        obj.GetComponent<MonoBehaviourHook>().OnUpdate = stTimer.UpdateTimer;

        return stTimer;
    }

    //We Store the GameObject so we can Destroy it later
    GameObject gameObject;

    float timer;
    Action TimeAction;


    public MyTimer(GameObject gameObject, Action action, float timer)
    {
        this.gameObject = gameObject;
        this.timer = timer;
        this.TimeAction = action;
    }

    /// <summary>
    /// Update the Time with OnUpdate from Monobehaviour.
    /// </summary>
    void UpdateTimer()
    {
        timer -= Time.deltaTime;

        if (timer > 0)
            return;

        TimeAction();
        DestroySelf();        
    }

    /// <summary>
    /// GameObject gets Destroyed after TimeAction is performed
    /// </summary>
    void DestroySelf()
    {
        GameObject.Destroy(gameObject);
    }

    /// <summary>
    /// We add a Simple Script on our GameObject
    /// With this we have the Update Method available which will Trigger our Action
    /// After a certain amount of Time.
    /// </summary>
    class MonoBehaviourHook : MonoBehaviour
    {
        public Action OnUpdate;

        private void Update()
        {
            if (OnUpdate != null) OnUpdate();
        }
    }
}
				
			

public static class AudioPlayer

Create  AudioPlayer.cs

				
					/// <summary>
/// A Static Class we dont need to Create
/// and is available on the entire Project
/// AudioPlayer.InitializeAudioPlayer(amount, maxamount)
/// </summary>
public static class AudioPlayer
{
    /// <summary>
    /// Create a AudioShot from the AudioObject Pool and pass the Position if necessary.
    /// </summary>
    /// <param name="_audioShot"></param>
    /// <param name="position">Activate the Audio Source on the defined Position.
    /// When Position is used to set the AudioSource to 3D.
    /// If Position is null the Sound gets Played as 2D Sound</param>
    public static void CreateAudio(AudioShot _audioShot, Vector3? position = null)
    {
        AudioShot audioShot = _audioShot;

        for (int i = 0; i < AudioSources.Count; i++)
        {
            if (AudioSources[i].gameObject.activeInHierarchy)
                continue;

            if (position != null)
                AudioSources[i].transform.position = (Vector3)position;

            AudioSources[i].clip = audioShot.audioClip;
            AudioSources[i].volume = audioShot.audioVolume;
            AudioSources[i].pitch = audioShot.audioPitch;
            AudioSources[i].spatialBlend = position != null ? 1 : 0;
            AudioSources[i].gameObject.SetActive(true);
            AudioSources[i].Play();

            MyTimer.CreateTimer(() => AudioSources[i].gameObject.SetActive(false), AudioSources[i].clip.length);
            return;
        }

        //Check if we still under the Max Amount of Audio Objects
        if (AudioSources.Count >= audioObjectsMaxAmount)
            return; //End the Function and dont Play any Sound


        //Create a New Audio Pool  Object and Play
        AudioSource audio = CreateAudioPoolObject(AudioSources.Count + 1);

        if (position != null)
            audio.transform.position = (Vector3)position;

        audio.clip = audioShot.audioClip;
        audio.volume = audioShot.audioVolume;
        audio.pitch = audioShot.audioPitch;

        //When we send a Position the AudioSource gets set to 3D Sound
        audio.spatialBlend = position != null ? 1 : 0;

        audio.gameObject.SetActive(true);
        audio.Play();

        //A Common Use for a Timer, to turn of something after a period of Time.
        MyTimer.CreateTimer(() => audio.gameObject.SetActive(false), audio.clip.length);
    }

    /// <summary>
    /// Store the  AudioSource Objects for Pool
    /// </summary>
    static List<AudioSource> AudioSources = new List<AudioSource>();

    static int audioObjectsMaxAmount = 0;

    public static void InitializeAudioPlayer(int preInstantiate, int maxAmount)
    {
        //Set Max Amount of AudioObjects
        audioObjectsMaxAmount = maxAmount;

        //Create new Min Amount of AudioObjects
        for (int i = 0; i < preInstantiate; i++)
        {
            CreateAudioPoolObject(i);
        }
    }

    /// <summary>
    /// Create Audio Pool Object and Set Parent.
    /// </summary>
    /// <param name="i">Number in Pool</param>
    /// <returns>AudioSource</returns>
    static AudioSource CreateAudioPoolObject(int i)
    {
        GameObject go = new GameObject();
        go.name = "AudioPoolObject-" + i + 1;
        go.transform.SetParent(MissionAudioManager.ReturnTransform());
        AudioSource audio = go.AddComponent(typeof(AudioSource)) as AudioSource;
        //audio.outputAudioMixerGroup = mixerGroup;
        AudioSources.Add(audio);
        go.SetActive(false);
        return audio;
    }
}
				
			

Unity AudioPlayer without MonoBehaviour

We create this class as static so that we can access it throughout the project. You can change the way you call the AudioPlayer. As an example, you can create it as MonoBehaviour and use it with a singleton, or get a reference to the class on your prefered way.

To Make use of the AudioPlayer we need to Initialize the AudioObjects.
				
					    public static void InitializeAudioPlayer(int preInstantiate, int maxAmount)

				
			

The code should be pretty self-explanatory and is documented.

One of the features implemented in this AudioPlayer is that it can decide between 2D and 3D sound. If a Vector3 parameter is passed, the AudioPlayer will convert the audio source to 3D Spatial Blend.

Manage Your Audio

We create the AudioManager as a singleton instance so that we can initialize the AudioPlayer by calling the AudioPlayer.InitializeAudioPlayer function.

				
					        //Initialize the AudioPlayer with Min and MaxAmount
        AudioPlayer.InitializeAudioPlayer(AudioObjectAmount, AudioObjectMaxAmount);

				
			

Connect the AudioManager to a game object in your scene. You can set up the AudioManager to be available in any scene. When you transition between scenes, the AudioManager and its components and audio objects are transferred to the new scene, while the AudioManager in the new scene, if available, is destroyed before instantiation.

public class AudioManager : MonoBehaviour

Create  AudioManager.cs

				
					/// <summary>
/// We Are using the Mission Audio Manager as Parent Object for our AudioObject Pool
/// And Initialize the Audio Player with it.
/// The Audio Manager can be Extendet with various more Tasks such as Music and other Sounds.
/// </summary>
public class AudioManager : MonoBehaviour
{
    <a href="https://stusse-games.com/tag/region/">region</a> Singleton
    /// <summary>
    /// Regular Singleton Instance to Access the Manager Object
    /// </summary>
    static AudioManager Instance;

    void Awake()
    {
        if (Instance != null)
        {
            Destroy(this.gameObject);
            return;
        }

        Instance = this;

        //We Use DontDestroyOnLoad, also the AudioObjects will get carried
        //Into the Next Scene and dont need to be reinstantiated.
        DontDestroyOnLoad(this);

        //Initialize the AudioPlayer with Min and MaxAmount
        AudioPlayer.InitializeAudioPlayer(AudioObjectAmount, AudioObjectMaxAmount);
    }
    <a href="https://stusse-games.com/tag/endregion/">endregion</a>

    [SerializeField]int AudioObjectAmount = 20;
    [SerializeField]int AudioObjectMaxAmount = 50;

    /// <summary>
    /// Used to Set AudioObjects Parent Transform for Sorting
    /// </summary>
    /// <returns></returns>
    public static Transform ReturnTransform()
    {
        return Instance.transform;
    }
}
				
			

Important: Don't Forget to Attach the AudioManager to a GameObject in your Scene, else the entire System won't Work.

public class AudioShot

Create AudioShot.cs

				
					/// <summary>
/// Use AudioShot inside a Script and Define the Parameters in the Inspector
/// Call the AudioShot by AudioShot.Play(Vector3?) to Play an AudioClip
/// </summary>
[Serializable]
public class AudioShot
{
    //Setup the Values in the Inspector
    public AudioClip audioClip = null; //AudioClip that gets Played
    public float audioVolume = 1; //Volume the AudioClip gets Played
    public float audioPitch = 1; //Pitch the AudioClip gets Played
    [Header("Add Start Delay If Necessary")]
    public float playDelay = .0f; //If > 0 the AudioManager will Activate the AudioClip as 3D Sound


    public void Play(Vector3? position = null) => PlayAudio(position);

    /// <summary>
    /// When PlayAudio is Called the AudioShot play's itself with the defined Settings
    /// </summary>
    /// <param name="position">Pass a Vector3 Position, Sound will be 3D on that Position</param>
    void PlayAudio(Vector3? position = null)
    {
        if (audioClip == null)
        {
            Debug.LogWarning("Audio Clip Missing");
            return;
        }

        //If we have a Delay we using our Timer Class to add the Delay before the Sound gets Executed
        if (playDelay > 0)
        {
            MyTimer.CreateTimer(() => AudioPlayer.CreateAudio(this, position), playDelay);
            return;
        }

        //Execute the Sound inside the AudioPlayer
        AudioPlayer.CreateAudio(this, position);
    }
}
				
			

Finally Lets Play Audio

The AudioShot class is simple, it contains a set of settings that can be accessed in the Unity Inspector when AudioShot is declared as a field in a script.

We call the Play function to play the AudioClip in the AudioPlayer. In case of a desired delay before the AudioClip is played, we create the function then within our Timer class.

Use AudioShot

Using AudioShot is fairly simple. Just add an AudioShot field to your script and then call FieldName.Play() at the desired location in your script to play it.

				
					    [SerializeField] AudioShot audio;

    void Start()
    {
        audio.Play();
    }
				
			

Use just One Script for all

Since the classes and functions are relatively small, you can put them all in one script. But for further assets of the AudioSystem you should separate everything for better understanding.

Just 73 Lines of Executable Code

This basic audio system lets you play sounds from any point in the game. It allows quick access and even adds a timer so that audio clips can be played after a delay.

Extend the AudioManager

The next solutions for the Audio Manager could be:

  • Audio Mixer Groups to enable volume settings and audio channels.
  • 3D Sound Settings Handle to make 3D sounds more accurate.
  • Feel free to use the scripts as you like.

 

Our Free Asset Zeus Unite – Easy Audio Scene, offers your such solutions.

public class AudioManager : MonoBehaviour

Create  AudioManager.cs

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

/// <summary>
/// We Are using the Mission Audio Manager as Parent Object for our AudioObject Pool
/// And Initialize the Audio Player with it.
/// The Audio Manager can be Extendet with various more Tasks such as Music and other Sounds.
/// </summary>
public class AudioManager : MonoBehaviour
{
    <a href="https://stusse-games.com/tag/region/">region</a> Singleton
    /// <summary>
    /// Regular Singleton Instance to Access the Manager Object
    /// </summary>
    static AudioManager Instance;

    void Awake()
    {
        if (Instance != null)
        {
            Destroy(this.gameObject);
            return;
        }

        Instance = this;

        //We Use DontDestroyOnLoad, also the AudioObjects will get carried
        //Into the Next Scene and dont need to be reinstantiated.
        DontDestroyOnLoad(this);

        //Initialize the AudioPlayer with Min and MaxAmount
        AudioPlayer.InitializeAudioPlayer(AudioObjectAmount, AudioObjectMaxAmount);
    }
    <a href="https://stusse-games.com/tag/endregion/">endregion</a>

    [SerializeField]int AudioObjectAmount = 20;
    [SerializeField]int AudioObjectMaxAmount = 50;

    /// <summary>
    /// Used to Set AudioObjects Parent Transform for Sorting
    /// </summary>
    /// <returns></returns>
    public static Transform ReturnTransform()
    {
        return Instance.transform;
    }
}

/// <summary>
/// A Static Class we dont need to Create
/// and is available on the entire Project
/// AudioPlayer.InitializeAudioPlayer(amount, maxamount)
/// </summary>
public static class AudioPlayer
{
    /// <summary>
    /// Create a AudioShot from the AudioObject Pool and pass the Position if necessary.
    /// </summary>
    /// <param name="_audioShot"></param>
    /// <param name="position">Activate the Audio Source on the defined Position.
    /// When Position is used to set the AudioSource to 3D.
    /// If Position is null the Sound gets Played as 2D Sound</param>
    public static void CreateAudio(AudioShot _audioShot, Vector3? position = null)
    {
        AudioShot audioShot = _audioShot;

        for (int i = 0; i < AudioSources.Count; i++)
        {
            if (AudioSources[i].gameObject.activeInHierarchy)
                continue;

            if (position != null)
                AudioSources[i].transform.position = (Vector3)position;

            AudioSources[i].clip = audioShot.audioClip;
            AudioSources[i].volume = audioShot.audioVolume;
            AudioSources[i].pitch = audioShot.audioPitch;
            AudioSources[i].spatialBlend = position != null ? 1 : 0;
            AudioSources[i].gameObject.SetActive(true);
            AudioSources[i].Play();

            MyTimer.CreateTimer(() => AudioSources[i].gameObject.SetActive(false), AudioSources[i].clip.length);
            return;
        }

        //Check if we still under the Max Amount of Audio Objects
        if (AudioSources.Count >= audioObjectsMaxAmount)
            return; //End the Function and dont Play any Sound


        //Create a New Audio Pool  Object and Play
        AudioSource audio = CreateAudioPoolObject(AudioSources.Count + 1);

        if (position != null)
            audio.transform.position = (Vector3)position;

        audio.clip = audioShot.audioClip;
        audio.volume = audioShot.audioVolume;
        audio.pitch = audioShot.audioPitch;

        //When we send a Position the AudioSource gets set to 3D Sound
        audio.spatialBlend = position != null ? 1 : 0;

        audio.gameObject.SetActive(true);
        audio.Play();

        //A Common Use for a Timer, to turn of something after a period of Time.
        MyTimer.CreateTimer(() => audio.gameObject.SetActive(false), audio.clip.length);
    }

    /// <summary>
    /// Store the  AudioSource Objects for Pool
    /// </summary>
    static List<AudioSource> AudioSources = new List<AudioSource>();

    static int audioObjectsMaxAmount = 0;

    public static void InitializeAudioPlayer(int preInstantiate, int maxAmount)
    {
        //Set Max Amount of AudioObjects
        audioObjectsMaxAmount = maxAmount;

        //Create new Min Amount of AudioObjects
        for (int i = 0; i < preInstantiate; i++)
        {
            CreateAudioPoolObject(i);
        }
    }

    /// <summary>
    /// Create Audio Pool Object and Set Parent.
    /// </summary>
    /// <param name="i">Number in Pool</param>
    /// <returns>AudioSource</returns>
    static AudioSource CreateAudioPoolObject(int i)
    {
        GameObject go = new GameObject();
        go.name = "AudioPoolObject-" + i + 1;
        go.transform.SetParent(AudioManager.ReturnTransform());
        AudioSource audio = go.AddComponent(typeof(AudioSource)) as AudioSource;
        //audio.outputAudioMixerGroup = mixerGroup;
        AudioSources.Add(audio);
        go.SetActive(false);
        return audio;
    }
}

/// <summary>
/// Create a Timer on a MonoBehaviour GameObject
/// After Time runs out a Action can be called.
/// </summary>
public class MyTimer 
{
    /// <summary>
    /// We Call this Function from other Scripts to Create a Timer at Runtime
    /// </summary>
    /// <param name="action">The Action that gets Performed after the Time runs out.</param>
    /// <param name="timer">The amount of Time that has to pass until the Timer gets Triggered</param>
    /// <returns></returns>
    public static MyTimer CreateTimer(Action action, float timer)
    {
        GameObject obj = new GameObject("AudioShot", typeof(MonoBehaviourHook));
        MyTimer stTimer = new MyTimer(obj, action, timer);
        obj.GetComponent<MonoBehaviourHook>().OnUpdate = stTimer.UpdateTimer;

        return stTimer;
    }

    //We Store the GameObject so we can Destroy it later
    GameObject gameObject;

    float timer;
    Action TimeAction;


    public MyTimer(GameObject gameObject, Action action, float timer)
    {
        this.gameObject = gameObject;
        this.timer = timer;
        this.TimeAction = action;
    }

    /// <summary>
    /// Update the Time with OnUpdate from Monobehaviour.
    /// </summary>
    void UpdateTimer()
    {
        timer -= Time.deltaTime;

        if (timer > 0)
            return;

        TimeAction();
        DestroySelf();        
    }

    /// <summary>
    /// GameObject gets Destroyed after TimeAction is performed
    /// </summary>
    void DestroySelf()
    {
        GameObject.Destroy(gameObject);
    }

    /// <summary>
    /// We add a Simple Script on our GameObject
    /// With this we have the Update Method available which will Trigger our Action
    /// After a certain amount of Time.
    /// </summary>
    class MonoBehaviourHook : MonoBehaviour
    {
        public Action OnUpdate;

        private void Update()
        {
            if (OnUpdate != null) OnUpdate();
        }
    }
}

/// <summary>
/// Use AudioShot inside a Script and Define the Parameters in the Inspector
/// Call the AudioShot by AudioShot.Play(Vector3?) to Play an AudioClip
/// </summary>
[Serializable]
public class AudioShot
{
    //Setup the Values in the Inspector
    public AudioClip audioClip = null; //AudioClip that gets Played
    public float audioVolume = 1; //Volume the AudioClip gets Played
    public float audioPitch = 1; //Pitch the AudioClip gets Played
    [Header("Add Start Delay If Necessary")]
    public float playDelay = .0f; //If > 0 the AudioManager will Activate the AudioClip as 3D Sound


    public void Play(Vector3? position = null) => PlayAudio(position);

    /// <summary>
    /// When PlayAudio is Called the AudioShot play's itself with the defined Settings
    /// </summary>
    /// <param name="position">Pass a Vector3 Position, Sound will be 3D on that Position</param>
    void PlayAudio(Vector3? position = null)
    {
        if (audioClip == null)
        {
            Debug.LogWarning("Audio Clip Missing");
            return;
        }

        //If we have a Delay we using our Timer Class to add the Delay before the Sound gets Executed
        if (playDelay > 0)
        {
            MyTimer.CreateTimer(() => AudioPlayer.CreateAudio(this, position), playDelay);
            return;
        }

        //Execute the Sound inside the AudioPlayer
        AudioPlayer.CreateAudio(this, position);
    }
}
				
			

Lets say you Play a Sound Now

				
					    [SerializeField] AudioShot audio;

    void Start()
    {
        audio.Play();
    }
				
			

Previously, you had to add an AudioSource to your GameObject and set up the AudioSource individually. Maybe you instantiated it before, which consumed unnecessary performance, or you could only play one sound at a time because the AudioSource is attached to the GameObject that controls the audio.

We want to separate this logic from the rest of the game so that we can reuse and recycle the objects instead of recreating them or limiting the amount of audio that can be played.

We hope you find the scripts and information useful. If you have any questions, feel free to join our Discord server.

Leave a Reply

Your email address will not be published.