OUYA Game Development by Example Beginner's Guide (2014)
Chapter 6. Saving Data to Create Longer Games
By now, you've learned all of the basic steps to create fun mechanics in OUYA games, and you can start being creative by playing around with your current knowledge and see if it leads to new discoveries. However, without persistent save data, your creative scope is limited to a single play session, which probably won't be longer than a few hours.
Implementing long-term skill progression and narrative development can do a lot for your game experience, and it keeps players coming back to the game to reach new milestones. Even single-session games such as Canabalt expand their replayability drastically by simply adding in save data for the all-time high score.
We'll accomplish the following goals in this chapter:
· Learn how to save game data with the Unity engine
· Enhance your prototype from Chapter 4, Moving Your Player with Controller Input, by adding collectibles
· Save collectible data so that player progress is tracked between play sessions
· An introduction to saving persistent data with the OUYA API
Creating collectibles to save
The Unity engine features an incredibly simple saving and loading system that can load your data between sessions in just a few lines of code. The downside of using Unity's built-in data management is that save data will be erased if the game is ever uninstalled from the OUYA console. Later, we'll talk about how to make your data persistent between installations, but for now, we'll set up some basic data-driven values in your marble prototype. However, before we load the saved data, we have to create something to save.
Time for action – creating a basic collectible
Some games use save data to track the total number of times the player has obtained a collectible item. Players may not feel like it's worth gathering collectibles if they disappear when the game session is closed, but making the game track their long-term progress can give players the motivation to explore a game world and discover everything it has to offer. We're going to add collectibles to the marble game prototype you created and save them so that the player can see how many collectibles they've totally gathered over every play session. Perform the following steps to create a collectible:
1. Open your RollingMarble Unity project and double-click on the scene that has your level in it.
2. Create a new cylinder from the Create menu in your Hierarchy menu. Move the cylinder so that it rests on the level's platform. It should appear as shown in the following screenshot:
3. We don't want our collectible to look like a plain old cylinder, so manipulate it with the rotate and scale tools until it looks a little more like a coin. Obviously, you'll have a coin model in the final game that you can load, but we can customize and differentiate primitive objects for the purpose of our prototype.
4. Our primitive is starting to look like a coin, but it's still a bland gray color. To make it look a little bit nicer, we'll use Unity to apply a material.
A material tells the engine how an object should appear when it is rendered, including which textures and colors to use for each object. Right now, we'll only apply a basic color, but later on we'll see how it can store different kinds of textures and other data.
Materials can be created and customized in a matter of minutes in Unity, and they're a great way to color simple objects or distinguish primitive shapes from one another.
5. Create a new folder named Materials in your Project window and right-click on it to create a new material named CoinMaterial as shown in the following screenshot:
6. Click on the material that you just created and its properties will appear in the Inspector window. Click on the color box next to the Main Color property and change it to a yellow color. The colored sphere in the Material window will change to reflect how thematerial will look in real time, as shown in the following screenshot:
Our collectible coin now has a color, but as we can see from the preview of the sphere, it's still kind of dull. We want our coin to be shiny so that it catches the player's eye, so we'll change the Shader type, which dictates how light hits the object.
7. The current Shader type on our coin material is Diffuse, which basically means it is a softer, nonreflective material. To make the coin shiny, change the Shader type to Specular. You'll see a reflective flare appear on the sphere preview; adjust the Shininessslider to see how different levels of specularity affect the material.
You may have noticed that another color value was added when you changed the material's shader from Diffuse to Specular; this value affects only the shiny parts of the object. You can make the material shine brighter by changing it from gray to white, or give its shininess a tint by using a completely new color.
8. Attach your material to the collectible object by clicking-and-dragging the material from the Project window and releasing it over the object in your scene view. The object will look like the one shown in the following screenshot:
Our collectible coin object now has a unique shape and appearance, so it's a good idea to save it as a prefab.
9. Create a Prefabs folder in your Project window if you haven't already, and use the folder's right-click menu to create a new blank prefab named Coin. Click-and-drag the coin object from the hierarchy to the prefab to complete the link.
We'll add code to the coin later, but we can change the prefab after we initially create it, so don't worry about saving an incomplete collectible. Verify whether the prefab link worked by clicking-and-dragging multiple instances of the prefab from the Project window onto the Scene view.
What just happened?
Until you start adding 3D models to your game, primitives are a great way to create placeholder objects, and materials are useful for making them look more complex and unique.
Materials add color to objects, but they also contain a shader that affects the way light hits the object. The two most basic shaders are Diffuse (dull) and Specular (shiny), but there are several other shaders in Unity that can help make your object appear exactly like you want it. You can even code your own shaders using the ShaderLab language, which you can learn on your own using the documentation at http://docs.unity3d.com/Documentation/Components/SL-Reference.html.
Next, we'll add some functionality to your coin to save the collection data.
Have a go hero – make your prototype stand out with materials
As materials are easy to set up with Unity's color picker and built-in shaders, you have a lot of options at your fingertips to quickly make your prototype stand out and look better than a basic grayscale mock-up. Take any of your existing projects and see how far you can push the aesthetic with different combinations of colors and materials.
Keep the following points in mind:
· Some shaders, such as Specular, have multiple colors that you can assign. Play around with different combinations to create a unique appearance.
· There are more shaders available to you than just the ones loaded into a new project; move your mouse over the Import Package option in Unity's Assets menu and import the Toon Shading package to add even more options to your shader collection.
· Complex object prefabs made of more than one primitive can have a different material on each primitive. Add multiple materials to a single object to help your user differentiate between its various parts and give your scene more detail.
Try changing the materials used in your scene until you come up with something unique and clean, as shown in the following screenshot of our cannon prototype with custom materials:
Time for action – scripting the collectible
The first thing we'll script is some basic movement. Typically, the collectibles in games have some basic animation to differentiate them from the static objects in the scene, and it helps the player identify where they are. For our coin, just a constant rotation should suffice to make it stick out. Perform the following steps to script the collectible:
1. Right-click on your Scripts folder in the Project window and create a new C# script named CoinRotation.cs.
2. Double-click on the script to open it in your code editor. Add the following lines to the script's Update function:
3. void Update()
5. gameObject.transform.Rotate(0, 0, 5);
These lines access whatever the GameObject script is attached to and call the transform property's Rotate function, which takes three parameters (one for each axis of rotation). We want to rotate our coin along the z axis, so we added a value of 5 in the Z axis parameter field and left the other values as 0.
The transform property in every GameObject also includes the Translate function, which takes the x, y, and z arguments and moves an object along those axes rather than rotating around them.
We don't need anything else from the rotation script; we'll be coding the collectible data on the coin as well, but it's a good idea to keep your scripts separated by purpose, so we'll leave this script as it is and create another one for a more advanced functionality.
6. Save the CoinRotation.cs file and attach it to your Coin prefab by dragging it from the Scripts folder to the prefab. Press play to ensure that your coin now constantly rotates in the game.
Next, we'll add another script that checks whether our marble touches the coin. When we created the primitive cylinder that our coin is based on, a Collider component was automatically added to it. Game objects use colliders to detect interaction with other objects in physical space, and they have several customizable properties in our coin's Inspector window under the Capsule Collider region, as shown in the following screenshot:
The checkbox next to the Is Trigger prompt allows the object to collect the collision data without applying any automatic physical force, which is useful for item pickups and other nonphysical interactions. As we don't want our coin to impact our marble in any way when we collect it, we'll want to set the coin's collider to act as a trigger.
7. Select your Coin prefab from the Project window, and locate the component labeled Capsule Collider in the Inspector window. Click on the box to the right of the Is Trigger prompt once to activate it.
If you want to make changes to an object prefab, make sure that you apply the change to the prefab (in the Project window) rather than an instance of it in the scene (in the Hierarchy window). Changes made to individual instances of objects only change that instance, which is useful to differentiate instances, but this can lead to some puzzling inconsistencies if you don't keep the base prefab updated. To update a prefab, click on the Apply button in the Inspector window, or click-and-drag the updated object from the Hierarchy window over to the existing prefab to overwrite it.
The other properties of the collider aren't important to us right now, but they're still relatively straightforward. The Physics Material property can accept any of Unity's built-in physical materials such as ice, which changes the way collisions and movement behave. The other position and orientation values all represent what part of the object the attached collider encompasses. When primitive objects create their own colliders, they're initially sized to fit perfectly around the shape, but they can always be adjusted to create a custom collidable area.
8. Test your game in the Unity editor and try to roll your marble into the coin. You'll notice that even though there's an active collider on the coin, it doesn't physically impact the marble at all. This is because we have it set to be a trigger instead of a collision.
Of course, simply not making an impact isn't enough of a functionality for the coin; we still need to make it interactive.
9. Create a new script labeled CollectibleScript.cs in your Code folder, and open it in your code editor.
To handle collision, we'll use one of Unity's built-in functions named OnTriggerEnter. This function is available on all objects with colliders, but it doesn't automatically add itself to scripts as the Start and Update functions do, so the first thing you'll need to do is add it to your new script.
10. Add the following OnTriggerEnter function extension to your new script under the Update function:
16.void OnTriggerEnter(Collider collidingObj)
The single parameter that the OnTriggerEnter function takes is of the type Collider, which is a Unity-specific datatype that we can use to access all sorts of information about any collision that occurs, including the position, type, and game objects involved.
We'll test this function using a simple print statement to output some text to the Unity editor. Add the following call to Unity's print function in your OnTriggerEnter function:
void OnTriggerEnter(Collider collidingObj)
19. The only thing left to do is complete the link between your coin and your new script. Click-and-drag CollectibleScript.cs and release it over your Coin prefab to add it to all the instances of that prefab.
20. Click on the play button in the Unity editor, and roll your marble into the coin as shown in the following screenshot:
You'll notice that as soon as the collision occurs, a print statement will appear in the bottom-left corner of the Unity editor, which outputs the location of the collision.
Every time you roll away from the coin and then roll back into it, it will reprint the collision data because the OnTriggerEnter function is called every time a collision is entered between two objects with colliders.
In addition to the OnTriggerEnter function, the Unity API also contains an OnTriggerExit function that gets called when two objects separate from a collision, and an OnTriggerStay collision that gets called for every frame that a collision occurs. Each of these are useful in their own way when creating your game.
In the case of our coin, we don't want our player to be able to pick it up more than once, so we'll tell the coin to delete itself as soon as a player collects it.
21. Add the following lines to the OnTriggerEnter function in your CollectibleScript.cs file:
22.void OnTriggerEnter(Collider collidingObj)
If you test the scene in the Unity editor again, the coin will disappear as soon as the marble touches it. Next, the coin will need to notify the game somehow that it's been collected, and then we can save that data so that the total number of collectibles is persistent.
What just happened?
Now is a good time to look back at what you've done so far. With the two scripts that you wrote, you've made your coin a fully functioning object in the game world.
Even though the code contained between the two scripts could have been put into one, it's a good practice as a developer to keep your scripts separated by purpose, so if you ever want to change the specific functionality of one aspect of an object, you can change that aspect's code without having to look through or change anything else.
You also utilized Unity's built-in OnTriggerEnter function for the first time. This function is extremely useful for handling interactions between objects in the game world, and the collider parameter provides a wealth of information about both objects involved in the collision, which you can use to collect data about your game.
There's a function similar to OnTriggerEnter that works with non-trigger colliding objects; this is useful for colliding objects that make an impact, such as bullets or balls. This function is named OnCollisionEnter, and the only thing that differentiates it from OnTriggerEnteris that it takes a Collision type as the parameter and not a Collider type. The collision data carries a few more values with it, such as impact force and momentum.
The only thing left to complete the collection event is tell the player that they've collected something, and we'll do this by accessing the player's script from the collider data.
Time for action – accessing the scripts on other objects
The only existing script on our marble right now is the input script, so create another one that will be responsible for handling the collection data. Perform the following steps to access the scripts:
1. Create a new file named PlayerCollection.cs in your Scripts folder and open it in your code editor.
2. Add the following variable and function to your code, above and below the Start and Update functions, respectively:
3. private int totalCoins = 0;
5. // Use this for initialization
6. void Start()
11.// Update is called once per frame
17.public void CollectCoin()
Let's look at the two things you just added to your script. We set the int variable to private because this script is the only one that needs to edit that value, but we added a public function that increments the value by one.
This method is better than directly accessing a public variable on another script because making variables private ensures that objects interact with the data only in ways you've defined.
Next, we'll call the CollectCoin function from the coin as soon as it's collected. We'll do this by accessing the player object with the collider variable in the OnTriggerEnter function.
21. Open your CollectibleScript.cs file, and add the following line to the OnTriggerEnter function:
22.void OnTriggerEnter(Collider collidingObj)
27. Test your new code in the editor and ensure that the print statement you wrote to display the total number of coins appears when the coin is destroyed.
28. Try adding several coins to the map and rolling into all of them to see the print statement's value get higher with each coin you collect.
Now the marble and coins should appear as shown in the following screenshot:
What just happened?
A lot of games require an immense amount of interaction between objects, so cohesion between code systems is a must. Unity's GetComponent function is a great way to access values from other scripts on any object in your scene.
Using the data returned from a trigger collision using Unity's built-in collision detection, we got the PlayerCollection data from the colliding marble game object and were able to call a public function that incremented a private variable representative of the total number of coins collected by it.
Later, you'll learn other ways to access other game objects from within a script; but for now, we'll focus on saving the data that you've already collected so that it reloads whenever the game is started.
At this point, the number resets whenever you stop the game and press play again, so next we'll save the data every time a new coin is collected.
Saving data with the Unity engine
Now that our prototype features a collectible that we can save, we need to actually program the saving operation using Unity's built-in data storage methods. In this section, you'll save data and see it loaded even after you close the game and reopen it.
Time for action – saving data with PlayerPrefs
We're finally ready to save and load data to the player's collection class. For this, we'll be using Unity's PlayerPrefs class in code.
1. Open the PlayerCollection.cs file in your code editor and add the following lines to your CollectCoin function:
2. public void CollectCoin()
6. PlayerPrefs.SetInt("TotalCoins", totalCoins);
It's as simple as that! Let's examine what this new line actually does. We're calling the SetInt function from the PlayerPrefs class because the data we're saving is an integer value. However, PlayerPrefs also contains functions to save floating-point values and strings.
No matter what kind of data you're saving, the parameters for the saving function are generally the same. The first value is a text string that represents a key or a way to label the data you're saving so that you can access it later.
The second parameter is the data you're saving with that key, be it an integer, string, or float. Of course, sometimes you may want to save data that doesn't fit any of those three datatypes; for example, the C# Color datatype. In such instances, you would need to program a way to translate the value into one of the accepted datatypes. One way to do this would be to program a function that saves and loads integers for the color value (red = 1, orange = 2, and so on).
Next, we'll set up our PlayerCollection class so that it loads in our saved value as soon as the game starts.
7. Add a line to the Start function in CollectibleScript.cs as follows:
8. // Use this for initialization
9. void Start()
11. totalCoins = PlayerPrefs.GetInt("TotalCoins");
As the Start function of PlayerCollection gets called once at the beginning of our game, we can use it to assign our last saved value to the totalCoins variable to ensure that we pick up right where we left off.
12. Add a print statement to the Start function, which will print out the value of the totalCoins variable right after it's loaded:
13.// Use this for initialization
16. totalCoins = PlayerPrefs.GetInt("TotalCoins");
17. print("Total coins on load: " + totalCoins);
18. Click on play, collect some coins, stop the game, and then click on play again. You'll see your number of previously collected coins in Unity's editor output, confirming that your save and load operations are now fully functional.
What just happened?
At this point, the data that you save is completely up to you. You can use integers, strings, and float datatypes in tandem with the saving and loading functions to record virtually any value that needs to be carried through to every play session.
Just being able to use Unity's saving and loading expands the scope of any of your games greatly because you can now set goals that take longer than a standard play session to complete and not worry about your player losing their progress.
The only thing your prototype is lacking now is a visual representation of the data that you've saved. To show the total number of coins collected without using Unity's output window, we'll use something called GUIText.
Time for action – setting up a GUI Text object
The Unity engine features a component/object type named GUIText, which can display raw text and numerical values as stylized text on your game's viewport. We can write a script for a new GUIText component that will determine the total number of coins collected and update the displayed text with that count. Perform the following steps to set up GUIText:
1. Make a new GUIText object from the Create menu of your Hierarchy window.
You'll notice that the GUIText position is set to 0.5, 0.5, 0 by default. However, it displays in the middle of the game window regardless of your position. That's because a GUIText position value doesn't affect its global position, only its location on the screen.
We want GUIText to appear in the upper-right corner so that it doesn't obscure the center of our view, so we'll change the coordinates to the maximum X and Y values. The Z value of GUIText represents the depth, which won't become apparent until you have multiple texts, when the texts with the greatest depth will be displayed behind all the others.
2. Set the position of the GUIText component to 1, 1, 0.
Unfortunately, even though the text's new position is perfectly situated in the upper-right corner, it's not visible on the screen because the anchor of the text is at the upper-left corner. We can fix this by adjusting the anchor of the text to be at the far right instead of the far left.
3. Edit the Anchor property of GUIText in the Inspector window to be upper right.
4. The text looks a little small as well, so increase the font size to 20.
Your GUIText component should now look something similar to the following Inspector window:
Now all that the GUIText component needs is the text to display, and we'll read that data from the PlayerCollection class. However, because a collision never occurs between GUIText and the marble that holds PlayerCollection, we need a different way to access that data.
Fortunately, this process is easily accomplished with Unity's tag system, which is a way that a developer can explicitly define and locate identifiers.
5. Select your Marble prefab in the Project window, and select Add Tag… in the Tag menu as shown in the following screenshot:
6. Select the empty text box next to Element 0, and type in Marble to add it as a new tag, as shown in the following screenshot:
Even though you've now created the tag, it doesn't automatically apply to your marble, so we'll need to go back to the marble's prefab and tag it.
7. Select your Marble prefab, and change the tag to Marble, as shown in the following screenshot:
Now that your marble is properly tagged, we can access it in code. However, right now we only have a function in PlayerCollection.cs that sets the total number of coins, and we don't have a function that collects it.
8. Add the following function to your PlayerCollection class:
9. public int GetTotalCoins()
11. return totalCoins;
12. Create a new script named CollectibleGUI.cs, attach it to the GUIText object in your scene, and then open it in the code editor.
13. Add the following lines to the Update function so that our text object updates every frame:
16. guiText.text = GameObject.FindGameObjectWithTag("Marble").GetComponent<PlayerCollection>().GetTotalCoins().ToString();
Don't be intimidated by the length of the line of code you just wrote; it's wordy, but the operation is relatively simple. Every frame, we find the value of the GetTotalCoins function based on the game object that we gave the Marble tag to. We then assign that to theguiText.text property for every frame so that our total number stays updated as we play.
17. Test your game to see how the GUIText component responds to the collection of coins.
What just happened?
You now have the means to convey numerical data to the player using text onscreen, which is very important for acclimating the player and giving them the information they need (or want) to play your game.
You also learned an alternate method to access scripts on objects in the game world using Unity's tag system. You can have as many custom tags in your game as you want—there are also a decent number of predefined tags to set—and those are useful to organize objects for easy access later.
Now that your saved data is displayed to the user, your save system is fully implemented. Feel free to get creative and try different approaches to perform the same basic functions; code can always be refactored and optimized, and the saving/loading methods in this book are just a couple of the myriad of ways you can structure a persistent data system.
Using save data in multiple scenes
So far, the save data you've interacted with has been stored and loaded by the same scene: the main game scene. However, as save data can be loaded anywhere within a project, we can easily create a system that saves in one scene and loads in another. We'll attempt this by returning to the cannon game prototype and adding a high score scene that displays whenever the game is won. The score will be a numerical value representing how many cannonballs it took the player to hit the target, with lower numbers being displayed higher up in the rankings.
Time for action – counting cannonballs
As with all data operations, the first step is to create a variable that stores the data you want to interact with. To count the number of cannonballs that the player fires, we'll need to use an integer variable with a script that increments the count by one every time theFireCannon function is called. Perform the following steps to count cannonballs:
1. Create a new C# script named ScoreKeeper, and attach it to your Cannon prefab.
2. Automatically add the script to all future instances of your Cannon prefab by clicking on Apply in the Inspector window after you attach the script.
3. Open the ScoreKeeper script, declare an int variable named numCannonballsFired, and initialize it to 0 in the Start function as shown in the following code:
4. private int numCannonballsFired;
5. void Start()
7. numcannonballsFired = 0;
Next, we need a public function that will increment the integer value by one every time it's called.
8. Create a new function named IncrementCount and add a line that increases the count of numCannonballsFired so that your function looks like the following code:
9. void IncrementCount()
Now that our function is set up, we need to create a way to call it when a cannonball is fired. We could set up an external object reference like we did with Unity's tag system earlier in this chapter, but there's another way we could call this function that would fit our needs just fine: we could turn it into a static function.
Static functions are functions that can be called whether or not any instances of an object with the function attached are present, and they're completely independent of the values of individual instantiations of that object or script. The only values that static functions can interact with are static variables; otherwise, discrepancies could occur with differing values of duplicate variables on separate instantiations.
Functions and variables can easily be made static by preceding them with the static keyword.
12. Convert your numCannonballsFired variable to static by inserting the static keyword after the private keyword:
private static int numCannonballsFired;
13. Convert your IncrementCount function to static by inserting the static keyword before the void keyword:
14.public static void IncrementCount()
Now the function is ready to be called.
17. Attach the ScoreKeeper script to the Cannon prefab and apply changes.
18. Open the CannonScript file to add a call to IncrementCount in the FireCannon function.
As previously mentioned, when calling a static function, a reference to an instance of that object type isn't necessary. Instead, the function can be called using the complete class name.
19. Add a call to IncrementCount at the very end of the FireCannon function, as shown in the following code:
20.void FireCannon(float xForce, float yForce)
What just happened?
Your ScoreKeeper script now keeps track of the number of cannonballs your player fires. You could have used standard functions to achieve this by making them public and adding a reference to them from the CannonScript file, but instead you made the variable and function static, allowing you to call it from anywhere using just the ScoreKeeper class name followed directly by the function name.
Next, we'll create a way to read that value when the game is complete and check if it's a better score than the three best scores.
Time for action – checking high scores in a new scene
The first thing we need is a way to store the high score whenever the level ends. This means we'll need to get the number of cannonballs fired at the end of a round and create a PlayerPrefs entry for it. We'll do this by creating a static function to return the total number of cannonballs fired in the script and calling it from the target whenever it's hit. The steps to do so are as follows:
1. Create a new static function called GetCannonballCount in the ScoreKeeper class, and add a line that returns the integer value of numCannonballsFired, as shown in the following code:
2. public static int GetCannonballCount()
4. return numCannonballsFired;
5. Open your TargetScript file, and add the following lines to its OnCollisionEnter function to save the player's score, which we'll access later from the high score script:
6. void OnCollisionEnter(Collision collidingObj)
8. gameObject.renderer.material.color = Color.red;
Your value is now being saved properly, so we can create a new scene to create and display a high score list and access the value from there.
10. Open Unity's File menu and click on New Scene.
11. Save your scene as HighScoreScreen in your Scenes folder.
12. Create a new GUI Text object in your scene, and position it in the middle of the screen by setting its Anchor setting to middle center and its Alignment setting to center in the Inspector window.
13. Set the font color to yellow in the Inspector window.
When you're finished, your Inspector window should look like the following screenshot:
14. Create a new C# script called HighScoreText in your Scripts folder and open it in your code editor.
15. Create a new function called ReadHighScores, defined as follows:
The first thing our function needs to do before collecting and displaying the high scores is check whether the latest score has beaten any of the old values. We'll do this by checking three score values in PlayerPrefs in order of best to worst and replace the value if the latest score is less than the saved score or there is no saved score yet (which would return a value of 0).
19. Add the following lines to your ReadHighScores function to check the three high score slots, and insert the new score if necessary:
22. int latestScore = PlayerPrefs.GetInt("NewScore");
24. if(latestScore < PlayerPrefs.GetInt("ScoreOne") || PlayerPrefs.GetInt("ScoreOne") == 0)
28. else if(latestScore < PlayerPrefs.GetInt("ScoreTwo") ||
29. PlayerPrefs.GetInt("ScoreTwo") == 0)
33. else if(latestScore < PlayerPrefs.GetInt("ScoreThree") ||
34. PlayerPrefs.GetInt("ScoreThree") == 0)
38. Add a call to ReadHighScores to the Start function of the HighScoreText script to check the data as soon as the high score list is loaded:
What just happened?
You just wrote a function that checks the player's latest score whenever the game ends and stores it as a high score if it's better than any of the existing high score values. You saved this data in your main game scene, but you're able to read it from theHighScoreScreen scene because the data saved with PlayerPrefs can be accessed from any scene across multiple play sessions.
All that's left is to display the three high score values after checking them using the GUI Text object that we created.
Time for action – displaying high score values
We haven't done anything complicated with GUI Text objects, but they can be powerful and versatile with the proper formatting, so now we'll go over a few tricks you can use to cleanly display three different scores in a list format using only one GUI Text object. Perform the following steps to display high score values:
1. Attach your HighScoreText script to your GUI Text object by clicking-and-dragging it over the GUI Text object in the Hierarchy window.
2. Create a new function called DisplayHighScores in the HighScoreText script as shown:
3. void DisplayHighScores()
By default, a GUI Text object displays information in a straight line of text, but what we want is something similar to a list. To achieve this, we'll use what's called an escape character or a special character in the middle of a string that dictates how it's processed or displayed. In this case, we'll be using \n, which creates a new line whenever it's called. While escape characters can be inserted directly into strings, they won't be displayed with the rest of the regular characters; they are only directions for the engine and won't be visible to your player.
6. Add the following lines to your DisplayHighScores function to show all three high score values formatted into a list:
7. void DisplayHighScores()
9. guiText.text = "First: " + PlayerPrefs.GetInt("ScoreOne").ToString() + "\nSecond: " + PlayerPrefs.GetInt("ScoreTwo").ToString() + "\nThird: " + PlayerPrefs.GetInt("ScoreThree").ToString();
Although the function text in the previous code may seem jumbled and incoherent, it's actually pretty simple; we're just making one long string that converts each score into string characters, labels them in order, and uses the \n escape character to create a new line for each entry.
10. Add a call to DisplayHighScores to the Start function of your HighScoreText script that displays the high score values using the GUI Text object directly after reading them:
15. Press the Play button to test your high score functionality and ensure that your text is being displayed properly.
Your formatted text should look similar to the following screenshot:
What just happened?
You're now successfully displaying the high score values that your HighScoreText script collects in a single GUI Text object. You used the \n escape character to format the GUI Text object to look more like a list, an invaluable trick that can save you a lot of time and help you avoid having to make a different GUI Text object for each line you want to display.
The data that your HighScoreText script reads is saved from the other scene, demonstrating the full potential of the saving and loading methods of PlayerPrefs . Now that you've mastered using PlayerPrefs to save whatever data you'd like, we'll take a brief look at the other method available to you to save data: the OUYA API.
Have a go hero – adding a reset button to your high score list
Right now, your high score list will keep the same scores until the end of time (or at least until you uninstall the game from your OUYA). However, you have all of the knowledge you need to create a reset button that clears the values and lets a new set of players shine with fresh high score values. Give it a try!
The following are a few hints to get you started:
· Create the button by encapsulating the GUI.Button function within an if statement, like you did for the Fire! button in the cannon prototype that you created in Chapter 5, Enhancing Your Game with Touch Dynamics.
· There's no function to clear a saved value, so instead just use the PlayerPrefs.SetInt function to set the function to 0 manually.
· Remember to write all of your GUI functionality within the OnGUI function in your HighScoreText script.
The following screenshot shows a reset button:
Saving data with the OUYA API
You may remember from the beginning of the chapter that Unity's storage method isn't the only one that you can use to save data. OUYA features its own storage API that can be used to save data with keys just like Unity, but the purpose is slightly different.
Unity's saving mechanism is great because it handles storage and organization of the files all on its own and can be easily accessed using keys. However, if you were to uninstall the game from OUYA or your computer, the saves would no longer be present as they're tied to the project files.
OUYA's storage API, on the other hand, makes sure that the data remains in place even if the game is uninstalled. That way, if a player uninstalls a game that they've made progress on but then reinstall it later, their game will load the OUYA API's saved data from the original installation.
So why not just use OUYA's storage API all the time? For one, it can only store values as strings. You could theoretically save any aspect of a game using strings, but it would require a lot more scripting on your end because strings have to be parsed into integers or floats before they represent any numerical value.
Additionally, if the OUYA API tries to save data from another app and it runs out of room, it may delete some old saves from an uninstalled game to make room. This doesn't happen a lot, but it's too risky to put in-game data in such a volatile place, especially if the difference could mean hours of play time.
Due to its specific purpose and unique constraints, the OUYA storage API is generally used to store data from downloadable content that expands an existing game. Representing this data with a string is fine because you'll most likely want downloadable content to be represented by a plain text word or phrase anyway. Also, it's okay if old data is deleted automatically by the OUYA because downloaded content can always be reacquired using the receipt.
Pop quiz – saving (and loading) the day
Q1. If you wanted to add a new component to a prefab that had already been instantiated at least once in your game, where would you apply the change?
1. In a current instantiation of the prefab
2. In the prefab itself in the Project window
Q2. Which of the following saving/loading requirements are best suited for the OUYA storage API?
1. High score/leaderboards
2. Prepurchased DLC
3. User profile
Q3. Which of the following saving/loading requirements are best suited for Unity's PlayerPrefs storage method?
1. Unlocked power-ups
2. Purchased content
3. Configuration settings
Q4. If you wanted to collect data from an instantaneous collision between a ball and a hard surface, which function would you use?
Q5. If you wanted to measure how long two triggers overlap with each other, which function would you use?
In this chapter, you built a unique collectible game object using primitives and basic materials, saved it to a prefab, and made its instantiations collectible and savable.
You collected coins by calling Unity's GetComponent function from the coin's script to access a collection script on the player. GetComponent is an extremely useful tool in the Unity engine that allows for substantial communications between two unrelated game objects, so get used to using it.
Finally, you scripted a system that counts the number of coins collected from each play session and adds them to a running total that's loaded every time the game starts. Incorporating save data into your game is essential to expanding your game's scope as it allows you to potentially develop a narrative and skill progression over several sessions rather than giving the player a short, one-shot repetitive experience such as Solitaire.
Next, we'll expand your game further by creating unlockable content that can be purchased by players on the OUYA marketplace. We'll store the data securely and ensure it persists between installations by using OUYA's storage API, which exists primarily to handle downloadable content and permanent settings.