Skip to main content

Tutorial: Scores

In this tutorial, we go over how to add a score system to our game and display it on the screen. We will also learn how to save the high score to persistent memory so that it is not lost when the game is closed.

Scores

To keep track of the score and manage how the player earns points, we will create a new script called ScoreManager.cs and attach it to the GameManager GameObject. The logic I'm going to use is pretty simple. Every time the player eliminates an enemy, we will add 50 points to the score. We will also add a bonus score when the player clears a stage based on how long it took for them to complete the stage.

Base score framework

We want a variable to keep track of the score. We also want a public function that we can call to add points to the score.

protected float score = 0;

// Add the given score to the whole thing
public void AddScore(float scoreToAdd)
{
score += scoreToAdd;
}

Bonus Score on Stage Clear

The player gets a bonus score which is based on how long it takes for the player to clear the stage. The faster the player clears the stage, the more points they get. So, the player has 30 seconds to clear the stage, and from there on and they have 1000 points to earn. But every second, the player losses 33 points.

[SerializeField] protected float bonusToGive = 1000;
[SerializeField] protected float timeToComplete = 30;

protected float bonus = 0;

void Update()
{
if (bonusToGive > 0)
{
// decrement the bonus on time elapsed
bonus -= (bonusToGive / timeToComplete) * Time.deltaTime;

// clamp the bonus to 0, so that we don't apply a negative score
if (bonus < 0) { bonus = 0; }
}
}

We need to add the bonus when the player clears the stage. We will add a public function to indicate that the stage has been cleared.

public void IndicateNewStage()
{
// add any bonus to the score, then reset the bonus
score += bonus;
bonus = bonusToGive;
}

Displaying the score

We want to display the score on the screen, so we need to add a UI text to the HUD canvas. I won't go over how to add the UI text, but I will show you how to set it up to work in our game. We can link the UI text to the UIManager script and then we make a call to the SetScore function from the ScoreManager script to update the score on the screen.

[SerializeField] TMP_Text ScoreUI;

public void SetScore(int newScore, int? scoreAdded = null)
{
ScoreUI.text = $"Score: {newScore}";

if(scoreAdded is not null)
{
// add the code to show score added here...
}
}

I have not added the implementation for showing the score added here, but you can add it if you want. It will be a little more complicated, so I will leave it up to you to figure out how to do it.

High Score

We want to save the high score so that it is not lost when the game is closed. Basically, persistent memory. To accomplish that, we will use Unity's PlayerPrefs system to save the high score.

// get a value from player prefs... if not found, return 0
int value = PlayerPrefs.GetInt("key", 0);

// save a value to player prefs
PlayerPrefs.SetInt("key", value);

We will add a function called CheckAndUpdateHighscore to check if the current score is higher than the high score. If it is, we will update the high score.

public void CheckAndUpdateHighscore()
{
// Get the high score from the player prefs
int highscore = PlayerPrefs.GetInt("highscore", 0);

// if the current score is greater than the high score, update it on the UI
if(score > highscore)
{
PlayerPrefs.SetInt("highscore", Mathf.CeilToInt(score));
}
}

For your challenge, you can add the high score to the UI and display it on the screen. My final script has the high score displayed on the screen, but I will leave it up to you to figure out how you want to display it.

It is also important to note that the PlayerPrefs system is not secure. Anyone knowledgable can access the data stored in the PlayerPrefs system and modify it, say, to give themselves a high score of 10000. In our case, it is not a big deal, but if you are making a game that has a lot of data that needs to be secured and unmodified, you should look into other options. One option is to store it in a custom file that is encrypted and can only be accessed by your game. However, the player can still decrypt the file and modify it, so it is not a perfect solution, but it is better than nothing. Another option is to use a server to store the data and then have the game access the data from the server and post any changes back to the server. But that is a lot more complicated and requires a server to be set up and maintained.

So, at the end of the day, it is very case specific and you will have to decide what is best for your game.

Final Script

using UnityEngine;

public class ScoreManager : MonoBehaviour
{
[Tooltip("The bonus to give per stage")]
[SerializeField] protected float bonusToGive = 1000;

[Tooltip("Time to complete the stage to recieve at least some bonus points")]
[SerializeField] protected float timeToComplete = 30;

protected float score = 0;
protected float bonus = 0;

protected UIManager uiManager = null;

void Start()
{
uiManager = GetComponent<UIManager>();
}

void Update()
{
if(bonusToGive > 0)
{
// decrement the bonus based on time elapsed
bonus -= (bonusToGive / timeToComplete) * Time.deltaTime;

// floor the bonus to 0
if (bonus < 0) { bonus = 0; }
}
}

// Add the given score to the whole thing
public void AddScore(float scoreToAdd)
{
score += scoreToAdd;
uiManager.SetScore(Mathf.CeilToInt(score), Mathf.CeilToInt(scoreToAdd));
}

// Indicate to the score manager, that the game switched to a new stage
public void IndicateNewStage()
{
// add the collected bonus
score += bonus;

// update the bonus score UI, if the player has a bonus
if(bonus > 0)
{
uiManager.SetScore(Mathf.FloorToInt(score), Mathf.FloorToInt(bonus));
}

// reset the score
bonus = bonusToGive;
}

// Update the high score information
public void CheckAndUpdateHighscore()
{
// Get the high score from the player prefs
int highscore = PlayerPrefs.GetInt("highscore", 0);

// if the current score is greater than the high score, update it on the UI
if(score > highscore)
{
PlayerPrefs.SetInt("highscore", Mathf.CeilToInt(score));
uiManager?.ReportHighscore(Mathf.CeilToInt(score), isNew: true);
return;
}

// not a new high score, but we still have to report it to the UIManager
uiManager?.ReportHighscore(highscore, isNew: false);
}
}