In this chapter, we are going to add background music to our game. Then, we will make our music fade in when the level starts, fade out when the level is completed, and stop if the player dies. After that, we will be using all the UI skills we have learned so far to create a pause screen and add some slider components to it (which will be used in the next chapter for volume controls). With the pause screen built, we will make our game pause by freezing the player, the enemies on the screen, bullets, and the moving textures. Also within the pause screen, we will be giving the player the option to resume play or quit so that the game goes back to the title screen with the use of Event Listeners, which we learned about in Chapter 9, Creating a 2D Shop Interface and In-Game HUD. Finally, we will be providing a mini mock test with 20 questions to cover what we have learned from this chapter, as well as previous ones.
By the end of this chapter, we will be able to make changes to the AudioSource component directly within our script. We will know how to make every GameObject stop moving on the screen for our pause screen. Finally, we will know how to create a more fulfilling experience by adding toggle and slider components.
The following topics will be covered in this chapter:
In terms of the Unity Programmer Exam, the next section will label the core objectives that will be covered in this chapter.
The following are the core exam skills that will be covered in this chapter:
Programming core interaction:
Developing application systems:
Programming for scene and environment design:
The project content for this chapter can be found at https://github.com/PacktPublishing/Unity-Certified-Programmer-Exam-Guide-Second-Edition/tree/main/Chapter_10.
You can download the entirety of each chapter's project files at https://github.com/PacktPublishing/Unity-Certified-Programmer-Exam-Guide-Second-Edition.
All the content for this chapter is held in this chapter's unitypackage file, including a Complete folder that holds all of the work we'll carry out in this chapter.
Check out the following video to see the Code in Action: https://bit.ly/3kjkSBW.
In this section, we are going to look at adding background music to our game levels. We will also be updating our scripts so that our music volume changes at different points of the game.
In the following sections, we are going to do the following:
So, let's make a start and add our game music to the level1 scene.
In this section, we are going to update the GameManager game object so that it holds a new game object (called LevelMusic) as a child in the Hierarchy window. We will then assign LevelMusic's AudioSource component and an MP3 fileto play. This kind of setup is ideal for a simple game; otherwise, we potentially run the risk of adding another manager, which is only suitable for a bigger and more complicated game.
To create a game object and add a music file to it, we need to do the following:
With the LevelMusic game object still selected, we can now drag our lvlMusic MP3 file from Assets/Resources/Sound in the Project window into the AudioClip parameter, as shown in the following screenshot:
Now is a good time to save our GameManager prefab by selecting it in the Hierarchy window and clicking on Overrides | Apply All in the top-right corner of the Inspector window.
If we now click Play to play the game from the level1 scene, the game will start to play music. This is because, by default, the Audio Source component is set to Play On Awake. This is good, but it won't stop playing until the scene changes, which is enough for most games. However, we want to add control to the music's volume via scripting.
In the next section, we are going to update the ScenesManager script and control when and how our music will be played.
In this section, we are going to ensure that our game music is no longer set to its default Audio Source setting of Play On Awake. We want the music to be aware of when to play, when to fade the volume down, and when to stop. These three states for the music are connected to the actions of when a game level starts, when the player completes a level, and when the player dies. So, it would be a fair judgment of the three music states to add the music code to the ScenesManager script as it is relatively connected.
To add our three music states (play, stop, and fade down), we need to do the following:
public MusicMode musicMode;
public enum MusicMode
{
noSound, fadeDown, musicOn
}
We covered enums back in the Setting up our scene's manager script section in Chapter 3, Managing Scripts and Taking aMock Test; the principles are the same as labeling our states. For our enum, we have assigned it a data type name of MusicMode.
Now that we have our three states labeled, we need to put these into action. We need to make our three states carry out their intended actions:
At various points of the game, we will want these states to be triggered, and the best way of accessing these short sets of states is to use a switch case to funnel out each outcome.
Now, we need to add a switch statement for our three music states.
Still inside the ScenesManager script, we are going to add an IEnumerator that will act on either state. We covered StartCoroutine/IEnumerator in the Setting up our EnemySpawner script section in Chapter 2, Adding and Manipulating Objects.
So, because we are adding an IEnumerator, we also need to add an extra library to support this functionality:
using System.Collections;
I'm going to place my IEnumerator just outside of the scope of the Update function and name it MusicVolume, where it takes the MusicMode data type, and we will refer to it as musicMode:
IEnumerator MusicVolume(MusicMode musicMode)
{
switch (musicMode)
{
case MusicMode.noSound :
{
GetComponentInChildren<AudioSource>().Stop();
break;
}
case MusicMode.fadeDown :
{
GetComponentInChildren<AudioSource>().volume -=
Time.deltaTime/3;
break;
}
case MusicMode.musicOn :
{
if (GetComponentInChildren<AudioSource>().clip != null)
{
GetComponentInChildren<AudioSource>().Play();
GetComponentInChildren<AudioSource>().volume = 1;
}
break;
To close the switch statement, we add our yield return with a fraction-of-a-second delay to give our game time to change the settings from the switch statement:
}
}
yield return new WaitForSeconds(0.1f);
}
Now that we have created our enum musicMode states and set up what each of them will do when triggered in the IEnumerator, we can move on to implementing the coroutines to make changes to the music.
In this section, we are going to continue making changes to our ScenesManager script and add StartCoroutines to specific parts of our code with the musicMode state, which is where our music's volume is going to change. So, for example, if the player dies in the game, we want the music to stop immediately by using the noSound state.
Let's make a start on this by loading our music into the game level, follow these steps:
if (GetComponentInChildren<AudioSource>().clip == null)
{
AudioClip lvlMusic = Resources.Load<AudioClip>
("Sound/lvlMusic") as AudioClip;
GetComponentInChildren<AudioSource>().clip = lvlMusic;
GetComponentInChildren<AudioSource>().Play();
}
Our if statement makes a check to see whether the audio clip of our LevelMusic's AudioSource is empty (null). If it doesn't have an audio clip, the if statement will carry out the following roles:
Now that our music plays when we start a level, we need to make it such that when a level is completed, the music fades out. This part is fairly simple as we are in the correct method to fade the game music out when a level is completed.
Scroll down to the //if level is completed comment and add the following line of code to fade the game music out when a level is completed:
StartCoroutine(MusicVolume(MusicMode.fadeDown));
The last thing to do within the switch statement is to add a line of code that resets the audio clip to null as a failsafe:
default :
{
GetComponentInChildren<AudioSource>().clip = null;
break;
}
Now, if our GamerTimer method is called and none of the cases (our player isn't on level 1, 2, or 3) apply, our player is likely to be on the title, game over, or bootup scene, which means we will not play any level music.
Now, we will look at how to use StartCoroutines.
Now, we need to learn how to stop and start the music, typically when the level is about to start or abruptly ends (typically when the player dies). Still inside ScenesManager, go back to the methods that will need updating so that they can support the music settings. Follow these steps:
The first method we will be updating is ResetScene. Within the scope of the method, enter the following code:
StartCoroutine(MusicVolume(MusicMode.noSound));
This will make a call to the MusicVolume IEnumrator to turn off the music. The following code block shows how the ResetScene method looks after it's been updated:
public void ResetScene()
{
StartCoroutine(MusicVolume(MusicMode.noSound));
gameTimer = 0;
SceneManager.LoadScene(GameManager.currentScene);
}
The next method we are going to update is the NextLevel method. We can start the music at any time, irrespective of where the player is. We can play it whenever we want with the following code:
StartCoroutine(MusicVolume(MusicMode.musicOn));
The following code block shows what the NextLevel method looks like when the code has been updated:
void NextLevel()
{
gameEnding = false;
gameTimer = 0;
SceneManager.LoadScene(GameManager.currentScene+1);
StartCoroutine(MusicVolume(MusicMode.musicOn));
}
Now, we'll move on to the Start function, which works as a failsafe for starting a scene and to see whether it should be playing music.
Whenever the ScenesManager script is active, it will automatically attempt to play music from our LevelMusic game object's AudioSource component.
If AudioSource doesn't contain a valid AudioClip (no MP3 found), then our code will presume the level the player is on doesn't require music.
The following code block shows the Start function in its entirety with the added StartCoroutine:
void Start()
{
StartCoroutine(MusicVolume(MusicMode.musicOn));
SceneManager.sceneLoaded += OnSceneLoaded;
}
The last method to update is OnSceneLoaded. When a level is loaded, we will attempt to turn the music on. The following code block shows the OnSceneLoaded method with the added StartCoroutine at the top:
private void OnSceneLoaded(Scene aScene, LoadSceneMode aMode)
{
StartCoroutine(MusicVolume(MusicMode.musicOn));
GetComponent<GameManager> ().SetLivesDisplay(GameManager.
playerLives);
if (GameObject.Find("score"))
{
GameObject.Find("score").GetComponent<Text>().text =
GetComponent<ScoreManager>().PlayersScore.ToString();
}
}
Save the script and the bootUp scene.
Our code for manipulating music is complete for our level scenes.
In this section, we updated our GameManager so that it holds a second game object called LevelMusic. This LevelMusic game object will hold an AudioSource component that can be manipulated when the player starts a level, completes a level, or dies via the ScenesManager script.
In the next section, we will add a pause screen to our game and learn how to adjust the volume of our music and sound effects, and much more.
Currently, we aren't able to pause the game, nor do we have an options screen that allows us to manipulate the settings of the game. In this section, we are going to combine these ideas so that our game is capable of pausing and we will also be able to change the volume of the music and sound effects.
In this section, we are going to do the following:
The end result of the pause screen can be seen in the following screenshot:
Let's make a start by focusing on the visuals of the pause screen. Then, we will hook up the sliders and buttons.
To start with the pause UI visuals, we need to do the following:
PauseContainer now needs to be scaled to the size of the game screen so that whatever is a child of this game object can be scaled to the correct scale and position.
That's our PauseContainer created and set to hold two main game objects. The first game object will house all of the pause screen's individual buttons and sliders. The second game object is for the pause button in the top-left corner of the screen and will make the game pause and bring the pause controls up.
The following screenshot shows our game with the pause button in the top-left corner of the screen:
But let's stay focused on the pause screen and its content before we work on the in-game pause button. To create a PauseScreen game object that will house the game objects, we need to repeat a similar procedure for PauseContainer in terms of our Rect Transform properties.
To create and house a PauseScreen game object in PauseContainer, follow these steps:
We can now make a start by filling our PauseScreen game object with its own game objects.
Let's start dimming the screen so that the player isn't distracted when the game is paused.
To create a dim effect, follow these steps:
We will now have a sheet of semi-darkness across the screen.
Now, let's add the Pause text. To do that, follow these steps:
Next, we need to add the Text component and set its properties for the PauseText game object.
If you require more information on Text Component, check out the Applying text and images to your scenes section in Chapter 8, Adding Custom Fonts and UI.
The following screenshot shows what the Hierarchy and Scene views currently look like:
We have our pause title customized and centered. Now, let's move on to some sliders for the Music and Effects volume settings. We'll make a start on the Music slider and then duplicate it to the other side of the screen for the Effects slider.
In this section, we are going to give the pause screen its title name and create and customize the pause screen volume sliders for our game's music and its sound effects.
To create, customize, and position the Music slider, follow these steps:
We will now change the color of the Music slider's bar to make it look more suited for the pause screen. We'll do this by changing it from light gray to red.
To change the color of the slider, do the following:
If you still have the Fill game object selected, you can view the slider's red backgroundby adjusting the Value slider at the bottom of the Slider component in the Inspector window, as shown in the following screenshot:
Also, as shown in the previous screenshot, we need to set the slider's Min Value to -80 and its Max Value to 0. The reason for this is that in the next chapter, these will match the same values as the Audio Mixer.
The Music slider is set to the right size; we just need to tweak the handle so it isn't so stretched and is easier to click or drag with our fingers. Follow these steps to do so:
The following screenshot shows what our handle looks like now:
The Music slider is now set. This means we can move on to the text so that we can label the slider for the player. To give the slider its own UI text, we need to do the following:
Our pause screen is starting to take shape. The following screenshot shows what we currently have:
We can now copy and paste our music text and slider to the other side of the screen and tweak some of its property values so that it will be identified as the sound effects volume bar.
To duplicate and tweak the music text and slider, do the following:
Next, we can move our Effects slider over so that it sits below the EFFECTS text in our scene view. To do this, follow these steps:
We are nearly done with our pause screen in terms of its visual elements. The last two things we have to configure are the Quit and Resume buttons. As with the slider game objects, we can make one, copy and paste it to create a second, and then edit them.
To create and customize a Quit button, do the following:
Next, we can customize it by changing the button's sprite, color, and text. We'll start with the button's sprite by taking off the curved corners that we can see in the previous screenshot.
With our Quit button still selected, we can remove the single sprite by doing the following:
Next, we'll change the color of the buttons, as follows:
The next thing we need to do to the button is to change its text.
We will be left with a button that looks more fitting for our game:
The last thing to do in this section is to duplicate the Quit game object we have just created and rename the text RESUME. The Resume button will be used to cancel the pause screen and let the player continue playing the game.
To create the Resume game object, we will need to do the following:
All that's left for the Resume game object is to rename its text from QUIT to RESUME by expanding the Resume selection by clicking on its arrow on the left in the Hierarchy window. Follow these steps:
The pause screen is now visually complete and can support various screen ratios thanks to the use of our Anchors from our Rect Transform properties. Earlier, we mentioned that we will have a pause button in the top-left corner of the game screen so that we can pause our game and load up the pause screen that we've just made.
Everything we did in this section was achieved within the Unity Editor without the use of any code. In this section, we covered the following topics:
Now, let's make the pause button. After that, we can start looking at hooking all these sliders and buttons up with our code.
At the beginning of the previous section, we briefly spoke about the in-game pause button. This button will appear at the start of a level and once pressed, the player, enemies, and bullets that have been fired will freeze in time. In this section, we will only be focusing on the visuals, just as we did with our pause screen in the previous section.
The pause button will act slightly differently from the previous buttons we have made. This time, the button will be an on-or-off-type button. The game object for this will be a toggle as it is more suited to our needs. To make a toggle game object, do the following:
To alter the current look of the PauseButton game object so that it looks like the prospective one in the preceding screenshot, we need to do the following:
The Toggle label will be removed.
The toggle will now be placed and scaled to the top-left corner of the game canvas, as circled in the following screenshot:
The Background game object is now the same size as the PauseButton game object with regard to the Anchor size.
We can now start tweaking the size and filling the Background with a suitable image. We'll replace the white-square-with-a-tick icon with a dark circle. Follow these steps to do so:
The square has now become a circle. Now, we can alter its color so that it matches the rest of our game's UI.
Next, we can make the gray oval shape into a circle.
Still in the Image component, set Image Type to Simple and tick the Preserve Aspect box, as shown in the previous screenshot.
Image Type offers different behaviors to an image; for example, Sliced works well as a progress bar/timer to increment how much of the image can be seen over time.
Preserve Aspect means that no matter which way the image is scaled, it will remain in its original form – there will be no squashed or stretched-looking images.
Here is a close-up view of PauseButton in the Scene view:
Now, we need to replace the tick image with a large pause symbol. Follow these steps to do so:
The Scene window should look something like this, with our pause button in the top left:
Finally, to make it so that the toggle button actually does something when we click on it, we need to make sure we have an EventSystem in our Hierarchy window. This is very simple to do; follow these steps:
In this section, we mixed our UI images, buttons, text, and sliders on one screen that supports various landscape variations.
In the next section, we are going to move on to the scripting side of what each of the UI components we made in the pause screen will do when the player presses the buttons or moves the slider.
The PauseComponent script will have the responsibility of managing anything to do with accessing and altering the conditions the pause screen gives the player. Here, we will follow a series of subsections that will take us through setting up individual segments of the PauseComponent script. Before we do that, though, we need to create our script. If you don't know how to make a script, then revisit the Setting up our camera section in Chapter 2, Adding and Manipulating Objects. Once you've done that, rename the script PauseComponent. For maintenance purposes, store your script in the Assets/Script folder in the Project window.
Now, let's move on to the first subsection of the PauseComponent script by applying logic to the in-game pause button.
In this section, we are going to make the pause button appear when the player has control of the game in the level. When the player presses the pause button, we need to make sure that all the moving components and scrolling textures freeze. Finally, we need to introduce the pause screen itself.
If we start the level in its current state, we will see that the PauseScreen game object overlays the screen. This looks great, but we need to turn it off for the time being. To turn off the PauseScreen game object, do the following:
using UnityEngine.UI;
using UnityEngine;
public class PauseComponent : MonoBehaviour
{
[SerializeField]
GameObject pauseScreen;
}
[SerializeField] will keep the pauseScreen variable exposed in the Inspector window as if it were public. The second line is a GameObject type that will store a reference to the entire PauseScreen game object.
Back in the PauseComponent script, we can now turn off the PauseScreen game object at the beginning of the level and turn it back on when the player presses the pause button. To turn PauseScreen off, we can do the following:
void Awake()
{
pauseScreen.SetActive(false);
}
We can now test it in the Editor when we press the Play button at the top of the screen. The game will run without the pause screen being shown. Now, we can focus on introducing the pause button to the player within a few seconds as the level begins.
Let's start by creating a method that will turn off/on the visuals and the interactability of the pause button for the player:
void SetPauseButtonActive(bool switchButton)
{
With our PauseComponent script being attached to the PauseContainer game object, we can easily access any of the game objects and their components. The other two main game objects attached are PauseScreen and PauseButton. The next few pieces of code we will add to our SetPauseButtonActive will relate to the visuals and interactivity of the PauseButton game object.
ColorBlock col = GetComponentInChildren<Toggle>().colors;
Enter the following code just after the line of code we entered previously:
if (switchButton == false)
{
col.normalColor = new Color32(0,0,0,0);
col.highlightedColor = new Color32(0,0,0,0);
col.pressedColor = new Color32(0,0,0,0);
col.disabledColor = new Color32(0,0,0,0);
GetComponentInChildren<Toggle>().interactable = false;
}
The preceding code shows that we run a check to see whether the bool parameter is false.
The screenshot on the left in the following figure shows the code we've just entered. The screenshot on the right is the Toggle component with the properties we have changed in our if statement:
If switchButton is set to true, we set the values from all zeros to their chosen color values and make the PauseButton intractable.
else
{
col.normalColor = new Color32(245,245,245,255);
col.highlightedColor = new Color32(245,245,245,255);
col.pressedColor = new Color32(200,200,200,255);
col.disabledColor = new Color32(200,200,200,128);
GetComponentInChildren<Toggle>().interactable = true;
}
The last two lines after this piece of code are applying the col value back to the Toggle component.
The second line of code turns the pause symbol on or off. If this wasn't set, then the pause button would appear/disappear without affecting the two white pause stripes.
GetComponentInChildren<Toggle>().colors = col;
GetComponentInChildren<Toggle>()
.transform.GetChild(0).GetChild(0).gameObject.SetActive
(switchButton);
}
void Awake()
{
pauseScreen.SetActive(false);
SetPauseButtonActive(false);
Invoke("DelayPauseAppear",5);
}
Here, I have added two extra lines of code in the Awake function. SetPauseButtonActive(false) turns the pause button off with the method we've just made, while the Invoke function will delay for 5 seconds until we run the DelayPauseAppear method. Inside DelayPauseAppear is SetPauseButtonActive(true), which is the time when our player gains control of their ship.
void DelayPauseAppear()
{
SetPauseButtonActive(true);
}
Back in the Unity Editor, press Play; our game will start normally and after 5 seconds, the pause button will appear in the top-left corner. If we press the pause button, it will break and nothing extra will happen. This is because we haven't made the pause button do anything when it is pressed.
Let's return to the PauseComponent script and add a small method that can run when the pause button is pressed. To add a pause method that freezes the game and brings up the pause screen we built earlier, follow these steps:
public void PauseGame()
{
pauseScreen.SetActive(true);
SetPauseButtonActive(false);
Time.timeScale = 0;
}
timeScale can also be found in Time Manager in the Unity Editor. This is located at the top of the Editor window, under Edit | Project Settings | Time.
You also have other useful properties such as Fixed Timestep, where you can change its value to make your physics simulation more precise. For more information about Time Manager and its properties, check out the following link: https://docs.unity3d.com/Manual/class-TimeManager.html.
Now, we need to attach the new PauseGame method to the PauseButton event system, as follows:
The following screenshot shows the marked-out steps we have gone through in selecting the PauseGame () method:
Now would be a good time to try and see whether the pause screen appears when we press the pause button. Press Play in the Unity Editor and in the Game window, press the pause button in the top-left corner when it appears. The pause screen will appear; we won't be able to escape from this until we code in the logic for our Resume and Quit buttons.
So far in this section, we have given the player the ability to pause the game. In the next section, we will make it so that the player will be able to resume or quit the game from the pause screen.
In this subsection, we will continue to extend the PauseComponent script by adding two methods:
Let's make a start by adding the logic for the Resume button; follow these instructions:
public void Resume()
{
pauseScreen.SetActive(false);
SetPauseButtonActive(true);
Time.timeScale = 1;
}
This code is similar to the code shown in the previous section; it's just in the opposite order (instead of the value being set to true, it's now false and vice versa to bring back the original settings).
public void Quit()
{
Time.timeScale = 1;
GameManager.Instance.GetComponent<ScoreManager>(). ResetScore();
GameManager.Instance.GetComponent<ScenesManager>(). BeginGame(0);
}
The code we've just entered resets the game; the timescale value goes back to 1. We reset the player's score from ScoreManager directly and also directly told ScenesManager to take us back to scene zero, which is our bootUp scene.
This is similar to the Resume button in regard to setting up an event to our script.
The following screenshot shows the Quit game object's button setup:
Before we wrap up this chapter, we need to ensure that the player and enemies behave how we expect them to when the game is paused.
So, we have reached the point where we can press our in-game pause button and watch our game freeze in time. To make sure the scene is saved, including new and edited scripts, let's test the pause screen:
Let's fix the enemy floating first.
void Update()
Change this to the following:
void FixedUpdate()
Further Information
More information about FixedUpdate can be found here: https://docs.unity3d.com/ScriptReference/MonoBehaviour.FixedUpdate.html.
Now, let's reinforce the player's behavior so that all of its functionality is frozen when the game pauses. To freeze our player, we need to reopen its script:
void Update ()
{
if(Time.timeScale == 1)
{
Movement();
Attack();
}
}
The preceding code runs a check to see whether the game's timeScale is running at full speed (1) and then carries on with the Movement and Attack methods.
Great! We now have the ability to pause our game, continue our game, or quit it. Don't worry about adding this pause screen to the rest of the levels as we will do this in the next chapter. Speaking of the next chapter, there, we will look at how we can change the Music and Effects sliders. For now, let's reflect on what we have covered in this chapter.
By completing this chapter, our game has improved even more and now has a pause screen, just as you would expect from any game. We also learned how to freeze time in our game with the timeScale value. We did revisit some things we covered in previous chapters such as Event Listeners and UI positioning and scaling, but we also used other UI components such as toggles and sliders and modified them to suit our pause screen. Other things we covered included bringing in some MP3 music and making it so that the script knew when to fade in and out and stop the soound.
In the next game you create outside of this book, you will know not only how and when to add background music to play when it's playing but also how to attach your audio to a state machine. With state machines, you can make it possible for your music to be played, stopped, and faded out when particular moments occur, such as the game's screen being paused. Now, you will be able to take the UI components you've learned about in this chapter and create your own menu/pause screen. By doing this, you can run events to close or resume your game. You also know how to pause your game completely and/or slow time down with the timeScale function.
In the next chapter, we will be looking at Unity's Audio Mixer to control the volume of our player's bullets and music and hook it up to our pause screen volume sliders. We will also look into different types of data that need to be stored, such as our game remembering the volume settings so that we don't have to adjust the sliders every time we start our game.
For now, I wish you the best of luck with your mini mock test!
Which time property isn't affected when we set Time.timeScale to 0?
What should the Transition field of the button be set to in the Unity Inspector to support these image changes?
What's the best way of amending the font to make sure it doesn't appear squashed?
What property value could potentially cause this in the enemy's Animator component?
In order to avoid the tomato plants appearing repetitively, some of the artists have turned off the tomato game objects so they can't be seen.
At the start of the scene, we need to count how many tomatoes are in the scene, including the hidden ones.
Which command would get a reference to all Tomato scripts?
What audio property would you add for this game?
Which property in the Audio Source component will fix this so that your music doesn't cut out?
In the Canvas Scaler component, which property in UI Scale Mode will make UI elements retain the same size in pixels, regardless of screen size?