How to keep score in Unity (with loading and saving)

In Game Systems by John FrenchUpdated 25 Comments

While keeping track of the score in Unity seems like an incredibly simple thing to do, there’s actually a lot to consider.

For example, how will you measure the player’s score as they play through the game?

Will you count their points by time elapsed, by their progress through the level or maybe by the number of enemies they defeat?

And how will you display the score in the UI, will you animate it as the value changes and how can you format the number value in a way that makes sense?

And that’s not all…

Once you’ve got a score value, how will you store it?

Will other scripts be able to read it, will it stay the same between scenes and how can you save it to a file, so that you can load a high score leaderboard that persists between sessions.

While creating a basic score value in Unity can be very straightforward, actually building a score system can be much more complex.

Exactly how you do it will depend on how you want scores to work in your game.

But don’t worry, because in this article you’ll learn how to manage, display and save a score value in Unity, step by step.

Here’s what you’ll find on this page:

Let’s get started.

How to count score in Unity

Counting up the score in Unity can be very straightforward.

All you really need is a variable, such as a float or an integer to store the score.

Like this:

public int score;

And then, a way to increase it when the player does something good.

Like this:

public void AddTenPoints()
{
    score += 10;
}

Easy, right?

There’s just one problem.

While adding up the score can be very straightforward, most games measure a player’s points in very different ways.

Some games measure how far you can progress through an endless level.

While other games measure how long you can survive.

Some games want you to collect items, others want you to avoid them.

Some games count money, experience, accuracy.

Others don’t count anything at all.

The point is (pun intended), how you choose to measure the score in your game will be very specific to how your game is played.

So you’ll need to decide which method is right for your game.

However… while there are many different ways to measure a player’s score, keeping track of the numeric value can be much more simple.

So here are four different ways to do exactly that.

How to increase score over time in Unity

One simple method of increasing the score in Unity is by time.

For example, by adding a fixed number of points for every second of the game that passes.

Typically, you might use a time-based score system like this for survival games, for measuring how long a player manages to do something, like keeping hold of an object for example, or pretty much any time you want to measure the player’s performance by how long they managed to do something.

So how can you use time to measure points?

Put simply, it involves measuring the time that has elapsed, by adding up the Delta Time every frame and then multiplying that amount by a points value.

Like this:

public float score;
public float pointsPerSecond=20;

void Update()
{
    score += pointsPerSecond * Time.deltaTime;
}

Multiplying a points value by delta time, which is the amount of time that has passed since the last frame, counts up the score at a consistent rate. 

In this case, at 20 points per second.

But what if the amount of time that’s passed isn’t what’s important in your game?

What if, instead of measuring how long a player did something for, you want to measure how quickly they did it?

How to measure the score based on the time remaining

Score by time

Instead of counting up a player’s score based on the amount of time that’s elapsed, you might, instead, want to give the player points for the time that’s remaining.

This is a common mechanic in older platform games, where stages were often time-limited and players got extra points for how much time was left on the clock.

It works in a similar way to counting the score for the amount of time elapsed except that while, before, the score was counted every second the game was still going, you’d typically calculate a time-remaining score at the end of the level instead.

Like this:

score = timeRemaining * pointsPerSecond;

How to increase score by distance

Another method of measuring progress, and one that’s often used in endless runner style games, is counting points based on total distance.

For example, in a side-scrolling game, where the player earns points by progressing through the level as far as they can, you might award 100 points for every unit of distance that they managed to cover.

While there are many ways to do this, one method is to track the horizontal movement of the camera.

Like this:

public float score;
public float origin;

private void Start()
{
    origin = transform.position.x;
}

void Update()
{
    score = transform.position.x - origin;
}

In this example, I’ve stored the origin point of the camera in Start before working out the score.

This is so that the score reflects the relative movement of the camera, not just its distance from the centre of the world. Meaning you can place the camera anywhere you like and, so long as it pans to the right, it’ll work.

In this example, I haven’t added a multiplier to modify the raw value of the score.

This is because, in Unity, 1 unit of measurement refers to 1 metre of distance by default.

So, when measuring score by distance, you may want to show the real-world distance as the player’s score, as opposed to using an arbitrary value.

How to add to the score on collision with another object

Chances are, you’ll have played a game before where you’re encouraged to collect something and that, by doing so, you’ll be awarded points.

Here’s how to do it in Unity.

In this basic example, I have a player object and I want to add points every time it collides with a collectable object.

