GameDevHQ — Day 33 — NovaStar Dev Diary: Audio and Singletons

Aloha!

Today in continuing with our retrospective and logging of the development of the NovaStar project I will be detailing some of the implementation of the audio for the game. Before we get into it I would like to plug our project which was published on itch.io and can be played here:

Implementing Basic Audio

When we first started developing the game and were working on our own separate parts locally the method that was used to implement the audio on my personal version of the game was to provide each prefab that would need audio with an audio source component that could be given an audio file which would then be played when commanded by a script. The method I used to run this audio was by specifying the audio clip that would be played and assigning it to the clip portion of the AudioSource component using the command:

Once the clip has been assigned we can play the audio clip once using the command:

However for audio that need to be looped such as music or the constant hum of the charge laser we can activate the loop boolean of the AudioSource component using the GetComponent() command as before:

GetComponent<AudioSource>().loop = true;

This issue with this method is that every audio source operates independently of any other which means if we wanted to lower the overall volume of the game we would have to go in and change the volume levels of all the game objects separately. This is a really clunky way to go about it so my teammate Ryan Yamura developed a method that would allow us to control all of the audio in the game at the same time and to allow the player this same level of control. The solution for this was to include an audio mixer component into the scene along with creating a Singleton script to allow for streamlined scripting of most audio actions. In the following sections I will attempt to detail how these systems that Ryan implmeneted work and if you would like to read up on his take on the process you can find his Medium blog post regarding this topic here:

Creating an Audio Mixer

To create an audio mixer prefab from the Create menu. This mixer will allow you to create different channels that control different aspects of the audio that you assign to them. In our project the mixer has three channels: Master, Background Music (BGM), and Sound Effects (SFX). These are mainly just labels and to have these channels control their respective portions of the audio we need to assign them as the controls for each sound we implement.

The Audio Mixer menu

To do this we need to create Audio Sources for the scene that will act as the components that will play the audio clips that we are using. To do this Ryan set up an empty object that would act as the container parent for two other empty objects that are assigned an audio source for the BGM and SFX channels. In the output field for each audio source we would then assign them their corresponding mixer channel.

Example setup of the BGM Audio Source with the BGM mixer as the assigned output.

Doing this assignment means that any audio that is player through these audio sources are subject to the volume levels of their assigned mixer channels. This means that if we wanted to fully silence all music in the game we can simply turn the volume for the BGM mixer to 0 and all audio sources assigned to the channel will be muted. Additionally to allow the player and developers to control all of the audio in the game at once Ryan also implemented the Master channel that acts as the parent container for both the BGM and SFX channels. This means that any changes made to the master volume apply to both the BGM and SFX channels. This allows us to manipulate the volume of all the game audio while preserving the audio balance settings for the two subchannels.

Establishing this master channel is useful as we can now allow the player to have some control over this mixer during runtime. To do this a canvas component was made to enable an options menu that included interactable sliders that could be manipulated to adjust the values for the master volume and brightness of the scene.

The interactable volume slider that changes the value of SetVolume in the MainMenu script when changed.

When this slider is adjusted it changes between a float value of -54 (minimum volume) and 0 (maximum volume) and gives this float value to the SetVolume() method in the MainMenu script as a parameter to adjust the volume value of the Master audio mixer.

The script assigning the slider value to the mixer volume and the inspector assigning the Master channel as the adjusted channel.

This means that we now have a working volume option within the game that player can adjust from either the main menu or the pause screen.

(Top) The options menu accessible from the main menu screen. (Bottom) The options available in the pause screen.

Implementing a Singleton

With the audio controls in place we now needed a way to easily apply every sound that is played under the control of the audio mixer. The simple but inefficient way of doing things would be to write methods for every script that would use sounds and provide them with a Audio Source that is linked to the mixer. This would work but would result in a lot of extra work that we don’t need to do for a majority of sound instances. Instead we could use a type of script called a “Singleton” to handle most of the heavily lifting for audio assignment and playing. A singleton is a pattern that a class that is designated as a singleton will by the single available instance of that class at any given time that is accessible on a global level. This means that if I make a singleton named “AudioManager” I should be able to access that script directly by calling the class “AudioManager” without having to get the component or assign it as a value of a variable within every script that would need it.

