How do you save scores and update a scoreboard with them?
Hi,
I'm pretty new to coding in general and am trying to have a table that takes the most recent score received in a game and adds it. I thought that a good way to do it would be to add the score at the Game Over scene because that displays the current score, but it hasn't been destroyed yet. Then, the score in that table would be preserved into the start scene.
The end goal is to have a list of preserved scores, so those can be referenced by other programs.
My two issues are that 1) The table is disappearing in the Game Over scene (Scene 11, yes I know it's bad practice to reference a number, but I just wanted to get it working first before I clean up. If you have a better solution for how to accomplish what I'm going for, please let me know.). And 2) I can't add the currentScore (at the time the game ends, that would be the final score, but not sure of a better way to do it then calling currentScore at the end of the game) to the table.
Below are the two main scripts that I have (one of which is mainly edited from a tutorial). One controls some game features and has the currentScore variable. The other one controls the table and references the other script's score variable (unsuccessfully).
See line 77 of GameScoreTable, which is commented out right now to avoid errors, but that is attempting to add the currentScore to the table.
Thanks!
GameSession:
using System.Collections;
using System.Collections.Generic;
using System;
using UnityEngine;
using UnityEngine.UI; // need for text component
using TMPro;
using UnityEngine.SceneManagement;
public class GameSession : MonoBehaviour
{
// config params
[Range(0.1f, 10f)] [SerializeField] float gameSpeed = 1f;
[SerializeField] int pointsPerBlockDestroyed = 83;
[SerializeField] TextMeshProUGUI scoreText;
[SerializeField] TextMeshProUGUI savedScoreText;
[SerializeField] bool isAutoPlayEnabled;
// state variables
[SerializeField] public int currentScore = 0; // made public to be accessed in other scripts
[SerializeField] int savedScore; // should I set this to 0 or not? No. it's good now
Text text; // using for the highscore/savedScore state variable
private void Awake()
{
int gameStatusCount = FindObjectsOfType<GameSession>().Length;
if (gameStatusCount > 1)
{
gameObject.SetActive(false);
Destroy(gameObject);
}
else
{
DontDestroyOnLoad(gameObject);
}
}
private void Start()
{
scoreText.text = currentScore.ToString();
// This loads data from GlobalControl but I'm commenting it out
// currentScore = GlobalControl.Instance.currentScore;
// below is for highscore scripting
text = GetComponent<Text>(); // do I need this? ______________________________
savedScore = PlayerPrefs.GetInt("saved score", savedScore);
// Commented out the following because the below made it redundant: text.text = savedScore.ToString();
// This gets the highscore to display
savedScoreText.text = savedScore.ToString(); // This may be redundant given the above text. Now just need to apply that to a text object in the game
}
// Update is called once per frame
void Update()
{
Time.timeScale = gameSpeed;
// Below is for saving state like score and this is used for highscore but I turned highscore to Saved Score
if(currentScore > savedScore)
{
savedScore = currentScore;
text.text = "" + currentScore;
PlayerPrefs.SetInt("saved score", savedScore);
}
/* Commenting out the below since it's happening in another script: GameScoreTable
if (SceneManager.GetActiveScene().ToString() == 11.ToString()) // convert both to string to make a bool, stupid solution, but should work since GameOver is 11
//This is trying to have it so if the scene is gameover, currentscore gets added to the list in gamescoretable
{
gameObject.GetComponent<GameScoreTable.GameScoreEntry>().score = currentScore; // want to populate "score" within GameScoreEntry as the currentScore
}
*/
}
public void AddToScore()
{
currentScore += pointsPerBlockDestroyed;
scoreText.text = currentScore.ToString();
}
public void ResetGame()
{
Destroy(gameObject);
}
public bool IsAutoPlayEnabled()
{
return isAutoPlayEnabled;
}
// This is from highscore script, took out private so now maybe it will work
public void OnDestroy()
{
PlayerPrefs.SetInt("saved score", savedScore);
PlayerPrefs.Save();
}
// Adding below method to call in GameScoreTable
public int CallSavedScore()
{
return savedScore;
}
}
GameScoreTable:
/*
------------------- Code Monkey -------------------
Thank you for downloading this package
I hope you find it useful in your projects
If you have any questions let me know
Cheers!
unitycodemonkey.com
--------------------------------------------------
https://unitycodemonkey.com/video.php?v=iAbaqGYdnyI
*/
using System.Collections;
using System.Collections.Generic;
using System;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using UnityEngine.SceneManagement;
// commented out the following because don't have CodeMonkey namespace: using CodeMonkey.Utils;
public class GameScoreTable : MonoBehaviour {
// I want instad of the name, I sort by game number and instead of sorting by score highest to lowest, I sort by name which is the game number. Figure out.
private Transform entryContainer;
private Transform entryTemplate;
// There used to be this so may want to put in my version if necessary: private List<HighscoreEntry> highscoreEntryList;
public List<GameScoreEntry> gameScoreEntryList; // edited from the above, made public to match below
public List<Transform> gameScoreEntryTransformList; // changed from highscoreEntryTransformList, also made public
// Adding this to link to my main state script
// GameSession.savedScore is the highscore? below commented out so no errors and also did the same in Awake beginning
GameSession gameSession;
int savedScore;
private void Awake()
{
gameSession = FindObjectOfType<GameSession>(); // for looking up scores
savedScore = gameSession.CallSavedScore(); // for returning savedScore from GameSession
entryContainer = transform.Find("gameScoreEntryContainer"); // used to be highscoreEntryContainer so may need to change associations elsewhere
entryTemplate = entryContainer.Find("gameScoreEntryTemplate"); // used to be highscoreEntryTemplate so may need to change associations elsewhere, this is null ref
// It may be acceptable because it's going to false after running the default so I'll leave it for now and come back later
entryTemplate.gameObject.SetActive(false); // hides the default template
// clear default scores before json tracks
clearScoreTable();
string jsonString = PlayerPrefs.GetString("gameScoreTable");
GameScores gameScores = JsonUtility.FromJson<GameScores>(jsonString); // JsonUtility stores a list of scores and converts an object to json
if (gameScores == null) // This was after line 34 but just deleted at 14 mins; It may be not working because need the names to initialize
{
// There's no stored table, initialize
Debug.Log("Initializing table with default values...");
AddGameScoreEntry(1000000, "CMK");
AddGameScoreEntry(897621, "JOE");
AddGameScoreEntry(872931, "DAV");
AddGameScoreEntry(785123, "CAT");
AddGameScoreEntry(542024, "MAX");
AddGameScoreEntry(68245, "AAA");
// Reload
jsonString = PlayerPrefs.GetString("gameScoreTable");
gameScores = JsonUtility.FromJson<GameScores>(jsonString);
}
if (SceneManager.GetActiveScene().ToString() == 11.ToString()) // below had errors so commented out, but I want to put stuff here. try with each of the two below
{
DontDestroyOnLoad(gameObject); // adding so it isn't destroyed on load because it's disappearing in game over
AddGameScoreEntry(gameObject.GetComponent<GameSession>().currentScore, "ACE"); // used to be the following bu took out 1 because no name assigned: AddGameScoreEntry(gameObject.GetComponent<GameSession>().currentScore, 1)
gameObject.GetComponent<GameScoreEntry>().score = gameObject.GetComponent<GameSession>().currentScore; // want to populate "score" within GameScoreEntry as the currentScore, drawing from gamesession
gameObject.GetComponent<GameScoreEntry>().score = GameObject.FindObjectOfType<GameSession>().currentScore; // same as above
}
// Sort entry list by Score
for (int i = 0; i < gameScores.gameScoreEntryList.Count; i++)
{ // This goes through the entire list
for (int j = i + 1; j < gameScores.gameScoreEntryList.Count; j++) // For all elements, cycle through the ones after j
{
if (gameScores.gameScoreEntryList[j].score > gameScores.gameScoreEntryList[i].score) // Test to see which element has the highest score
{
// Swap positions if needed
GameScoreEntry tmp = gameScores.gameScoreEntryList[i];
gameScores.gameScoreEntryList[i] = gameScores.gameScoreEntryList[j];
gameScores.gameScoreEntryList[j] = tmp;
}
}
}
gameScoreEntryTransformList = new List<Transform>();
foreach (GameScoreEntry gameScoreEntry in gameScores.gameScoreEntryList)
{
CreateGameScoreEntryTransform(gameScoreEntry, entryContainer, gameScoreEntryTransformList);
}
}
// changed below from create highscoreentryTransform and highscoreEntry; may need to change others to gameScoreEntry; instantiated transform goes from this list
private void CreateGameScoreEntryTransform(GameScoreEntry gameScoreEntry, Transform container, List<Transform> transformList)
{
float templateHeight = 31f;
Transform entryTransform = Instantiate(entryTemplate, container); // instantiate entry transform based on template
RectTransform entryRectTransform = entryTransform.GetComponent<RectTransform>();
entryRectTransform.anchoredPosition = new Vector2(0, -templateHeight * transformList.Count); // position based on the template height
entryTransform.gameObject.SetActive(true); // now true to display the list
// This is the switch for ranking but could probably use this to display the most recent games
int rank = transformList.Count + 1;
string rankString;
switch (rank) {
default:
rankString = rank + "TH"; break; // This is 4th-10th, not necessary for other rankings
case 1: rankString = "1ST"; break;
case 2: rankString = "2ND"; break;
case 3: rankString = "3RD"; break;
}
// This updates text field and it used to be: entryTransform.Find("posText").GetComponent<Text>().text = rankString;
entryTransform.Find("gameNumText").GetComponent<Text>().text = rankString; // Still want to rank because want it to sort by most recent first
int score = gameScoreEntry.score;
entryTransform.Find("scoreText").GetComponent<Text>().text = score.ToString();
// maybe don't need if I don't have a name so commenting out; or maybe this should be catalogued like gameScoreEntry.game
string name = gameScoreEntry.name;
entryTransform.Find("nameText").GetComponent<Text>().text = name;
// Set background visible odds and evens, easier to read
entryTransform.Find("background").gameObject.SetActive(rank % 2 == 1);
// Highlight most recent game score
if (rank == 1) {
entryTransform.Find("gameNumText").GetComponent<Text>().color = Color.green; // changed association
entryTransform.Find("scoreText").GetComponent<Text>().color = Color.green;
entryTransform.Find("nameText").GetComponent<Text>().color = Color.green;
}
// Set tropy
/* The below uses the UtilsClass which was in a Namespace I don't have, so I took it out, but could use to rank in the future
* Default hides trophy image and and the others make it a different color depending on rank
switch (rank)
{
default:
entryTransform.Find("trophy").gameObject.SetActive(false);
break;
case 1:
entryTransform.Find("trophy").GetComponent<Image>().color = UtilsClass.GetColorFromString("FFD200");
break;
case 2:
entryTransform.Find("trophy").GetComponent<Image>().color = UtilsClass.GetColorFromString("C6C6C6");
break;
case 3:
entryTransform.Find("trophy").GetComponent<Image>().color = UtilsClass.GetColorFromString("B76F56");
break;
}
*/
transformList.Add(entryTransform);
}
private void AddGameScoreEntry(int score, string name) // used to be AddHighScoreEntry
{
// Create GameScoreEntry
GameScoreEntry gameScoreEntry = new GameScoreEntry { score = score, name = name }; // may need to change based on associations
// Load saved Highscores, adds to saved list by using playerprefs
string jsonString = PlayerPrefs.GetString("gameScoreTable"); // change highscoreTable later
GameScores gameScores = JsonUtility.FromJson<GameScores>(jsonString);
if (gameScores == null) // gameScores was highscores, so may needed to change elsewhere
{
// There's no stored table, initialize
// I think I could add scores here
gameScores = new GameScores()
{
gameScoreEntryList = new List<GameScoreEntry>()
};
}
// Add new entry to Highscores
gameScores.gameScoreEntryList.Add(gameScoreEntry);
// Save updated Highscores
string json = JsonUtility.ToJson(gameScores); // video called highscores as highscoreEntryList, but needs to be an object that contains a list instead of the list itself so changed to gameScores
PlayerPrefs.SetString("gameScoreTable", json); // stores all data
PlayerPrefs.Save();
}
// clears default table scores so that it can be tracked in the game, then this is called in Awake
private void clearScoreTable()
{
// Load saved scores
string jsonString = PlayerPrefs.GetString("gameScoreTable");
GameScores gameScores = JsonUtility.FromJson<GameScores>(jsonString);
//Clear scores table
gameScores.gameScoreEntryList.Clear();
//Save updated scores
string json = JsonUtility.ToJson(gameScores);
PlayerPrefs.SetString("scoreTable", json);
PlayerPrefs.Save();
}
[System.Serializable]
public class GameScores // Used to be Highscores and private here, but made public because needs to be in unity 2019 and added serializable above to affect this
{
public List<GameScoreEntry> gameScoreEntryList;
}
/*
* Represents a single Game score entry; used to be a high score entry
* */
[System.Serializable] // makes the bellow serializable
// This is where I may need to add the reference to other highscores
public class GameScoreEntry // Used to be private here, but made public because needs to be in unity 2019
{
/*This wasn't working so commented out
GameSession gameSession;
int savedScore;
gameSession = GameObject.FindObjectOfType<GameSession>().currentScore; // for looking up scores
savedScore = gameSession.CallSavedScore(); // for returning savedScore from GameSession
GameSession gameSession = FindObjectOfType<GameSession>();
*/
public int score = GameObject.FindObjectOfType<GameSession>().currentScore; // I want to reference GameSession savedScore. Here was: public int score but want to access the awake part;a
public string name; // this might be inidividual games?
}
}
Your answer
Follow this Question
Related Questions
show score and keep score on screen until start new game? 0 Answers
Get user score in Game Center Scoreboard 0 Answers
Highscore system unity C# 0 Answers
How to add and track scoring in online multiplayer with UI? (Unet) 0 Answers
reload game 1 Answer