XNA Essentials

Game Programming for Xbox 360, PC + Windows Phone

NAVIGATION - SEARCH

Storing and Retreiving High Scores

To display high scores, we must first create a format to store the high scores. Then the high scores need a place to be physically stored. Here is an example of the structure to hold high score data:

[Serializable]
public struct HighScoreData
{
    public string[] PlayerName;
    public int[] Score;
    public int[] Level;
 
    public int Count;
 
    public HighScoreData(int count)
    {
        PlayerName = new string[count];
        Score = new int[count]
        Level = new int[count];

        Count = count;
    }
}

The struct is marked as serializable so the data can be saved to a file. We are simply storing a list of player names, their scores and the levels they reached. The constructor requires a count parameter that determines how many player names will be stored in the high score list. We need to add the System.Xml.Serialization namespace to our using statements. We need to add two helper methods. One will save the high scores and the other will load them.

public static void SaveHighScores(HighScoreData data, string filename)
{
    // Get the path of the save game
    string fullpath = Path.Combine(StorageContainer.TitleLocation, filename);
 
    // Open the file, creating it if necessary
    FileStream stream = File.Open(fullpath, FileMode.OpenOrCreate);
    try
    {
        // Convert the object to XML data and put it in the stream
        XmlSerializer serializer = new XmlSerializer(typeof(HighScoreData));
        serializer.Serialize(stream, data);
    }
    finally
    {
        // Close the file
        stream.Close();
    }
}

We need to add the System.IO and Microsoft.Xna.Framework.Storage namespaces to our using statements. Regardless if we are using the Xbox 360 or Windows, we use normal .NET Framework methods to load and save files. We use the Combine method from the Path class to combine the TitleLocation path we retrieved from XNA's StorageContainer class with the filename passed into the method. We wrap the serialization code around a try/finally block in case something goes wrong. We do not want to leave the file stream open. Serializing the .NET Framework is extremely easy.

Following is the code to load the high scores:

public static HighScoreData LoadHighScores(string filename)
{
    HighScoreData data;
 
    // Get the path of the save game
    string fullpath = Path.Combine(StorageContainer.TitleLocation, filename);
 
    // Open the file
    FileStream stream = File.Open(fullpath, FileMode.OpenOrCreate,
    FileAccess.Read);
    try
    {
 
        // Read the data from the file
        XmlSerializer serializer = new XmlSerializer(typeof(HighScoreData));
        data = (HighScoreData)serializer.Deserialize(stream);
    }
    finally
    {
        // Close the file
        stream.Close();
    }
 
    return (data);
}

The code is very similar to how we saved the data. The filename is passed into the method and we obtain the full path the same way. We open the file for read-only access and then deserialize the high score data inside of a try/finally block. We then return the high score data.

When the game starts we want to check to see if the high score file exists on the system. If it does not, we need to create it with some default high scores. This could be done inside of Initialize method.

protected override void Initialize()
{
    // Get the path of the save game
    string fullpath = Path.Combine(StorageContainer.TitleLocation, HighScoresFilename);
 
    // Check to see if the save exists
    if (!File.Exists(fullpath))
    {
        //If the file doesn't exist, make a fake one...
        // Create the data to save
        HighScoreData data = new HighScoreData(5);
        data.PlayerName[0] = "Neil";
        data.Level[0] = 10;
        data.Score[0] = 200500;
 
        data.PlayerName[1] = "Shawn";
        data.Level[1] = 10;
        data.Score[1] = 187000;
 
        data.PlayerName[2] = "Mark";
        data.Level[2] = 9;
        data.Score[2] = 113300;
 
        data.PlayerName[3] = "Cindy";
        data.Level[3] = 7;
        data.Score[3] = 95100;
 
        data.PlayerName[4] = "Sam";
        data.Level[4] = 1;
        data.Score[4] = 1000;
 
        SaveHighScores(data, HighScoresFilename);
    }
 
    base.Initialize();
}

The code simply populates some default high score values and saves them to a file. The HighScoresFilename field needs to be added to the our code:

public readonly string HighScoresFilename = "highscores.lst";

The last thing we need to do is actually save the score when a game is over if it belongs in the list.

private void SaveHighScore()
{
    // Create the data to save
    HighScoreData data = LoadHighScores(HighScoresFilename);
 
    int scoreIndex = -1;
    for (int i = 0; i < data.Count; i++)
    {
        if (score > data.Score[i])
        {
            scoreIndex = i;
            break;
        }
    }
 
    if (scoreIndex > -1)
    {
        //New high score found ... do swaps
        for (int i = data.Count - 1; i > scoreIndex; i--)
        {
            data.PlayerName[i] = data.PlayerName[i - 1];
            data.Score[i] = data.Score[i - 1];
            data.Level[i] = data.Level[i - 1];
        }
 
        data.PlayerName[scoreIndex] = "Player1"; //Retrieve User Name Here
        data.Score[scoreIndex] = score;
        data.Level[scoreIndex] = currentLevel + 1;
 
        SaveHighScores(data, HighScoresFilename);
    }
}

We start the method by loading our high score file. We then loop throguh each high score checking to see if the score we ended the game with is higher than the one we are checking. We loop through the scores in order. They are saved highest to lowest and if find that the game score is greater than the stored score, we store the index. If a new high score should be added, then we loop through current high scores and swap out as many as needed. We push the other scores down the list and then save our high score in the newly available spot. Finally, we save the high score list again. The above code assumes there is a score field as well as a currentlLevel field.

We actually call this method when the player's game is over.

SaveHighScore();