In this chapter, we will be looking at common ways of storing and sending data for our game. This will also involve us making use of Unity's ready-made Audio Mixer for us to store the player's volume settings for the game.
As you may recall, in the previous chapter, we had begun making our own pause screen from scratch. We will be carrying on with this in this chapter. We still need to work on the music and sound effects slider on the pause screen. We will hold all Audio Source controls for each sound to be played in the Audio Mixer. The Audio Mixer will act as a central point for all sound and can also be manipulated via scripting, which we will also be doing in this chapter. If our game had more sound effects and more music, an Audio Mixer controlling the game's sound from one place would help us avoid getting tangled up with all the different audio source components attached to game objects.
We will be making use of storing the volume settings with Unity's own PlayerPrefs, which stores data locally on the platform playing the game. This is also known as persistent data because we can turn off the machine that holds the volume information and when the machine is turned back on, the data remains on the system. We will introduce serializing our objects into data and deserializing the data back into objects. This is useful if we want to send batches of data onto a database.
In this chapter, we will be covering the following topics:
Let's get started!
The following are the core exam skills that will be covered in this chapter:
Programming core interactions:
Developing application systems:
Programming for scene and environment design:
Working in professional software development teams:
The project content for this chapter can be found at https://github.com/PacktPublishing/Unity-Certified-Programmer-Exam-Guide-Second-Edition/tree/main/Chapter_11.
You can download the entirety of each chapter's project files at https://github.com/PacktPublishing/Unity-Certified-Programmer-Exam-Guide-Second-Edition.
All content for this chapter is held in the chapter's unitypackage file, including a Complete folder that contains all of the work that we'll carry out in this chapter.
Check out the following video to see the Code in Action: https://bit.ly/3EYHpxf.
As the game grows, it's useful to have a mixer channel that focuses on all the allocated volume levels and sound effects. Otherwise, if not for a separate mixer channel, we would be clicking on various game objects and adjusting each of their components in the Inspector window.
For our game, we are going to keep this simple and create three Audio Groups with no added effects. Let's take a look at what each Audio Group will focus on:
The following screenshot shows Audio Mixer and the setup for the three Audio Groups:
Further Information
If you would like to know more about the layout of Audio Mixer, check out the documentation at https://docs.unity3d.com/Manual/AudioMixerOverview.html.
Let's now start by creating the Audio Mixer within the Unity Editor, by following these steps:
Before we hook up the mixer to the LevelMusic and Player_Bullet game objects (because these are the two game objects making the sounds), we need to go into the Audio Mixer and create Music and Effects mixers first (we only have the master Audio Group on its own at the moment).
Let's have a closer look at our Audio Mixer. To view the Audio Mixer with our MasterMixer, double-click the MasterMixer file in the Project window. We will be presented with the following screen:
The previous screenshot shows our setup for the Audio Mixer. It consists of four categories:
Don't worry too much about the details as we are going to focus mainly on groups. There is more we can do with Audio Mixers. Check the official Unity documentation to find out more at https://docs.unity3d.com/Documentation/Manual/AudioMixer.html.
To add two extra volume mixers next to our Master, we need to do the following:
The following screenshot shows what the Audio Mixer window will look like now, with all three ASVs:
Great! Now, we can move on to hooking these audio groups to the game objects we want to affect. The first one is our LevelMusic game object, which is a child of the GameManager game object.
To update our LevelMusic game object's Audio Source component, do the following:
Now, we need to do something similar for our player_bullet prefab. To update its Audio Source with the Effects mixer, do the following:
The following screenshot shows what the player_bullet game object's Audio Source should look like in the Inspector window:
Now, the Audio Mixer is nearly ready to be connected to our pause screen's Music and Effects sliders. We need to do one more thing before we move on to the next section, and that is to make the audio group's volume accessible so we can make it communicate with the pause screen's audio sliders. To do this, we need to set our Audio Mixer's Attenuation Volume property to open or expose it to our scripts.
To expose and name our audio groups, we need to do the following:
Now, we also have the option to give the exposed volume a reference name instead of its default name of MyExposedParam.
To change the reference of the exposed Music Volume, do the following:
The following screenshot shows the stages we just spoke about in the preceding steps:
I hope you understood this process well, because I want you to do it again but with the Effects group. Also, when it comes to naming the Effects reference in the last step that we just discussed, rename it effectsVol.
Finally, we will have our exposed volume references named like so in our Audio Mixer:
Nice work! Before we move on to the next section, let's briefly recap what we have covered so far in this section:
In the next section, we are going to code our pause screen's Volume and Effects sliders.
In this section, we are going to write two methods (SetMusicLevelFromSlider and SetEffectsLevelFromSlider) that attach our pause screen's Music and Effects sliders to the Audio Mixer that we created in the previous section.
Let's start by adding the Music slider to our Audio Mixer via the script, as follows:
Because we are going to access the Audio Mixer, we need an extra Unity library to let this happen.
using UnityEngine.Audio;
[SerializeField]
AudioMixer masterMixer;
[SerializeField]
GameObject musicSlider;
[SerializeField]
GameObject effectsSlider;
Now that our three references (music slider, effects slider, and master mixer) are hooked up to their parameters, we can return to our PauseComponent script and code in a method for each of the pause screen's volume sliders.
To add functionality so that our Music slider controls the Music mixer, do the following:
public void SetMusicLevelFromSlider()
{
masterMixer.SetFloat("musicVol",musicSlider.GetComponent<Slider>
().value);
}
The public method we have just entered, SetMusicLevelFromSlider, will work as an event from the Music slider. Inside the method, we have a reference to our masterMixer. Within this variable, we call its SetFloat function, which takes two parameters. The first is the reference name of the mixer (we called this musicVol earlier in this chapter), while the second is what value it is receiving to be changed. We are sending the value from our pause screen's Music slider.
Next, we need to attach our Music slider's event to the SetMusicLevelFromSlider method. To make the Music slider communicate with the method, follow these steps:
The following screenshot references the previous instructions for the Music game object in the Inspector window:
If we go back to the bootUp scene and click Play in the Unity Editor and then, in the Game window, click the game's pause button when it appears, we will be able to turn the music up and down with the Music slider.
Now, we need to repeat this in a similar fashion for our Effects slider volume to work:
public void SetEffectsLevelFromSlider()
{
masterMixer.SetFloat("effectsVol",effectsSlider. GetComponent
<Slider>().value);
}
As we can see, the code is virtually the same as the code for SetMusicLevelFromSlider, apart from the variable names.
Finally, test to see if the Effects slider works when we run the game.
This will obviously only work in level1 as level2 and level3 don't have the extra game objects. In the next chapter, we will be making a new level3, so if you can hold on until then, it'll save going through the process of removing and adding scenes again.
In this section, we covered the following functionality for the PauseComponent script:
In the next section, we are going to start looking at how to store our data. We will use the pause screen one more time to show the benefit of our game remembering our volume settings.
In this section, we are going to cover how we are going to store our data, such as the game's volume settings so that when we play our game, we don't have to keep setting the volume settings to where they were before. We want the game to remember them for us.
There are multiple ways we can store data. The ones we are going to cover are the two most common choices for Unity development. They are as follows:
It's wise to use this form of application programming interface (API) for transferring game data (lives, levels, player progress, energy, and so on), but don't store highly personal details locally with regards to in-game credit, bank details, personal addresses, and emails, unless you are using some form of encryption.
Information
An API basically tells us how applications communicate with each other.
For more information about JSON with Unity, check the documentation at https://docs.unity3d.com/Documentation/Manual/JSONSerialization.html.
In the following sections, we are going to cover these two ways of storing data on the basis they are officially covered by Unity and are likely to be mentioned in your exam:
Let's make a start by looking at how to use PlayerPrefs and revisit our pause screen one last time.
As we know, our game has volume controls for its music and sound effects on the pause screen. To make our game remember these volume settings, even when the game has been turned off and back on again, we need to do the following:
public void SetMusicLevelFromSlider()
{
masterMixer.SetFloat("musicVol",musicSlider. GetComponent
<Slider>().value);
PlayerPrefs.SetFloat("musicVolume",musicSlider. GetComponent
<Slider>().value); // << NEW CODE LINE
}
In the preceding code, we used the value from our music <Slider> component and applied its float value to the PlayerPrefs float with musicVolume as our key (the reference name to identify the PlayerPrefs value).
public void SetEffectsLevelFromSlider()
{
masterMixer.SetFloat("effectsVol",effectsSlider. GetComponent
<Slider>().value);
PlayerPrefs.SetFloat("effectsVolume",effectsSlider. GetComponent
<Slider>().value); // << NEW CODE LINE
}
That's our PlayerPrefs file ready to store the music and effects volume. The next thing to do is reapply the music/effects volume the next time we load the level from our PlayerPrefs.
To grab the music volume setting from our PlayerPrefs, do the following:
masterMixer.SetFloat("musicVol",PlayerPrefs.GetFloat("musicVolume"));
masterMixer.SetFloat("effectsVol",PlayerPrefs.GetFloat("effectsVolume"));
In the preceding code, we are reapplying our saved PlayerPrefs values for our music and effects volume (which are both floats) to our Audio Mixer's Audio Groups.
The volumes that we want the mixers to have are now set. The last thing we need to do is to set both volume sliders to their UI positions.
float GetMusicLevelFromMixer()
{
float musicMixersValue;
bool result = masterMixer.GetFloat("musicVol",
out musicMixersValue);
if (result)
{
return musicMixersValue;
}
else
{
return 0;
}
}
The preceding code is a method that returns a float value called GetMusicLevelFromMixer.
Let's go through the steps of this GetMusicLevelFromMixer:
Next, we need to call this GetMusicLevelFromMixer and make it so it sends its value to the music slider. Let's code this in now.
musicSlider.GetComponent<Slider>().value = GetMusicLevelFromMixer();
In the preceding piece of code, we are sending the result from our GetMusicLevelFromMixer to the value of our musicSlider when the level starts.
That's our music slider set. Now, we need to repeat this process for our effects slider. The process is the same, apart from using the effect slider's variables, so without repeating the same process, I want you to do the following:
Give it a go – if you're struggling, check out the Complete folder in this book's GitHub repository.
Save the script and return to the Unity Editor. Play the first level, change the volume, quit the game, and return to the first level to see if our volume has been saved for the music and effects sliders.
Now, we will move on and learn how to store and send data in a slightly different way.
JSON is great for creating, storing, and updating information across our game. As we mentioned earlier in this chapter, JSON is typically used for sending data from our game to a server online where the JSON data can be delivered to another set of data.
The best way JSON was explained to me is with an analogy of me being at a restaurant, sitting at a table (my game); the waiter comes over and takes my (JSON) order, then sends it to the kitchen (the online server). Finally, the waiter returns with my food.
With regards to coding JSON, we are storing variables in a single class, then serializing the class (object) into data (system memory or file). From there, we can transfer this data to an actual file or upload it to a server on a database. This whole process can also be reversed, where we take the data and return it as an object. This is called deserializing.
Now, let's move on to coding some JSON values.
The objective of working with JSON is to create a simple way of storing and updating data with JSON. In our project, we will provide a simple example of storing statistical data for our game. When the player completes the game, we will store data and put it in JSON format.
The three variables we are going to store are as follows:
Let's make a start by creating a new script that will receive our game's three statistical updates. These will then be converted into JSON format. Follow these steps:
public class GameStats
{
public int livesLeft;
public string completed;
public int score;
}
Notice how the GameStats script doesn't require a library or need to inherit MonoBehaviour. We don't require either of these extra functionalities.
When the player completes the game, we will take these three readings and store them in JSON format. From there, we can convert this data into a JSON file. This process is known as serialization.
Serialization/Deserialization
These two terms basically refer to the direction that data is stored in.
Serialization: This refers to converting an object from our script and turning it into bytes (a file, in our case).
Deserialization: As you can probably imagine, deserialization is the opposite of serialization. This means we are taking our raw data (file) and converting it into objects.
Next, we need to write some code that will update the player's lives, time and date, and score. We are going to do this when we play through the game and complete level 3. In this case, we need to go to our ScenesManager and update the code.
To update our ScenesManager so that it takes a reading of our player's stats and converts them into JSON format, we need to do the following:
The following screenshot shows where in the ScenesManager script we need to add our new method:
In the preceding screenshot, there is an asterisk (*) marking where we are going to enter the name of our new method, along with a string parameter, which will be the name of the level we have completed.
SendInJsonFormat(SceneManager.GetActiveScene().name);
void SendInJsonFormat(string lastLevel)
{
if (lastLevel == "level3")
{
GameStats gameStats = new GameStats();
gameStats.livesLeft = GameManager.playerLives;
gameStats.completed = System.DateTime.Now.ToString();
gameStats.score = GetComponent<ScoreManager>(). PlayersScore;
string json = JsonUtility.ToJson(gameStats,true);
Debug.Log(json);
}
}
In the previous code, we go through this series of steps:
Now that we have applied the three variables to our gameStats instance, we can use Unity's JsonUtility class to send our gameStats into the ToJson function.
We can also make the JSON data readable by adding true to the parameter so that when we send a log command to the console to see that this has worked correctly, we can read the results.
The following screenshot shows the console log when I played through the game and completed level 3:
As you can see, we have the data from our script but displayed in JSON format.
This information can be saved to a physical file or can be sent to a server to keep a record of our player's gameplay and/or deserialize the results later on with our project (check out the following tip if you are interested in this). The point is that we are storing and carrying data that can be sent away for us or another system to pick up, store, and alter.
Further Information
At this point, we have successfully taken the variables from our object and converted them into JSON data format (serialization).
Now, imagine if we altered our data (changed its values) and wanted to bring that data back into our game's code and into a class. The reverse method would be GameStats loadJsonData = JsonUtility.FromJson<GameStats>(json);.
This would update our GameStats variables from the JSON file. You can imagine that this would be handy for saving and loading data in games.
Next, we will take the most current JSON data file and send it to the device (the machine we play the game on). To make and store a JSON file containing our custom-made stats, do the following:
Debug.Log(Application.persistentDataPath + "/GameStatsSaved.json");
System.IO.File.WriteAllText(Application.persistentDataPath +
"/GameStatsSaved.json",json);
The preceding code block shows that we don't necessarily need to add Debug.Log and shows us where the next line of code is creating and storing our JSON file. Each platform will store data in different folders. For more information on the locations for different platforms, please refer to Unity's own documentation about persistent data at https://docs.unity3d.com/ScriptReference/Application-persistentDataPath.html.
My system is a Windows PC, so Debug.Log will display the following location on my system:
The second line of the code we just entered is using a system library and uses a function (Application.persistentDataPath) that will refer to our device's local storage. Then, after the function, we add the name we want to use to refer to our JSON file (/GameStatsSaved.json), along with the format type, which is json.
With this, we are now aware of how to store non-sensitive data such as our game's volume (PlayerPrefs), as well as how to create, store, and send other types of data in JSON format.
Now, let's summarize this chapter.
Further Information
For more information about all of these events, check the official Unity documentation at https://docs.unity3d.com/Manual/UnityAnalyticsEvents.html.
This chapter covered a variety of topics, including understanding Unity's Audio Mixer, which is where we can control the sounds in our game, and altering levels with our script. Then, we moved on and looked at storing data with PlayerPrefs and custom storage in JSON format in order to recognize the differences between the two ways of storing data. For JSON, we converted our data from object-based data into bytes and stored the results in a file (serialization).
In future projects, you will likely make use of the coding we covered in the last two chapters regarding storing and reapplying data such as music and sound effect volume sliders. Hopefully, you will also be able to go further with this data by using other components in your projects so that your game can send out data onto the cloud and monitor your players' progress as helpful feedback to improve development.
In the next chapter, we are going to look at pathfinding and how to improve the overall performance of our game.