Increase score on collision

In this example, I want to increase the score every time the player object (this big pointy brick), collides with a collectable.

For this to work, I need to detect a collision between the two objects, which means that both objects will need collider components, in this case, 2D Collider components since I’m working in 2D:

2D Collider Component

Both the player and the collectable need colliders for this to work.

Adding a collider component to an object allows it to obstruct and collider with other collider objects.

However, while the player needs to have a physical presence, the collectable doesn’t.

I don’t need the two objects to bounce off of each other, I simply need the player to be able to detect the collectable.

So, for that reason, I can mark the collectable colliders as Triggers.

Like this:

2D Collider Component Trigger

A trigger collider can be used to detect collisions without obstructing the player.

Trigger colliders can be used to detect when one collider enters the space of another.

Which, in this case, is exactly what I want to do.

I want the player to detect when moving into the collectable, but I don’t want them to be obstructed by it.

Finally, for any collision between the two collider components to be detected, I also need to add a Rigidbody 2D component to one of the objects.

Like this:

Rigidbody2D

The Rigidbody component allows you to apply physics to an object, in this case it allows me to detect collisions with the collectable items.

Adding the Rigidbody component, in this case, the 2D version of the Rigidbody component, allows me to apply physics forces to a game object.

As a general rule, physics can’t be applied, or detected, without a Rigidbody.

So, for this to work, I need to add one, even though I’m not directly applying any physics forces.

Colliders in Unity are physics-based components so, in order to detect collisions against colliders, the player, at least, will need a Rigidbody attached to it.

The trigger collider objects, however, don’t need their own Rigidbodies.

The player, as the moving object, with its Rigidbody and collider components, is enough to detect the collision on either of the objects.

Now, with everything set up, I need to actually detect the collisions as they happen, which I can do with the On Trigger Enter message.

Increasing the score with On Trigger Enter

On Trigger Enter is a collision function that is automatically called when certain conditions are met.

In this case, when a collider enters the space of a trigger collider and so long as one of the objects also has a Rigidbody component attached to it.

void OnTriggerEnter(Collider other)
    {
        // A Trigger was hit!
    }

In the same way that Update is automatically called every frame, and Start is called before the first frame On Trigger Enter is called, by Unity, when an object intersects with a trigger collider.

It’s called on both colliders’ objects, so any script using On Trigger Enter, on either object, will receive the message. 

The collider parameter, “other”, gets a reference to the other collider involved in the collision, which can be useful for getting references to scripts on other objects when they touch.

I can use the On Trigger Enter message, and the collider reference it returns, to detect when the player has touched a collectable object and, when it does, destroy the collectable and add some points to the score.

Like this:

public class CollectObjects : MonoBehaviour
{
    public float score;
    public float pointsPerCollectable = 25;

    void OnTriggerEnter2D(Collider2D other)
    {
        score += pointsPerCollectable;
        Destroy(other.gameObject);
    }
}

You may have noticed that I’m using the On Trigger Enter 2D function for this.

In the same way that collider and Rigidbody components use different versions for 2D and 3D physics, the On Trigger Enter message also requires its own 2D version to work with 2D physics.

This can be surprisingly easy to forget and, when working in 2D, you’ll need to use On Trigger Enter 2D with a Collider 2D parameter for this to work correctly.

In this example, the player scores points by physically moving into objects, which makes keeping track of the score very simple to do.

Which, for the purpose of this basic example at least, works fine.

However… 

What if, instead of increasing the score when the player touches an object, you want to increase the score when something else happens.

Such as defeating an enemy, for example.

Chances are that, unlike moving into collectable objects, the player might not actually touch an enemy to destroy it.

Plus, whereas this script uses a fixed points value, different types of enemies might, instead, be worth different amounts of points.

So how can you increase a single score value based on different events in the game?

How to add to the score when an enemy is killed

In the previous example, the player object increased its own score amount by colliding with collectable objects.

However, chances are, in your game, you may want the objects that cause the score to increase, such as collectables or enemies to add to the score directly.

While there are many different ways to do this, one simple method is to simply make the player’s score variable Static.

Like this:

public class PlayerScore : MonoBehaviour
{
    public static float score;
}

A static variable is shared by all instances of the class, which means that, instead of getting a reference to an individual instance of a class, like the one that’s on the player, you can access the static variable via the class name itself.

Like this:

