VG1, Quest 7 - Audio, Menus, and Pausing

Download Files

Supporting files for VG1 quests are part of a single archive that you can download here.

Continue Platformer

Be sure to have completed the prior two platformer assignments and reopen the Platformer scene.

Import Sounds

Download the "shoot", "miss", and "impact" sound files from the course files and import them into the Assets/Audio folder.

Audio Listener

Basic audio has three main contributors in Unity:

The default Main Camera comes with an Audio Listener by default.

Select your Main Camera to confirm that it already has an Audio Listener. You should never have more than one Audio Listener in your entire scene or you will get errors.

Audio Source

Your Audio Source is a gameObject component that plays the sound. Playing sounds from different objects can give a sense of relative position between the Audio Source and Audio Listener components in both 2D and 3D games.

Select your Projectile Prefab and add an Audio Source component. Make sure "Play On Awake" is checked because we want the shoot sound to play right away and set your “Spatial Blend” to 2D to match our style of game.

Drag the shoot sound file into the AudioClip blank.

Now, whenever you fire a projectile, it automatically plays the shoot sound.

SoundManager

We also want Hit and Miss sounds when the projectiles hit the Targets and Ground, respectively. We can’t have the projectiles or targets play these sounds because they (and their Audio Source components) are destroyed right after impact.

Instead, we will create a separate SoundManager object to play the different sounds.

Create a new Empty Game Object in the scene named SoundManager. Attach an Audio Source component to it along with a new Sound Manager script. Continue to keep any code for this assignment organized in the same Platformer folder as before.

Open your SoundManager script. It will be accessed using a Static Instance Reference similar to the GameController in the past assignments.

The sound files we want to play are in the project asstes and must be referenced by our code. Create public outlets in our code to configure these asset references. Similarly, we have to create an outlet to reference the Audio Source component.

Fill in the properties created by this code.

Next, we’ll create public functions that other objects can call to trigger sounds.

In our Projectile script, we will set up the conditions for our different sounds and call the SoundManager appropriately.

Save Data

Unity can save Persistent Data by storing either a complex file format of your own design in the device’s documents or simple datatypes in the PlayerPrefs. This tutorial covers the latter.

These simple datatypes include string, int, and float.

Score Counter

We are going to update the PlayerController to save an ongoing score counter, so the player can quit the game without losing their score.

There are several changes going on at once here:

Add the TMPro using statement because we will be working with text in our UI.

Use a public static outlet to make the PlayerController easy to reference.

Separate variables for the scoreUI and the score data are required for this mechanic.

Static instance references must be prepared in the Awake event.

In the Update of our PlayerController we will update the UI with our current score.

In the Target script, update the collision event so it also adds to the player’s score.

Create a Text UI object and adjust its formatting so it shows in the top-center of the screen. Remember to create UI that scales and is always readable across diverse devices, just like previous exercises.

Don’t forget to assign the text UI outlet in the PlayerController.

Making Score Persistent

If you play the game in its current state, the score system will still reset when you restart the game.

To persistently save our score, we also need to store and update an entry in PlayerPrefs whenever score changes during our Target script.

PlayerController must also reload prior score data during its Start event.

Now when restarting the game, score returns to where the player left off instead of 0.

Menus and Submenus

Submenus are usually just nested UI objects that are turned on and off as needed. We will set up a Pause Menu with submenus for Options and Level Select. All of these menus will be contained in a UI Panel which gives us a rectangle background.

Remember to create UI that scales and is always readable across diverse devices, just like previous exercises.

By default, panels will stretch to fill the screen. Instead, we will specify a smaller portion of the screen for the menus. Change the color tint of the background image to remove the transparency, so that the menu is less noisy.

Refer to this Scene Hierarchy and these screenshots for setting up your Menus.

The "Main Menu", "Options", and "Level Select" game objects are just empty objects stretched to fill the parent:

Main Menu content (when Options and Level Select are turned off):

Options menu content (when Main Menu and Level Select are turned off):

Level Select menu content (when Main Menu and Options are turned off):

Menu Coding

Create a MenuController script in the Platformer code folder and attach it to your "Panel - Menu" object.

MenuController is in charge of all menu and submenu actions.

There will only be one MenuController, so we can use a static instance reference to make the menu easily accessible from other objects.

In order for our MenuController to control our 3 submenus it needs outlets to reference them.

Don’t forget to fill in the blanks for the outlets.

We are going to set up some utility functions for managing our menus before we code the primary menu functionality.

This SwitchMenu function manages cleanup when switching between menus ensuring other menus are properly turned off before another is turned on.

To further streamline setting up our Button component click events, we’re going to create even simpler utility functions for switching to specific menus.

We have a tricky timing scenario with the MenuController: We want it to be active early enough to set up the static instance reference but inactive before the player can see anything on screen at the start of the game. The first thing the player should see is the gameplay, not the menu.

We must have the MenuController game object active at the launch of the game in order for it to run its Awake event and set up the static instance reference. If this event does not happen, any code referencing the instance will fail and crash the game.

Since we don’t want our menu to stay visible right at the beginning of the game, the Hide function is called from the Awake after the menu has had time to set up.

In PlayerController’s Update function, the Escape key will trigger showing the menu.

If you playtest the game now, you should be able to press Escape to show the Menu Panel, and it should show the Main Menu by default. None of the other two menus should be visible.

Main Menu Coding

The Play button hides the menu and returns to the game.

The Options button switches to the Options menu.

The Levels button switches to the Levels menu.

Options Menu Coding

The Back button switches to the Main Menu.

The Reset Score button requires a new function that we will put in the PlayerController.

Hook that function up to the Reset Score button. Notice that this button targets the Character object because that is where the code is located.

Level Menu Coding

The Back button switches to the Main Menu.

The Level 1 button requires a new function. The game only has one level, so we’ll just switch to our current scene, but this feature could be expanded for as many levels as you need.

Make sure whatever scenes you reference in any sort of level select menu are also added to the list of scenes in Build Settings, otherwise, your game may throw an error when switching scenes.

Hook that function up to the Level 1 button.

Pausing Gameplay

You may have noticed gameplay still continues even when the menu is on screen. To fix this, we have to make two changes to the game:

For the first one, we will use an isPaused boolean in the PlayerController script.

Everything in the Update loop after the return statement will be skipped if the game is paused. This prevents the character from responding to keyboard and mouse input.

Finally, we freeze the time scale and update the isPaused property from the MenuController script whenever the menu is shown.

When we hide the menu, we unpause the game by doing the opposite. Remember that this code is also triggered by the Awake event when the Menu first automatically hides itself before gameplay. Because the PlayerController may not be set up yet (such as if its Awake function runs after MenuController's Awake event), we must check if the PlayerController instance is null.

Save and Test

Playtest to ensure all interactions work as expected and that the addition of any new features hasn’t broken any earlier interactions.

Submit Assignment

SAVE any open files or scenes.

Submit your assignment for grading following the instructions supplied for your particular classroom.