High Scores

This tech tip adds a new high score category to P3SampleApp and covers some high score implementation details.

High scores are well documented in the SDK Guide here file:///C:/P3/P3_SDK_V0.8/P3SampleApp/Documentation/html/_persistent_data.html#HighScores

P3SAHighScoreCategories

The new high score will reflect how many times the lower lanes have been completed. To implement the new high score we need to create a HighScoreCategory in P3SAHighScoreCategories and add it to the list returned by GetCategories().

Edit Assets\Scripts\Modes\DataManagement\P3SAHighScoreCategories.cs, change GetCategories() as shown and add the LowerLanes() method. Bold lines indicate a changed line. The rest of the file is not shown for licensing reasons.

public static List<HighScoreCategory> GetCategories()

{

    List<HighScoreCategory> cats = new List<HighScoreCategory>();

    cats.Add (Score ());

    cats.Add (RightRamps ());

    cats.Add (LeftRamps ());

    cats.Add (LowerLanes ());

    return cats;

}

private static HighScoreCategory LowerLanes()

{

    HighScoreCategory hsCat = new HighScoreCategory("NumLaneCompletions", "Lower Lanes", HIGH_SCORE_COUNT);

    List<float> values = new List<float>();

    float startingValue = HIGH_SCORE_COUNT;

    for (int i = 0; i < HIGH_SCORE_COUNT; i++)

    {

        values.Add(startingValue - i);

    }

    hsCat.SetDefaultValues(values);

    return (hsCat);

}

As specified by the first argument of the HighScoreCategory constructor, this high score category will take the player’s score value from NumLaneCompletions in the player’s data. LanesMode already stores the number of lower lane completions in the player’s data with that key. It does it with this statement:

data.currentPlayer.SaveData("NumLaneCompletions", numCompletions);

We were lucky that the player’s data was already populated by an existing mode. In general, you will need to implement the code to save the category score in player’s data.

The player’s data key NumLaneCompletions makes sense. We cannot say the same for two of the existing high score categories in P3SampleApp. The keys "Evt_LeftRampInc" and "Evt_RightRampInc" are rather unfortunate.

HighScoreCategory hsCat = new HighScoreCategory("Evt_LeftRampInc", "Left Ramps", 10);

HighScoreCategory hsCat = new HighScoreCategory("Evt_RightRampInc", "Right Ramps", 10);

Those are event names which can lead to confusion. These events are sent by ShotCounter mode but the same name is used for the key in player’s data. It would have been better to pass another parameter on the ShotCounter constructor for the player’s data key, something like "NumLeftRamps" for the left ramps and "NumRightRamps" for the right ramps. We will not change that code now, but keep that in mind when designing new HighScoreCategories.

Try It

After you made the changes above, hit the Play button in Unity. Start a game by pressing s. Launch the first ball by pressing l (lowercase L). Drag the mouse over the lower lanes to complete the set of 4 lower lanes at least twice. Drain the ball by pressing 0 (zero). Launch and drain ball 2 and 3. Enter your name for the Lower Lanes high score.

When in Attract mode, you can use the main flipper buttons to display the high score categories immediately. The right flipper button moves forward and the left flipper button moves backwards among the categories.

When testing, remember a player is not eligible to receive a high score if he is a member of a team (multiple players combining their scores), or the game was restored from a save point.

Customizing the HighScoreCategory

You can specify the number of places for a HighScoreCategory in the 3rd argument of the constructor. In the lower lanes high score category, we passed the constant  HIGH_SCORE_COUNT which is equal to 10.

It is recommended to keep numPlaces at 10 or less. If numPlaces is >10, then the high score display in Attract mode is broken with names appearing below the window box. To display this properly would require reimplementing HighScoreResultsMode.

The defaultNames are the names of the players for the initial high scores. If the defaultNames are not specified, the HighScoreCategory class picks: { "G S", "J W", "R C", "L P", "T W", "T J", "S G", "D T", "N P", "S S", "T L" }. You can specify your own defaultNames with SetDefaultNames. For example:

hsCat.SetDefaultNames(new List<string>() { "BEN", "BOB", "EVE", "IAN", "JAY", "JIM", "JOE", "KEN", "SAM", "TOM" });