public class Collectable : MonoBehaviour
{
    float scoreValue = 25;

    private void OnTriggerEnter2D(Collider2D other)
    {
        PlayerScore.score += scoreValue;
        Destroy(gameObject);
    }
}

Doing it this way means that different objects can add different amounts to the score, depending on the object.

And while this is only a basic example, it works well as an easy method for increasing the score by different amounts from different objects, without needing a reference to the score script.

Using Static Variables to save the score between scenes

Another benefit of using a static variable to save the score is that static variables are saved between scenes.

Their value persists, which means that, if you load a new scene and use the same class to keep track of the score, the value will be the same as it was when leaving the previous scene.

While they’re best used in moderation, static variables can be extremely useful and, in this example, provide a simple method for increasing the score value from other objects.

They allow other scripts to easily access the score variable, across multiple scenes, without needing a reference to it beforehand.

How to access the score from another script (using Singletons)

While using a static variable is the most straightforward method for accessing the score from another script, it’s not the only way to do it.

In fact, there are many different ways to access a script from another object in Unity.

For example, the Singleton Pattern, which is a method of creating a single instance of a class inside a scene, is a commonly used technique for creating game manager objects, such as score managers.

It works by setting a single public static reference of the class type, to reference its own instance in the scene.

Like this:

public class ScoreManager : MonoBehaviour
{
    public static ScoreManager Instance { get; private set; }

    public float score;

    private void Awake()
    {
        Instance = this;
    }
}

Any script can then access the score value via the static reference to the local instance.

Like this:

ScoreManager.Instance.score += 25;

This also allows you to implement levels of control over the score value itself.

Such as how it’s increased and what happens when it is.

For example, instead of setting the score value directly, you could instead create a function that takes an amount to increase the score by.

Like this:

public class ScoreManager : MonoBehaviour
{
    public static ScoreManager Instance { get; private set; }

    public float Score { get; private set; }

    private void Awake() 
    {
        Instance = this; 
    }

    public void IncreaseScore(float amount)
    {
        Score += amount;
    }
}

Then, to increase the score by a set amount, from any script in the game, all you need to do is call the Increase Score function.

Like this:

ScoreManager.Instance.IncreaseScore(scoreValue);

How to display a score in Unity

Once you have a system for adding up the score value, you’re going to need a way to actually display the score in your game.

While there are different ways you could do this, one of the most straightforward methods is to simply use a UI Text object.

For this example, I’ve created a UI Text object with some placeholder text.

Unity UI Score Object

Next, I need to update the display to show the score value.

Here’s how…

First, I need to create a reference to the Text object.

To be able to do that, I need to add the UI namespace, so that I can access UI specific classes in my script, such as the Text component.

Like this:

using UnityEngine;
using UnityEngine.UI;

public class ScoreDisplay : MonoBehaviour
{
    public Text scoreText;
}

I can then connect the Text reference variable to the Text object in the Inspector.

Connect Score Text reference in Inspector in Unity

Don’t forget to connect the Score Text object to the Score Text reference variable in the Inspector.

Once it’s connected, I can then set the text property of the Text object to update the score display whenever points are added.

Like this:

float score;

public void IncreaseScore(float amount)
{
    score += amount;
    UpdateScoreDisplay();
}

public void UpdateScoreDisplay()
{
    scoreText.text = "Score: " + score;
}

Which looks like this:

Unity Score Display

You can’t normally display a numeric value in a text field like this.

However, this still works because of the “Score:” string before the reference to the score value, which is allowing the rest of the line to be converted.

Without it, I would instead need to convert the float to a string manually.

Like this:

scoreText.text = score.ToString();

But what if I want to format the number value to display in a specific way.

For example, I could use the String Format function to display a five-digit score, using zeros to fill out any unused spaces.

Like this: 

scoreText.text = string.Format("Score: {0:00000}", score);

This works by passing in the score value and outputting it in a specific format. In this case as a five-digit number.

Formatted score value in Unity

Using String.Format makes it easy to display the score in a way that makes sense to your game.

The String Format is useful for displaying numbers in a specific way, for example for time values and, in this case, scores.

However, while the score is now formatted in a way I like, each change to the score value is added instantly, as soon as it occurs.

For score values that change frequently, or that reflect distance, for example, this might be exactly what you need.

Otherwise, you might want to animate the score value so that it smoothly transitions when it’s updated.

How to create a score counter animation

Animating the score counter basically involves displaying the change between values when an increase occurs.

