Introduction
Here, we are at what will be the last series before we hit the big 100.
Reflecting at these past 10+ posts, I’ve realized that we transitioned from VR back to more game development.
My only regret is not transitioning to an easier content medium (like videos) and instead, I had to type everything out. As a result, I feel like my progress just isn’t nearly as fast as it could have been. However, it’s too late now and with only 4 more articles to go, I’m not going to turn back!
Transitioning back to today’s goal…
For the past 12+ days or so, we’ve been working on adding power-ups into the game. In the next 4 days, we’re going to create a simple item store where we can use the coins that we collect in the game to improve our existing upgrades.
To do that however, we need to be able to save the coins that we have gathered in our game.
Our goal today is to:
- Save our coins and score when the game is over
- Show the high score on our game over screen
We got quite a bit of things to do, so let’s get to it.
Step 1: Saving Our Coin and Score
Step 1.1: Create a Manager to Save and Load Our Coins and Score
I’m not going to go over how to save and load data, review Day 32: Learning about Saving and Loading in Unity for a quick recap on the different ways of storing data.
For our game, we’re not going to use anything fancy, we’re just going to use PlayerPrefs to save our information.
To do that, we’re going to create a simple Data
helper script that will help us save and load our data.
In the project folder, let’s create a new script called DataManager
(it’s not necessary for us to attach this script to any game objects).
DataManager
will be used to save and load the coin
and score
that we collect. Here’s what it looks like:
using UnityEngine;
public class DataManager
{
private static string _coinKey = "Coin";
private static string _scoreKey = "Score";
public static int LoadCoin()
{
return PlayerPrefs.GetInt(_coinKey, 0);
}
private static void SaveCoin(int coin)
{
PlayerPrefs.SetInt(_coinKey, coin);
}
public static int LoadScore()
{
return PlayerPrefs.GetInt(_scoreKey, 0);
}
private static void SaveScore(int score)
{
PlayerPrefs.SetInt(_scoreKey, score);
}
public static void AddCoin(int coin)
{
int totalCoin = LoadCoin() + coin;
SaveCoin(totalCoin);
}
public static void AddNewScore(int score)
{
if (LoadScore() < score)
{
SaveScore(score);
}
}
}
Walking Through the Code
One important thing to note is that these functions are static
. What this means is that we can call them anywhere.
Another important note is that to use PlayerPrefs
, we have to make sure that whatever script that calls our helper class must extend MonoBehavior
and be called after Awake()
or Start()
.
Here’s what our helper class does.
LoadCoin()
returns the coin that we saved in our game under the string
Coin
(from _coinKey
). If we didn’t save anything there, we will return 0
. SaveCoin()
saves the given coin value to our PlayerPrefs
under the string
Coin
, because we have certain rules we want to enforce, the class is private
and can only be accessed with AddCoin()
AddCoin()
is our public interface
that we use to add our new coin
s we collected. In the function, we get our existing coin
and add it to the coin
we collected and save the results with SaveCoin()
LoadScore()
returns the coin
that we saved in our game under the string
Score
(from _scoreKey
). If we didn’t save anything there, we will return 0
. SaveScore()
saves the given score value to our PlayerPrefs
under the string Score
, similar to SaveCoin()
, we don’t want anything to be able to call it, so it’s only accessed with AddNewScore()
. AddNewScore()
is the public interface
that we use to keep track of our high score. In the function, we check to see if our score is higher than our high score. If it is, we save it.
Step 1.2: Using Our DataManager to Save Our Score
With this script, we can now save and load data whenever we want to. Luckily for us, we have already written a game over code that we can leverage to save our results.
Specifically, this all gets called in our PlaneCollider
script when we collide against a wall.
From there, we call our PlayerManager
, GameUIManager
, and CameraManager
. The appropriate place to add our GameOver
functionality would be in our GameManager
as it governs everything in the game.
Here’s what it looks like:
using UnityEngine;
public class GameManager : MonoBehaviour
{
public static GameManager Instance;
private int _coin = 0;
void Start ()
{
if (Instance != null)
{
Destroy(gameObject);
return;
}
Init();
}
private void Init() {
Instance = this;
_coin = 0;
}
public void CollectCoin()
{
int scoreIncrease = 1;
if (PlayerManager.Instance.ContainsPowerUp(PlayerManager.PowerUpType.Score))
{
scoreIncrease *= 2;
}
_coin += scoreIncrease;
GameUIManager.Instance.SetCoinText(_coin);
}
public int GetCoin()
{
return _coin;
}
public void GameOver()
{
DataManager.AddCoin(_coin);
DataManager.AddNewScore(ScoreManager.Instance.GetScore());
}
}
Walking Through the Code
We also need to implement GetScore()
for ScoreManager
so we can access our score.
Speaking of ScoreManager
, we’re not going to refactor the code to fix this, but in retrospect, our GameManager
should have also been keeping track of our score alongside our coin. They shouldn’t be 2 separate managers.
Moving on:
We create a public
function GameOver()
that PlaneCollider
can later call that will save our score for when the game is over. In this function, we add the coins we collected and add our new score that we have achieved.
Step 1.3: Updating Our ScoreManager
using UnityEngine;
public class ScoreManager : MonoBehaviour {
public static ScoreManager Instance;
private float _score = 0;
void Start()
{
if (Instance != null)
{
Destroy(gameObject);
return;
}
Init();
}
private void Init()
{
Instance = this;
_score = 0;
}
void Update()
{
float increaseTime = Time.deltaTime * 10;
if (PlayerManager.Instance.ContainsPowerUp(PlayerManager.PowerUpType.Score))
{
increaseTime *= 2;
}
_score += increaseTime;
GameUIManager.Instance.SetScoreText((int)_score);
}
public int GetScore()
{
return (int) _score;
}
}
Walking Through the Code
To make our GameManager
script work, we need to implement GetScore()
. All we do in our function is return our score
value.
With this, our code now works correctly.
Step 1.4: Calling GameOver from PlaneCollider
Now that we have everything ready to make the game work, it’s time to use it in PlaneCollider
. Here’s our small change:
using UnityEngine;
public class PlaneCollider : MonoBehaviour
{
public GameObject PlaneObject;
public GameObject Explosion;
public AudioClip MagnetSFX;
public GameObject MagnetParticleEffect;
public AudioClip MultiplierSFX;
public GameObject MultiplierParticleEffect;
public AudioClip InvincibleSFX;
private SoundManager _soundManager;
private GameObject _currentParticleEffect;
…
private void EnemyCollision()
{
if (!PlayerManager.Instance.ContainsPowerUp(PlayerManager.PowerUpType.Invincible))
{
Instantiate(Explosion, PlaneObject.transform.position, Quaternion.identity);
Destroy(PlaneObject);
GameManager.Instance.GameOver();
PlayerManager.Instance.GameOver();
GameUIManager.Instance.GameOver(gameObject);
CameraManager.Instance.GameOver();
}
}
}
Walking Through the Code
In EnemyCollision()
, we just call our newly implemented GameOver()
which will save our coin
and score
.
Step 2: Showing Our Coin and Score in the Game Over Panel
Step 2.1: Change the UI
The first thing we need to do to show our next text is to modify our Game Over Canvas.
We want our canvas
to look like this:
To make this, we should:
- Drag our
GameOverCanvas
prefab from our Prefab folder into the game, we need to make some modifications - Duplicate the Restart Button and call it Shop Button
- Duplicate the Game Over Text and call it High Score Text, change the font size to be
16
- Duplicate High Score Text 2 more times and rename them Score Text and Coin Text
Now that we have our new UI pieces, let’s change their values:
- Game Over Text change Pos Y to be
3
- High Score Text change Pos Y to be
10
- Score Text change Pos Y to be
0
- Coin Text change Pos Y to be
-10
- Shop Button change Pos Y to be
-17.6
- Restart Button change Pos Y to be
-35
Here’s what it looks like now:
Step 2.2: Making Changes to Show the New UI Value
Now that we have our new UI, we need to write some code to add the correct text value to our UI. We can do this inside the Game Over Canvas Controller script which is attached to our Game Over Canvas.
Here’s what our change looks like:
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class GameOverCanvasController : MonoBehaviour {
public Text HighScoreText;
public Text ScoreText;
public Text CoinText;
public void ClickRestartButton()
{
print("clicked the button");
SceneManager.LoadScene(0);
}
public void ClickShopButton()
{
SceneManager.LoadScene(1);
}
public void GameOver()
{
HighScoreText.text = "High Score: " + DataManager.LoadScore();
ScoreText.text = "Score: " + ScoreManager.Instance.GetScore();
CoinText.text = "Coin: " + GameManager.Instance.GetCoin();
}
}
Looking at the Fields
public Text HighScoreText
– This is the text that we will use to display the high score public Text ScoreText
– This is the text that we will use to display the score we got in the current round public Text CoinText
– This is the text that we will use to display the coin we collected in the current round.
Walking Through the Code
GameOver()
this will be a public
function that will be called so that we will know to populate our UI with the high score, score, and coin we received in the game. We get the values from the appropriate managers. ClickShopButton()
, we added a new shop button that we will use later to take us to a new item shop scene. For now, that scene doesn’t exist, but we’ll add it in later.
Setting Up the Script
Now with these changes, we just need to drag and drop the scripts to the appropriate sections:
- Drag each of the
Text
game objects inside the Game Over Canvas into the appropriate slot in the GameOverCanvasController
script component. - In
ShopButton
, in the Event Trigger component, change the PointerClick
to call ClickShopButton
instead of ClickRestartButton
- Finally, go back to the Game Over Canvas object and hit Apply to save our change to the
prefab
.
With these changes, we will now correctly show our coin
and score
, but nothing will happen yet when we click on the Shop button.
Step 2.3: Call GameOver from the GameUIManager
We’re almost there, there’s just one more thing to do, we need to call GameOver
when we display our GameOverCanvas
in the GameUIManager
.
Here’s what the script looks like:
using UnityEngine;
using UnityEngine.UI;
public class GameUIManager : MonoBehaviour
{
public static GameUIManager Instance;
public GameObject GameOverCanvas;
void Start()
{
if (Instance != null)
{
Destroy(gameObject);
return;
}
Init();
}
private void Init()
{
Instance = this;
}
public void GameOver(GameObject player)
{
GameObject gameOverCanvas = Instantiate
(GameOverCanvas, player.transform.position, Quaternion.identity);
GameOverCanvasController gameOverController =
gameOverCanvas.GetComponent<GameOverCanvasController>();
if (gameOverController != null)
{
gameOverController.GameOver();
}
}
}
I think the code is simple enough by itself! One thing that I messed up is
End of Day 97
With these changes now, we should have a working UI that shows our score. Here’s what it might look like:
Nice so to recap, today we:
- created a helper class to Save and Load our score and coin data
- updated our Game Over Canvas UI to show that includes our
Score
and Coins
earned
With these changes in, we can transition to the next part of our item shop series and that is to create our new item store scene and create the basic UI for us to buy upgrades for our power-up and display our coins.
Stay tuned for the next post!