If numPlaces is less than the number of default names, then the extra names are ignored. If numPlaces is greater than the number of defaultNames, then the name "A A" is used to fill the missing names.

The defaultValues are the values for the initial high scores. Those are the values you have to beat to be allowed to enter you name in the high score table. The default defaultValues are all 0. We showed how to set the defaultValues with SetDefaultValues() in the code above.

If numPlaces is less than the number of default values, then the extra values are ignored. If numPlaces is greater than the number of defaultValues, then the last value is duplicated to fill the missing values.

The zero value is (conceptually) the score the player starts with at the start of the game. The default zeroValue is 0. Notice there is a single zeroValue. Compare that to numPlaces many default values.

The default sortUp is false. This is the usual case where the best score is the highest score. The score decreases as the positions increase. Sometimes you want the opposite. For example, when the score is the time it takes to finish an objective. Finishing faster is better. In that case, change the sort order with SetSortUp(true) to let the score increase as the positions increase.

When sortUp is true, you want the player to start with a bad score, therefore the zeroValue should be high, certainly not 0.

The default decimalPlaces is 0. This displays the floating point high score as an integer. Set this value to the number of digits to the right of the decimal point. For example, if the score represents a time interval, you might want to call SetDecimalPlaces(2) to display up to 1/100th of a second.

Statistics Menu

To bring up the Statistics menu, open the coin door and press the launch button, then select Statistics.

“Enable high score display” determines whether the high scores are shown in Attract mode.

Selecting “Reset scores to defaults” brings up a confirmation dialog. If you select Yes, this will erase all user high scores for this application and sets them back to the initial high scores as determined by defaultNames and defaultValues.

Selecting “Clear high scores” brings up a confirmation dialog. If you select Yes, this will erase all user high scores for this application and sets them all to the zeroValue. This score is meant to be so bad that the next player is sure to enter his name for a high score, and the next player after that, until all high score positions are filled again.

High Score GameAttributes

High scores are stored in GameAttributes to make them persistent.

The number of places in the high score category is how many entries are kept for this high score. If there are 10 places, up to 10 players will be able to enter their name to immortalize their score.

If the high score category has numPlaces entries, this will create numPlaces GameAttributes named HS_<description>NN where NN goes from 0  to numPlaces-1. For example, the lower lanes high score category has 10 places and the description is “Lower Lanes”, so the GameAttributes will be named “HS_Lower Lanes0” to “HS_Lower Lanes9”. Notice the space character in the name, that’s perfectly fine.

The value of the high score GameAttribute has the format "<name>:<score>" where <name> is the name the player entered and <score> is the actual score achieved. For example, the value could be GSS:1,000.0000000000 which means GSS got a high score when scoring 1000.

All these internal details are taken care of by HighScoreResultsMode when displaying the current high score values.

Since high scores are stored internally as float values before they are converted to string, they become inaccurate above 10M. There is not enough precision in a float to store that many digits. You can still have high scores higher than that, but you will lose the least significant digits: 10M loses track of the last digit, 100M loses track of the last two digits, and so on.

High score GameAttributes are stored in <userhome>/.multimorphic/P3/Data/<AppCode>/HighScores.db

This is determined by HighScoresMode, i.e. the base class of P3SAHighScoresMode. In P3SAHighScoresMode there is almost nothing except the database version for the HighScores.db file.

We changed the GameAttributes by adding a new high score category, therefore we might want to increase the DBVersion. Be careful with that because this will revert to the default high scores, wiping out all existing user high scores for this application.

If you decide to change the DBVersion, it is always a good idea to keep DataVersion the same value. Since DataVersion is 7 and DBVersion is 4 in P3SampleApp, I would probably jump to 8.

I did not want to wipe out the previous high scores, so I did not increase DBVersion in this tutorial. Increasing DBVersion is not absolutely necessary in this case since we are strictly adding new GameAttributes.

Notice the member “private List<HighScoreCategory> categories;” in P3SAHighScoresMode is dead code. It can safely be removed. This is not how HighScoresMode gets the list of categories.

In reality, P3SAGameAttributeManagerMode calls P3SAHighScoreCategories.GetCategories() and posts the Evt_AddHighScoreCategory event for each category. HighScoresMode handles that event to recreate the list of categories.