So, instead of instantly updating the score, the number appears to count up towards its new value instead.

Animated Score

By creating a 2nd ‘display value’ that follows the ‘real’ score, it’s possible to animate the score display without directly changing it.

This involves creating a second value, the display score, that follows the ‘real’ score value at a fixed rate.

Doing it this way allows you to animate the score value for display, without manipulating the actual score.

The display value can be animated with the Move Towards function which is a maths function that moves one value towards another at a fixed rate, without overshooting.

Like this:

using UnityEngine;
using UnityEngine.UI;

public class ScoreManager : MonoBehaviour
{
    public float score;
    public float transitionSpeed = 100;
    float displayScore;
    public Text scoreText;

    private void Update()
    {
        displayScore = Mathf.MoveTowards(displayScore, score, transitionSpeed * Time.deltaTime);
        UpdateScoreDisplay();
    }

    public void IncreaseScore(float amount)
    {
        score += amount;
    }

    public void UpdateScoreDisplay()
    {
        scoreText.text = string.Format("Score: {0:00000}", displayScore);
    }
}

Notice that, for this to work, the Update Score Display function now needs to be placed in Update, so that it continuously updates the score’s display in the scene.

The transition speed, when divided by delta time, determines how fast the value will change in points per second.

You’ll probably want to set this to a relatively high value to allow the score to animate in a reasonable amount of time.

In this case, I’ve used 100 points per second but it will depend on the scale of scores used in your game and how high they’re likely to reach.

Speaking of high scores…

How to display a High Score leaderboard in Unity

While you might only need to display a single score in your game, being able to display multiple high scores in a table can be a great way to demonstrate a player’s personal achievements or to compare their scores with other players’ records.

High Score Table in Unity

So how can you create a high score leaderboard in Unity?

First, you’ll need the high score table itself…

In this example, I’ve created a table of ten blank UI objects, where each row contains a separate object for the player’s name and for their score:

Each row has a separate object to display the player’s name and score.

While the list of results, as seen, can be reordered, these rows won’t actually move.

Instead, each row’s name and score values, which are currently blank, will be updated via a High Score Display script.

Which looks like this:

using UnityEngine;
using UnityEngine.UI;

public class HighScoreDisplay : MonoBehaviour
{
    public Text nameText;
    public Text scoreText;

    public void DisplayHighScore(string name, int score)
    {
        nameText.text = name;
        scoreText.text = string.Format("{0:000000}", score);
    }

    public void HideEntryDisplay()
    {
        nameText.text = "";
        scoreText.text = "";
    }
}

Each of the ten rows has an instance of the above script attached, with the score and name Text references connected to their child Text objects.

Each row’s script can then be used to either update the entry to show high score data or, optionally, hide the entry by making it blank, so that it doesn’t display anything.

Next, I’ll need to define what makes a single high score entry, and build a list to manage all of them together.

Create a single high score entry

Because each high score includes both a name and score value, I need a way to store the two pieces of information together.

The simplest way for me to do that is with a High Score Entry class, that contains a variable for the player’s name and for their score.

Like this:

public class HighScoreEntry
{
    public string name;
    public int score;
}

That’s it, that’s the whole class.

There are no using directives at the top of the script.

There’s no Start, no Update.

It doesn’t even inherit from Monobehaviour.

It’s just a string and an integer, and that’s all.

Put simply, it’s nothing more than a data type to store the high score values together and with it, I can begin to build the high scores list.

Create the high score List

A List in Unity is a collection type that’s similar to an array, except that it’s more easily sorted and resized.

Which is ideal for a set of high scores.

In this case, I want to create a list of High Score Entry classes.

Like this:

List<HighScoreEntry> scores = new List<HighScoreEntry>();

This initialises a blank list that’s ready to receive data.

While I can add to the list directly, using the Add function, it’s slightly easier for me to create my own Add Scores function, to create new high score entries.

Like this:

void AddNewScore(string entryName, int entryScore)
    {
        scores.Add(new HighScoreEntry { name = entryName, score = entryScore });
    }

This allows me to set the name and score value of the entry when I create it, which makes it easier to create and manage the high score entries.

It also allows me to easily create some test data when the script first runs:

void Start()
    {
        // Adds some test data
        AddNewScore("John", 4500);
        AddNewScore("Max", 5520);
        AddNewScore("Dave", 380);
        AddNewScore("Steve", 6654);
        AddNewScore("Mike", 11021);
        AddNewScore("Teddy", 3252);
    }