In our 1.0 version of the project Ryan created a singleton that will act as the primary controller for most audio instances that will be played. The singleton works by searching for any audio files that are passed to it on Awake() and then possesses two methods that handle either playing a sound once or looping it repeatedly.

This setup will take in any audio that is given, pass them to either the BGM or SFX objects in the scene, and then play them using the Audio Source component that is attached to those objects. Since these objects use the Master mixer for volume control this means that any sounds played through this method will also be effected by any volume changes made on their corresponding mixer channel. This also means we will not need to add an Audio Source component to every object that will need to play sounds as that will be handled by the Audio Sources on the BGM and SFX objects.

Once the singleton has been created I was able to use this as a way to set up the sound for most objects in the game by issuing the command:

This command passes the given sound clip to be played by the audio source and plays the sound clip once. I we wanted to have a clip loop such as with music we would use the command:

This allows the given audio clip to play on a loop but did provide some minor issues that we had to design around. The primary issue being that if the audio source is playing a looping sound clip it will not be able to play a secondary looping sound. This became an issue with trying to play the music for the game alongside attempting to play a looping sound effect for the charge laser. Trying to do so would result in one audio clip overwriting the other and only one would be playing on loop in the end. To solve this issue the laser beam and the charging sprite were given special treatment and given their own audio sources that would play independently from the audio sources being used by the singleton. The two scripts that would control these functions would use a coroutine to assign and play these clips on loop until the object was destroyed on deactivation.

This setup allowed the looping audio files to play alongside the looping music but one last detail had to be covered which was that the new audio sources were not effected by the global mixer. To remedy this we had to assign the output of both prefabs to the SFX channel of the main mixer. Doing so allowed us to modify the volume of these looping sound effects alongside the rest of the game audio.

Implementing the Audio

Once this system was fully implemented I was given the task of assigning the audio to different aspects of the game which included:

  • Hit sounds for both the player and enemies.
  • Explosions that play on both player and enemy death.
  • Sound effects whenever the enemy/player fire their weapons.
  • Unique sound effects for almost every player weapon type.
  • Looping sound for both the charging and firing laser beam.

Each aspect used the singleton for playing their audio except for the aspects related to the laser beam. To get the music for the game we used the free asset libraries provided by OpenGameArt.org. This was an especially fun process as I was given some creative freedom to develop what the sound profile would be for our game and to find songs and develop audio that would compliment it. One such example was the boss song simply titled “Orbital Collossus” that I felt fit perfectly with our game’s aesthetic. The song was created by Matthew Pablo and can be found at this link. Sound effects were also sourced from that site but were also modified by me using Audacity to create some of the loops and for the laser beam and to create some variety in the shot sounds using what audio clips I felt I could use. We wanted to ensure that both the audio assets we were using were both royalty free and open to use but we also wanted to make sure the creators of these assets received credit for their work which is why we implemented a credits screen to reference all of the parts that we used for the project.

NovaStar Credits Screen

This is one of the bigger takeaways that I hope most people who read this post will get is that when using assets you didn’t create you must make sure that you have the permission to use the assets either due to author consent via a royalty free platform or from contacting the creator directly and receiving permission. Additionally it is important to credit your sources for any assets that make it into the final product. Failure to perform these steps can lead to potential legal trouble down that line.

With that I will be closing out today’s post and I hope it has been informative in some way. Tomorrow I want to go into detail of our version control process with Git and how we organized our development. Mahalo for reading and I hope you all have a great day. Aloha!

— Kurt

An aspiring game-dev from Hawai’i. Previous experience was in programming and animation work.