Creating a High Score System in Unity
Creating a high score system was a fun challenge. I'll do my best to explain my process and approach for my game. Hopefully you'll find this information helpful if you'd like to add a similar system to your own game.
Let's start by looking at the finished product, so we have an idea of where we are going.
The Concept: When the Player gets a score that is higher than an existing high score in the top 10 list, they can enter their initials for the new high score. They can cycle through three initials, which consist of the English alphabet, numbers, and the hashtag symbol (#). Once the new high score is entered, it appears in the top 10 list, which is then saved locally to the Player's device, using PlayerPrefs
(See Unity's Documentation for more information on PlayerPrefs
, as that is outside the scope of this devlog).
Now that you understand the concept behind it, let's break down the flow of the code:
1. When the Player gets a new score, I check if that score is higher than the lowest high score in the top 10 list (10th place)
2. If the new score is higher, I save the high score to a newScore
variable, and I set a newHighScore
variable to true
(the new high score may be higher than 9th, or even 1st in the top 10 list, but for now, I just need to know if it makes the top 10 list or not (greater than 10th))
3. When the Player gets a game over, since newHighScore
is true
, I load the New High Score screen (pictured above) so they can enter their initials. This screen reads the existing high scores and corresponding initials from two separate arrays (one for initials and one for scores) that are saved to PlayerPrefs
4. Before the high scores are printed to the page (shown above), the existing high score that the new high score is replacing must be removed, and dropped down one number in the list, if applicable(if you replace 10th place high score, it is just removed from the list). This poses an interesting challenge: you have to "save" the existing high scores temporarily, along with their number in the listing, so you can move them all down one position in the list (more on that later).
5. Finally, the new high score flashes on the screen (see tweet below):
Elevader: Added local high scores to the game. It was a fun challenge. #gamedev #indiedev #screenshotsaturday #highscores #elevadergame #madewithunity pic.twitter.com/BumFXg2udW
— Mike (@AnimationMW) December 18, 2021
Here is how my Classes are broken down. All of my code is written in (Unity) CSharp:
First and foremost, I have a gameManager
Class. This holds all my variables that I want to move between scenes and have access to, such as the number of continues the player has left, or the player's checkpoint. These variables must persist through a scene when the player dies, assuming they still have continues left. To do that, I have placed a simple Don't Destroy script on the GameObject in the scene that the gameManager
script is attached to.
public class dontDestroy : MonoBehaviour {
public string tagName; //the tag that is attached to the GameObject I don't want to destroy
void Awake() {
// When the GameObject is moved from one scene to another, the object may already exists
// in that scene, in which case you can remove the copy
if ((GameObject.FindGameObjectsWithTag(tagName)).Length > 1) {
// Destroy the GameObject. This is a built-in function in Unity.
Destroy(gameObject);
}
else {
// If this is the only copy if the GameObject with that tag, don't destroy it in the scene.
// This is also a built-in function in Unity.
DontDestroyOnLoad(this);
}
}
}
Back in the gameManager
Class, I have two key variables:
public int newScore;
public bool newHighScore = false;
newScore
as the name suggests is the new score, which in my case is the current Floor in the elevator the player is on. If the newScore
is higher than the lowest high score, then newHighScore
is true
, as shown in the code snippet below:
// if the current floor the Player is on is higher then the 10th, lowest,
// high score (9th index in the HighScoresList Array),
// set newScore to the current floor number, and set newHighScore to true
if(elevatorFloorNumber > manager.GetComponent<gamemanager>().HighScoresList[9]){
manager.GetComponent<gamemanager>().newScore = elevatorFloorNumber;
manager.GetComponent<gamemanager>().newHighScore = true;
}
As I said before, when the Player gets a game over, the initials screen is loaded. Let's take a look at the initialsManager
class to see how that works.
The available initials are stored in a List, declared at the top of the Class:
List<string> initialLetters = new List<string>{
"A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U",
"V","W","X","Y","Z","#","0","1","2","3","4","5","6","7","8","9"," "
};
In the Start()
function, I read the newScore
so it can be printed to the screen and set the initials to default to "AAA" (see the code snippet below):
void Start () {
// If the text GameObject is on the page set it to the newScore to display it
if(highScoreNumberText){
highScoreNumberText.GetComponent<unityengine.ui.text>().text = "" + manager.GetComponent<gamemanager>().newScore;
}
// If the first initial text is present, set it and the 2nd and 3rd initial to the first letter
// of the alphabet (A, 0 in the index of the initialLetters Array)
if(firstInitialText){
firstInitialText.GetComponent<unityengine.ui.text>().text = initialLetters[0];
secondInitialText.GetComponent<unityengine.ui.text>().text = initialLetters[0];
thirdInitialText.GetComponent<unityengine.ui.text>().text = initialLetters[0];
}
}
Each initial has its own controllers for moving up or down. The code is the same for each, so I'll just cover the first initial. I have a public function firstInitialUp()
that is called when the Player clicks the up arrow. Likewise, I have a firstInitialDown()
function that is called when the Player clicks down. The functions increase/decrease the position in the initialLetters
List and update the initial text to display the new string.
public void firstInitialUp(){
// Move the list index up one
firstInitialNumber++;
// If the index has reached the end of the List, loop the list back around to the beginning
if(firstInitialNumber == initialLetters.Count){
firstInitialNumber = 0;
}
// Display the new initial in the text
firstInitialText.GetComponent<unityengine.ui.text>().text = initialLetters[firstInitialNumber];
}
public void firstInitialDown(){
// Move the list index down one
firstInitialNumber--;
// If the index has reached the beginning of the List, loop the list back around to the end
if(firstInitialNumber == -1){
firstInitialNumber = initialLetters.Count - 1;
}
// Display the new initial in the text
firstInitialText.GetComponent<unityengine.ui.text>().text = initialLetters[firstInitialNumber];
}
And that's all for the basic controls. When the Player enters the new high score is where the real fun begins. My code may be a bit spaghetti, but I'll do my best to explain how I replace and rearrange the existing high scores.
First, I have to iterate/read the existing high scores and compare my new high score with each one. If the score is higher, I have to save the position in the high score list:
// Read the List and compare each index number with the new high score
for(int o=0;o<manager.getcomponent<gamemanager>().HighScoresList.Count;o++){
// If the newScore is higher or equal to the existing index value, mark its index in the List for reference
if(manager.GetComponent<gamemanager>().newScore >= manager.GetComponent<gamemanager>().HighScoresList[o]){
// Save the index postion as as replacementScorePosition
replacementScorePosition = o;
manager.GetComponent<gamemanager>().replacementScorePosition = o;
// Once I've found the score to replace, I can stop reading the List, break
break;
}
}
Next, Save the existing high scores and corresponding initials from that index/point forward in the List to temporary Lists for reference, so I can move them:
// iterate/read the existing HighScoresList List starting at the replacementScorePosition I saved earlier
for(int r=replacementScorePosition;r<manager.getcomponent<gamemanager>().HighScoresList.Count;r++){
// Save high scores from that index onward to tempHighScores List
tempHighScores[r] = manager.GetComponent<gamemanager>().HighScoresList[r];
// Save high score intials from that index onward to tempHighScoresInitials List
tempHighScoreInitials[r] = manager.GetComponent<gamemanager>().HighScoresInitialsList[r];
}
Next, rearrange/replace the high scores and corresponding initials:
// Iterate/read the HighScoresList List, starting from the replacementScorePosition saved from earlier
for(int c=replacementScorePosition;c<manager.getcomponent<gamemanager>().HighScoresList.Count;c++){
// The temporary List has the old scores/initials at one index higher, since the new high score/initials
// will replace where the old high scores/initials were.
// Therefore, the count will be off by one after the new high score/initials have been written into the Lists
// EX: Let's say new high score/initials are higher than the score at index 8. 8 will move to 9, 9 to 10, etc.
// So the new high score and initials will write over the item at index 8. However, the old high scores/initials
// were saved at their old indexes. so the count (int c up top) will no longer match in the for loop.
// To account for that and read the proper item at the proper index, we will need to set the index count
// back one to C-1. That way the proper old item/score at index 8 will be read and re-indexed at its new index.
int tempElement = (c-1);
// If this is the score/initials being replaced, replace the items in the Lists with the new high score
// and new initials that the Player just entered
// If this isn't the new replacement index, skip this and move the items down one instead
if(c == replacementScorePosition){
manager.GetComponent<gamemanager>().HighScoresList[c] = manager.GetComponent<gamemanager>().newScore;
manager.GetComponent<gamemanager>().HighScoresInitialsList[c] = "" + initialLetters[firstInitialNumber] + initialLetters[secondInitialNumber] + initialLetters[thirdInitialNumber];
// Since this item is updated, move on to the next items in the List to move them down one index
continue;
}
// Write over the existing item in the index with the old high score that was saved to the temporary List
// EX: if C = 8, writeover index 8 with the old score that was originally at index 7, to move it down one
manager.GetComponent<gamemanager>().HighScoresList[c] = tempHighScores[tempElement];
// Write over the existing item in the index with the matching old initials that were saved to the temporary List
manager.GetComponent<gamemanager>().HighScoresInitialsList[c] = tempHighScoreInitials[tempElement];
}
Finally, once the Lists/scores/initials are updated, they are saved to PlayerPrefs and the high score screen scene is loaded. Below is a code snippet that shows how this is done for the top high score and initials. The same is done for all 10 spots in the List:
PlayerPrefs.SetString("player01", manager.GetComponent<gamemanager>().HighScoresInitialsList[0]);
PlayerPrefs.SetInt("player01Score", manager.GetComponent<gamemanager>().HighScoresList[0]);
PlayerPrefs.Save();
SceneManager.LoadScene("highScore");
And that's it! I know that's a lot. I hope you found this useful, or at least some of the code useful. Thanks for reading, and keep an eye out for Elevader, which will be releasing soon!
Get Elevader
Elevader
A crazed A.I. has trapped you on an elevator. Jump, dodge, and dive to stay alive! Can you survive?
More posts
- Creating a Load/Save Game system in UnityJun 22, 2022
- Elevader v2.0 publishedJun 09, 2022
- Elevader is published!!!Jun 06, 2022
- Adding an Old School Instruction Manual to ElevaderMar 23, 2022
- Making CutscenesDec 01, 2021
- Elevader: Jump, dodge, and dive to survive the crazed A.I.Nov 28, 2021
Leave a comment
Log in with itch.io to leave a comment.