How to sort the list and update the display

So I now have a high score list, a method of creating new score values, but no way of actually updating the display.

So here’s how to do that…

First, I need to sort the list.

This is done using the List Sort function:

scores.Sort((HighScoreEntry x, HighScoreEntry y) => y.score.CompareTo(x.score));

What’s happening here is that I’m declaring two High Score Entry types, x and y, and comparing them based on their score value, highest to lowest.

Then, with the list sorted, I can update the name and score text objects in the table rows, one by one.

A simple way to do this is to create an array to store references to each one of the High Score Display scripts that are on the table rows.

Like this:

public HighScoreDisplay[] highScoreDisplayArray;

And then manually connect each of them in the Inspector, in their correct order, one to ten.

Then, to display the high score values, I can loop through the list of scores, which has now been sorted, adding each set of data to a row. Or, if there are no more entries to display, displaying a blank row instead.

Which looks like this:

for (int i = 0; i < highScoreDisplayArray.Length; i++)
        {
            if (i < scores.Count)
            {
                highScoreDisplayArray[i].DisplayHighScore(scores[i].name, scores[i].score);
            }
            else
            {
                highScoreDisplayArray[i].HideEntryDisplay();
            }
        }

Here’s how it all looks together…

High score leaderboard: Finished code example

using System.Collections.Generic;
using UnityEngine;

public class HighScores : MonoBehaviour
{
    public HighScoreDisplay[] highScoreDisplayArray;
    List<HighScoreEntry> scores = new List<HighScoreEntry>();

    void Start()
    {
        // Adds some test data
        AddNewScore("John", 4500);
        AddNewScore("Max", 5520);
        AddNewScore("Dave", 380);
        AddNewScore("Steve", 6654);
        AddNewScore("Mike", 11021);
        AddNewScore("Teddy", 3252);

        UpdateDisplay();
    }

    void UpdateDisplay()
    {
        scores.Sort((HighScoreEntry x, HighScoreEntry y) => y.score.CompareTo(x.score));

        for (int i = 0; i < highScoreDisplayArray.Length; i++)
        {
            if (i < scores.Count)
            {
                highScoreDisplayArray[i].DisplayHighScore(scores[i].name, scores[i].score);
            }
            else
            {
                highScoreDisplayArray[i].HideEntryDisplay();
            }
        }
    }

    void AddNewScore(string entryName, int entryScore)
    {
        scores.Add(new HighScoreEntry { name = entryName, score = entryScore });
    }
}

This is a simple method of displaying a sorted list of high scores in a fixed array of score display rows, and it works.

There’s just one problem.

While this works with test data, there’s no way to actually save the high score results to disk.

And, after all, what’s the point of a high score if it can’t actually be saved?

So, here’s how to do that.

How to save a high score to a file in Unity

There are several different methods for saving high score data in Unity.

Some can be very simple, such as Player Prefs, which can be used to save single points of data, such as a string, a float or an integer, using a key.

How to save a single high score (using Player Prefs)

Player Prefs are designed to save player preferences data between gameplay sessions.

You’d typically used Player Prefs to store graphics and gameplay settings, control preferences and other options.

This is because Player Prefs are designed to hold small amounts of data in a simple way.

However, I could also use Player Prefs to store a high score value.

For example, I could save the game’s high score by setting a float Player Pref, with a string key of “highScore”, using the Set Float function of the Player Prefs class.

Like this:

PlayerPrefs.SetFloat("highScore", 123456);

I could then load the same value again using the Get Float function.

Like this:

float highScore = PlayerPrefs.GetFloat("highScore");

The Player Prefs value is stored in a settings file that’s specific to the project.

The value is written to disk automatically when the application is closed but, alternatively, all of the preference values can be saved manually at any time using the Save function.

Like this:

PlayerPrefs.Save();

Keep in mind, however, that this isn’t explicitly necessary and can cause a slight delay when used. It’s designed to avoid data loss in the event of a crash.

Player Prefs data persists between gameplay sessions, meaning that it’ll work even if you close the game and reload it again.

Which is entirely the point.

Keep in mind, however, that Player Prefs work in the same way in the Unity Editor as they do in the build.

Which means that even when closing and reopening the editor, if you want to clear the Player Prefs value, you’ll need to manually delete the key.

Like this:

PlayerPrefs.DeleteKey("highScore");

Player Prefs can be incredibly useful for saving a single high score value.

And while it’s not what Player Prefs are designed to do, It’s an easy method, that works well.

However… 

Using Player Prefs to store a set of values, such as a full high score leaderboard, for example, can be tricky.

This is because Player Prefs work best with single pieces of data, not classes or lists.

So how can you save and load a list of high score values in Unity?

One option is to use XML.

How to save a list of high scores to a file in Unity (using XML)

XML, which stands for extensible markup language, is a document encoding method that is designed to be both machine readable and human readable.

In Unity, XML can be used to encode game data into XML documents, which allows you to save more complex sets of data to disk than when using Player Prefs, for example.

Which means that, instead of saving a single value, I could instead save an entire list of data to an XML file.

Such as my high score list, for example.

Here’s how it works.

In the previous example, I created a leaderboard of high score data using a List, which I could then sort and display in an array of UI rows to show a table of high scores.

To save and load the high score list as an XML file, I’m going to create a separate script, just for managing XML files, which I’m calling XMLManager.

First, I’m going to make the XML Manager a Singleton, by giving it a static public reference to an instance of its own type and setting it to itself in Awake.

Like this:

public static XMLManager instance;

    void Awake()
    {
        instance = this;
    }

While this isn’t strictly necessary (I could simply get a reference to the instance of the script instead), it does mean that I can call the Save and Load functions that will be on this script from anywhere in the scene.

However… while this can be very convenient, be careful not to access the same file from two different scripts at the same time, as this can cause an error.

Next, I need to add two specific namespaces to the top of the class.

System.Xml.Serialization, which allows me to serialize data into XML files, and System.IO, which is required to save and load files.

using System.Xml.Serialization;
using System.IO;

Next, I’m going to write a very simple class inside of this XML Manager class, called Leaderboard.

[System.Serializable]
public class Leaderboard
{
    public List<HighScoreEntry> list = new List<HighScoreEntry>();
}

And then add a reference to a variable of the Leaderboard class to the top of the XML Manager script:

public Leaderboard leaderboard;

Why do it like this?

The Leaderboard is simply a serializable container for the data I’m going to save.

It contains one variable, a List, called list, that will hold the high score data when it’s saved and loaded.

While I’ve placed this inside of the XML Manager class document, it could also be separate.

However, what’s important is that it’s a serializable class, meaning that it can be saved, and that I have a reference to an instance of it inside my XML Manager class, called leaderboard.

That way whenever I want to refer to the data that’s being saved or loaded, I just need to reference leaderboard.list.

All that’s left to do is to write a function to save and load the file.

To save, I want to be able to pass in a list of high scores and then save that to a file.

Like this:

public void SaveScores(List<HighScoreEntry> scoresToSave)
    {
        leaderboard.list = scoresToSave;
        XmlSerializer serializer = new XmlSerializer(typeof(Leaderboard));
        FileStream stream = new FileStream(Application.persistentDataPath + "/HighScores/highscores.xml", FileMode.Create);
        serializer.Serialize(stream, leaderboard);
        stream.Close();
    }

What’s happening here is a list of high scores is being passed in and is being saved locally using the leaderboard variable.

Then a new serializer is declared, with a type of Leaderboard (so it knows what type of object to expect), and a file stream is opened and is directed to the Application Persistent Data Path.

The leaderboard variable is then passed in to the serializer and the file stream is closed, saving the file.

For this to work, the high scores folder will already need to exist in the Persistent Data Path location.

Which is why it’s a good idea to check for it when the script is first loaded.

Like this:

if (!Directory.Exists(Application.persistentDataPath + "/HighScores/"))
    {
        Directory.CreateDirectory(Application.persistentDataPath + "/HighScores/");
    }

Should you use Data Path or Persistent Data Path to save data in Unity

When in the editor, Application.dataPath refers to the Assets folder of the project.

While developing your game, this can be useful, as it means data you save will be be available alongside the other assets in the project, making debugging slightly easier.

However, in the build of the game, the Assets folder doesn’t exist in the same way that it does in the editor.

Instead, the location that Application.dataPath refers to will vary depending on the platform.

Also, while Data Path can be useful in the editor, it’s not suitable for storing save data persistently. For that, you’ll most likely want to use Persistent Data Path instead.

Application.persistentDataPath works in the same way as Data Path except that it’s designed for data that’s meant to be stored between sessions.

Which makes it more suitable for high scores.

This is especially true on mobile, where Persistent Data Path points to a public directory on the device that is separate from the application’s installation.

Which means that, if the app is updated, the high scores are protected.

Loading the file involves a similar process, except that, instead of creating the file, I instead need to open and deserialize it.

Like this:

public List<HighScoreEntry> LoadScores()
    {
        if (File.Exists(Application.persistentDataPath + "/HighScores/highscores.xml"))
        {
            XmlSerializer serializer = new XmlSerializer(typeof(Leaderboard));
            FileStream stream = new FileStream(Application.persistentDataPath + "/HighScores/highscores.xml", FileMode.Open);
            leaderboard = serializer.Deserialize(stream) as Leaderboard;
            stream.Close();
        }

        return leaderboard.list;
    }

This function checks to see if a file already exists and, if it does, opens a file stream from the same location as before.

It then deserializes the result as a Leaderboard class and sets the local value to whatever was loaded.

If there was a file to load, the leaderboard’s list value it now contains the saved list data.

If there wasn’t, the Leaderboard class automatically initialises the list variable that it contains as a new list, meaning it will be blank but it won’t cause an error.

Here’s what the XML Manager class looks like all together…

Saving to an XML file in Unity: Finished code example

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

using System.Xml.Serialization;
using System.IO;

public class XMLManager : MonoBehaviour
{
    public static XMLManager instance;
    public Leaderboard leaderboard;

    void Awake()
    {
        instance = this;

        if (!Directory.Exists(Application.persistentDataPath + "/HighScores/"))
        {
            Directory.CreateDirectory(Application.persistentDataPath + "/HighScores/");
        }
    }

    public void SaveScores(List<HighScoreEntry> scoresToSave)
    {
        leaderboard.list = scoresToSave;
        XmlSerializer serializer = new XmlSerializer(typeof(Leaderboard));
        FileStream stream = new FileStream(Application.persistentDataPath + "/HighScores/highscores.xml", FileMode.Create);
        serializer.Serialize(stream, leaderboard);
        stream.Close();
    }

    public List<HighScoreEntry> LoadScores()
    {
        if (File.Exists(Application.persistentDataPath + "/HighScores/highscores.xml"))
        {
            XmlSerializer serializer = new XmlSerializer(typeof(Leaderboard));
            FileStream stream = new FileStream(Application.persistentDataPath + "/HighScores/highscores.xml", FileMode.Open);
            leaderboard = serializer.Deserialize(stream) as Leaderboard;
        }

        return leaderboard.list;
    }
}

[System.Serializable]
public class Leaderboard
{
    public List<HighScoreEntry> list = new List<HighScoreEntry>();
}

Then to save the file, all I need to do is call the Save Scores function from the XML Manager class, passing in the List variable that I want to save.

Like this:

List<HighScoreEntry> scores;

    void Save()
    {
        XMLManager.instance.SaveScores(scores);
    }

And to load the saved scores, I can set a local list to the result of the Load Scores function which, in this example, returns the same type of list value.

List<HighScoreEntry> scores;
    void Load()
    {
        scores = XMLManager.instance.LoadScores();
    }

Using XML to save a list of high scores allows you to save and load more complex data than when using Player Prefs.

It’s relatively straightforward to use and works well.

However, there is a drawback.

While this method does work well, the nature of the XML format, which was designed to be readable by both machines and humans, means that an entry can be easily viewed and easily changed in some cases.

It’s possible to edit the file using a text editor, which means that players could change the high score values without too much difficulty.

Saving an XML File in Unity

XML Files, by design, are human readable, meaning they’re fairly easy to change with a text editor.

This is both the advantage and disadvantage of using XML with Unity.

On the one hand, being able to edit XML-based asset data outside of the editor can be very useful.

However, it also means that, by default, the saved data is easy to view and easy to change with any text editor.

For this reason, while using XML for high score and save data does work very well, many prefer to use ready-made options, such as Easy Save which, as well making it much quicker to set up a save / load system, makes advanced features, such as file encryption, much easier to use.

Now it’s your turn

How are you counting the score in your game?

Are you saving the high score in a Player Prefs value, an XML file or something else?

And what score counting tips do you have that you know others will find useful.

Whatever it is, let me know by leaving a comment.

Attribution

From the experts

John French profile picture

by John French

Game audio professional and a keen amateur developer.

Get Game Development Tips, Straight to Your inbox

Get helpful tips & tricks and master game development basics the easy way, with deep-dive tutorials and guides.

How this content was created

This article was written using first-hand research and experience, without the use of AI. For more information on how Game Dev Beginner articles are written, see my writing policy.

Comments

  1. Nice write-up as always!

    Also useful for high-value scores is N0 formatter e.g.
    string.Format(“Score: {N0}”, score);
    Which will display your score as 1,234,567 (or whatever separator is appropriate for your current locale).

  2. Oh and I forgot to mention, there are various versions of “N”:
    N: 1,234.56
    N0: 1,234
    N1: 1,234.6
    N2: 1,234.56
    N3: 1,234.567

  3. how would i check the last in a top 10 and see if a new score is better to add to list?

    1. Author

      One option is to keep all of the results (so more than ten) sort the list so that the top ten results are at the top and then only show the top ten (entries 0-9 in the list).

  4. When I try to call load scores function using “scores = XMLManager.instance.LoadScores()” in HighScores class, unity returns an error: NullReferenceException: Object reference not set to an instance of an object.
    How to deal with it?

    1. I made all the functions static, but get another error: XmlException: Root element is missing.

    2. Author

      Just to double-check, did make sure to set the instance of the XML Manager using “instance = this;” in Awake?

      1. Yes. My code is the same as your example. I don’t know why but now I want to focus on “XmlException: Root element is missing.”

          1. Author

            After looking into this, the most likely cause is that, at some point, the file you’re loading from was emptied. This means that the file exists but the root element of the XML isn’t there. This can happen if you try to access the file from two places at once, causing an IO sharing violation error. Deleting the file will fix it, when it gets saved it will be properly created again, but keep in mind, if you are trying to access the file when the filestream is open, e.g. trying to load and save at the same time, the error will pop up again.

          2. I had the same issue – here is how I fixed it.
            1) attached the XML Manager script to an object in the scene (otherwise awake wasn’t being called). Apparently all scripts that inherit from MonoBehaviour have to be attached to an object. This resolved the first error, but left me with another one – about multiple access issues. That was fixed by:
            2) Adding a stream.Close(); to LoadScores. In the code provided there was one in SaveScores but not LoadScores. Now it works! thanks so much.

  5. Thanks for the great tutorial.

    is easy save the perfect solution to stop people messing with scores? Can people still cheat?

    1. Author

      I would imagine that people can always cheat somehow, although I expect that Easy Save probably does a good enough job to prevent most attempts. Probably best to ask the developer, I’m sure they’d help: https://moodkie.com/

  6. Hi
    How do I assign HighScoreEntry in the XMLManager script as I just get
    The type or namespace name ‘HighScoreEntry’ could not be found (are you missing a using directive or an assembly reference?)
    Thanks for this write up.

    1. Author

      Hi, just checking, did you make the HighScoreEntry class? The XML example uses a High Score Entry class that is shown in an earlier example (this section). If you jumped straight to the XML section, you may have missed it.

  7. i have used your ontriggerenter 2d method , i have one slight issue , every time the collectible and the player collide the player gets destroyed. if you could help me that would be great.

    1. Author

      Just to double check, are you calling Destroy(other.gameObject); from the player or the collectible? In this example, it needs to be triggered from the player, or the collectible needs to destroy itself, instead of the ‘other’ collider.

  8. Hi John, this post has cleared up a lot of issues I was having with XML file management, but I still cannot figure out a way to save my player name and score to the XML file at the end of my final level (I can easily save these as global values or via player prefs but cannot add to the list / save to the XML)

      1. Thanks very much for the response, John. In the meantime I found a solution. After saving the current name and score temporarily using PlayerPrefs, I found I could load the scene with my High Score table, load the list of scores from the XML file, add the saved name and score to the list, and save the entire list again (overwriting the original XML file). Then I just had to clear PlayerPrefs once the new list was saved. Not the most elegant method but it works so far.

  9. So for my game, I’m increasing the score when my player kills a zombie. Where do I attach my score script to? The player’s GameObject or the Text GameObject?

    1. Author

      There are lots of ways you could do it, but you would probably want to keep the script that changes the score wherever the actual score is stored. Then you can choose how you want to trigger it, i.e. from the zombie when it dies, or from the player that kills it. So this might mean that you have one script that updates the score that is on your text object and another script that raises an event when a zombie is killed.

Leave a Comment