Recorded Video Session: Quiz Game 2
Tutorial
·
intermediate
·
+0XP
·
65 mins
·
(18)
Unity Technologies
In this live training session we will learn how to extend our basic Quiz Game created in session one to include: loading game data from an external json file, editor scripting to create a window for editing game data and very basic saving of player progress. Download the starting version of the project here
Languages available:
1. Intro To Part Two
In this live training session we will learn how to extend our basic Quiz Game created in session one to include: loading game data from an external json file, editor scripting to create a window for editing game data and very basic saving of player progress.
Quiz Game 2 - Intro to Part Two [1/7] Live 2016/11/25
PlayerProgress
public class PlayerProgress
{
public int highestScore = 0;
}
DataController
using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections;
using System.IO; // The System.IO namespace contains functions related to loading and saving files
public class DataController : MonoBehaviour
{
private RoundData[] allRoundData;
private PlayerProgress playerProgress;
private string gameDataFileName = "data.json";
void Start()
{
DontDestroyOnLoad(gameObject);
LoadGameData();
LoadPlayerProgress();
SceneManager.LoadScene("MenuScreen");
}
public RoundData GetCurrentRoundData()
{
// If we wanted to return different rounds, we could do that here
// We could store an int representing the current round index in PlayerProgress
return allRoundData[0];
}
public void SubmitNewPlayerScore(int newScore)
{
// If newScore is greater than playerProgress.highestScore, update playerProgress with the new value and call SavePlayerProgress()
if(newScore > playerProgress.highestScore)
{
playerProgress.highestScore = newScore;
SavePlayerProgress();
}
}
public int GetHighestPlayerScore()
{
return playerProgress.highestScore;
}
private void LoadGameData()
{
// Path.Combine combines strings into a file path
// Application.StreamingAssets points to Assets/StreamingAssets in the Editor, and the StreamingAssets folder in a build
string filePath = Path.Combine(Application.streamingAssetsPath, gameDataFileName);
if(File.Exists(filePath))
{
// Read the json from the file into a string
string dataAsJson = File.ReadAllText(filePath);
// Pass the json to JsonUtility, and tell it to create a GameData object from it
GameData loadedData = JsonUtility.FromJson<GameData>(dataAsJson);
// Retrieve the allRoundData property of loadedData
allRoundData = loadedData.allRoundData;
}
else
{
Debug.LogError("Cannot load game data!");
}
}
// This function could be extended easily to handle any additional data we wanted to store in our PlayerProgress object
private void LoadPlayerProgress()
{
// Create a new PlayerProgress object
playerProgress = new PlayerProgress();
// If PlayerPrefs contains a key called "highestScore", set the value of playerProgress.highestScore using the value associated with that key
if(PlayerPrefs.HasKey("highestScore"))
{
playerProgress.highestScore = PlayerPrefs.GetInt("highestScore");
}
}
// This function could be extended easily to handle any additional data we wanted to store in our PlayerProgress object
private void SavePlayerProgress()
{
// Save the value playerProgress.highestScore to PlayerPrefs, with a key of "highestScore"
PlayerPrefs.SetInt("highestScore", playerProgress.highestScore);
}
}
GameController
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
using System.Collections.Generic;
public class GameController : MonoBehaviour
{
public SimpleObjectPool answerButtonObjectPool;
public Text questionText;
public Text scoreDisplay;
public Text timeRemainingDisplay;
public Transform answerButtonParent;
public GameObject questionDisplay;
public GameObject roundEndDisplay;
public Text highScoreDisplay;
private DataController dataController;
private RoundData currentRoundData;
private QuestionData[] questionPool;
private bool isRoundActive = false;
private float timeRemaining;
private int playerScore;
private int questionIndex;
private List<GameObject> answerButtonGameObjects = new List<GameObject>();
void Start()
{
dataController = FindObjectOfType<DataController>(); // Store a reference to the DataController so we can request the data we need for this round
currentRoundData = dataController.GetCurrentRoundData(); // Ask the DataController for the data for the current round. At the moment, we only have one round - but we could extend this
questionPool = currentRoundData.questions; // Take a copy of the questions so we could shuffle the pool or drop questions from it without affecting the original RoundData object
timeRemaining = currentRoundData.timeLimitInSeconds; // Set the time limit for this round based on the RoundData object
UpdateTimeRemainingDisplay();
playerScore = 0;
questionIndex = 0;
ShowQuestion();
isRoundActive = true;
}
void Update()
{
if (isRoundActive)
{
timeRemaining -= Time.deltaTime; // If the round is active, subtract the time since Update() was last called from timeRemaining
UpdateTimeRemainingDisplay();
if (timeRemaining <= 0f) // If timeRemaining is 0 or less, the round ends
{
EndRound();
}
}
}
void ShowQuestion()
{
RemoveAnswerButtons();
QuestionData questionData = questionPool[questionIndex]; // Get the QuestionData for the current question
questionText.text = questionData.questionText; // Update questionText with the correct text
for (int i = 0; i < questionData.answers.Length; i ++) // For every AnswerData in the current QuestionData...
{
GameObject answerButtonGameObject = answerButtonObjectPool.GetObject(); // Spawn an AnswerButton from the object pool
answerButtonGameObjects.Add(answerButtonGameObject);
answerButtonGameObject.transform.SetParent(answerButtonParent);
answerButtonGameObject.transform.localScale = Vector3.one;
AnswerButton answerButton = answerButtonGameObject.GetComponent<AnswerButton>();
answerButton.SetUp(questionData.answers[i]); // Pass the AnswerData to the AnswerButton so the AnswerButton knows what text to display and whether it is the correct answer
}
}
void RemoveAnswerButtons()
{
while (answerButtonGameObjects.Count > 0) // Return all spawned AnswerButtons to the object pool
{
answerButtonObjectPool.ReturnObject(answerButtonGameObjects[0]);
answerButtonGameObjects.RemoveAt(0);
}
}
public void AnswerButtonClicked(bool isCorrect)
{
if (isCorrect)
{
playerScore += currentRoundData.pointsAddedForCorrectAnswer; // If the AnswerButton that was clicked was the correct answer, add points
scoreDisplay.text = playerScore.ToString();
}
if(questionPool.Length > questionIndex + 1) // If there are more questions, show the next question
{
questionIndex++;
ShowQuestion();
}
else // If there are no more questions, the round ends
{
EndRound();
}
}
private void UpdateTimeRemainingDisplay()
{
timeRemainingDisplay.text = Mathf.Round(timeRemaining).ToString();
}
public void EndRound()
{
isRoundActive = false;
dataController.SubmitNewPlayerScore(playerScore);
highScoreDisplay.text = dataController.GetHighestPlayerScore().ToString();
questionDisplay.SetActive(false);
roundEndDisplay.SetActive(true);
}
public void ReturnToMenu()
{
SceneManager.LoadScene("MenuScreen");
}
}
GameDataEditor
using UnityEngine;
using UnityEditor;
using System.Collections;
using System.IO;
public class GameDataEditor : EditorWindow
{
public GameData gameData;
private string gameDataProjectFilePath = "/StreamingAssets/data.json";
[MenuItem ("Window/Game Data Editor")]
static void Init()
{
EditorWindow.GetWindow (typeof(GameDataEditor)).Show ();
}
void OnGUI()
{
if (gameData != null)
{
SerializedObject serializedObject = new SerializedObject (this);
SerializedProperty serializedProperty = serializedObject.FindProperty ("gameData");
EditorGUILayout.PropertyField (serializedProperty, true);
serializedObject.ApplyModifiedProperties ();
if (GUILayout.Button ("Save data"))
{
SaveGameData();
}
}
if (GUILayout.Button ("Load data"))
{
LoadGameData();
}
}
private void LoadGameData()
{
string filePath = Application.dataPath + gameDataProjectFilePath;
if (File.Exists (filePath)) {
string dataAsJson = File.ReadAllText (filePath);
gameData = JsonUtility.FromJson<GameData> (dataAsJson);
} else
{
gameData = new GameData();
}
}
private void SaveGameData()
{
string dataAsJson = JsonUtility.ToJson (gameData);
string filePath = Application.dataPath + gameDataProjectFilePath;
File.WriteAllText (filePath, dataAsJson);
}
}
2. High Score with PlayerPrefs
In this live training session we will learn how to extend our basic Quiz Game created in session one to include: loading game data from an external json file, editor scripting to create a window for editing game data and very basic saving of player progress.
Quiz Game 2 - High Score with PlayerPrefs [2/7] Live 2016/11/25
PlayerProgress
public class PlayerProgress
{
public int highestScore = 0;
}
DataController
using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections;
using System.IO; // The System.IO namespace contains functions related to loading and saving files
public class DataController : MonoBehaviour
{
private RoundData[] allRoundData;
private PlayerProgress playerProgress;
private string gameDataFileName = "data.json";
void Start()
{
DontDestroyOnLoad(gameObject);
LoadGameData();
LoadPlayerProgress();
SceneManager.LoadScene("MenuScreen");
}
public RoundData GetCurrentRoundData()
{
// If we wanted to return different rounds, we could do that here
// We could store an int representing the current round index in PlayerProgress
return allRoundData[0];
}
public void SubmitNewPlayerScore(int newScore)
{
// If newScore is greater than playerProgress.highestScore, update playerProgress with the new value and call SavePlayerProgress()
if(newScore > playerProgress.highestScore)
{
playerProgress.highestScore = newScore;
SavePlayerProgress();
}
}
public int GetHighestPlayerScore()
{
return playerProgress.highestScore;
}
private void LoadGameData()
{
// Path.Combine combines strings into a file path
// Application.StreamingAssets points to Assets/StreamingAssets in the Editor, and the StreamingAssets folder in a build
string filePath = Path.Combine(Application.streamingAssetsPath, gameDataFileName);
if(File.Exists(filePath))
{
// Read the json from the file into a string
string dataAsJson = File.ReadAllText(filePath);
// Pass the json to JsonUtility, and tell it to create a GameData object from it
GameData loadedData = JsonUtility.FromJson<GameData>(dataAsJson);
// Retrieve the allRoundData property of loadedData
allRoundData = loadedData.allRoundData;
}
else
{
Debug.LogError("Cannot load game data!");
}
}
// This function could be extended easily to handle any additional data we wanted to store in our PlayerProgress object
private void LoadPlayerProgress()
{
// Create a new PlayerProgress object
playerProgress = new PlayerProgress();
// If PlayerPrefs contains a key called "highestScore", set the value of playerProgress.highestScore using the value associated with that key
if(PlayerPrefs.HasKey("highestScore"))
{
playerProgress.highestScore = PlayerPrefs.GetInt("highestScore");
}
}
// This function could be extended easily to handle any additional data we wanted to store in our PlayerProgress object
private void SavePlayerProgress()
{
// Save the value playerProgress.highestScore to PlayerPrefs, with a key of "highestScore"
PlayerPrefs.SetInt("highestScore", playerProgress.highestScore);
}
}
GameController
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
using System.Collections.Generic;
public class GameController : MonoBehaviour
{
public SimpleObjectPool answerButtonObjectPool;
public Text questionText;
public Text scoreDisplay;
public Text timeRemainingDisplay;
public Transform answerButtonParent;
public GameObject questionDisplay;
public GameObject roundEndDisplay;
public Text highScoreDisplay;
private DataController dataController;
private RoundData currentRoundData;
private QuestionData[] questionPool;
private bool isRoundActive = false;
private float timeRemaining;
private int playerScore;
private int questionIndex;
private List<GameObject> answerButtonGameObjects = new List<GameObject>();
void Start()
{
dataController = FindObjectOfType<DataController>(); // Store a reference to the DataController so we can request the data we need for this round
currentRoundData = dataController.GetCurrentRoundData(); // Ask the DataController for the data for the current round. At the moment, we only have one round - but we could extend this
questionPool = currentRoundData.questions; // Take a copy of the questions so we could shuffle the pool or drop questions from it without affecting the original RoundData object
timeRemaining = currentRoundData.timeLimitInSeconds; // Set the time limit for this round based on the RoundData object
UpdateTimeRemainingDisplay();
playerScore = 0;
questionIndex = 0;
ShowQuestion();
isRoundActive = true;
}
void Update()
{
if (isRoundActive)
{
timeRemaining -= Time.deltaTime; // If the round is active, subtract the time since Update() was last called from timeRemaining
UpdateTimeRemainingDisplay();
if (timeRemaining <= 0f) // If timeRemaining is 0 or less, the round ends
{
EndRound();
}
}
}
void ShowQuestion()
{
RemoveAnswerButtons();
QuestionData questionData = questionPool[questionIndex]; // Get the QuestionData for the current question
questionText.text = questionData.questionText; // Update questionText with the correct text
for (int i = 0; i < questionData.answers.Length; i ++) // For every AnswerData in the current QuestionData...
{
GameObject answerButtonGameObject = answerButtonObjectPool.GetObject(); // Spawn an AnswerButton from the object pool
answerButtonGameObjects.Add(answerButtonGameObject);
answerButtonGameObject.transform.SetParent(answerButtonParent);
answerButtonGameObject.transform.localScale = Vector3.one;
AnswerButton answerButton = answerButtonGameObject.GetComponent<AnswerButton>();
answerButton.SetUp(questionData.answers[i]); // Pass the AnswerData to the AnswerButton so the AnswerButton knows what text to display and whether it is the correct answer
}
}
void RemoveAnswerButtons()
{
while (answerButtonGameObjects.Count > 0) // Return all spawned AnswerButtons to the object pool
{
answerButtonObjectPool.ReturnObject(answerButtonGameObjects[0]);
answerButtonGameObjects.RemoveAt(0);
}
}
public void AnswerButtonClicked(bool isCorrect)
{
if (isCorrect)
{
playerScore += currentRoundData.pointsAddedForCorrectAnswer; // If the AnswerButton that was clicked was the correct answer, add points
scoreDisplay.text = playerScore.ToString();
}
if(questionPool.Length > questionIndex + 1) // If there are more questions, show the next question
{
questionIndex++;
ShowQuestion();
}
else // If there are no more questions, the round ends
{
EndRound();
}
}
private void UpdateTimeRemainingDisplay()
{
timeRemainingDisplay.text = Mathf.Round(timeRemaining).ToString();
}
public void EndRound()
{
isRoundActive = false;
dataController.SubmitNewPlayerScore(playerScore);
highScoreDisplay.text = dataController.GetHighestPlayerScore().ToString();
questionDisplay.SetActive(false);
roundEndDisplay.SetActive(true);
}
public void ReturnToMenu()
{
SceneManager.LoadScene("MenuScreen");
}
}
GameDataEditor
using UnityEngine;
using UnityEditor;
using System.Collections;
using System.IO;
public class GameDataEditor : EditorWindow
{
public GameData gameData;
private string gameDataProjectFilePath = "/StreamingAssets/data.json";
[MenuItem ("Window/Game Data Editor")]
static void Init()
{
EditorWindow.GetWindow (typeof(GameDataEditor)).Show ();
}
void OnGUI()
{
if (gameData != null)
{
SerializedObject serializedObject = new SerializedObject (this);
SerializedProperty serializedProperty = serializedObject.FindProperty ("gameData");
EditorGUILayout.PropertyField (serializedProperty, true);
serializedObject.ApplyModifiedProperties ();
if (GUILayout.Button ("Save data"))
{
SaveGameData();
}
}
if (GUILayout.Button ("Load data"))
{
LoadGameData();
}
}
private void LoadGameData()
{
string filePath = Application.dataPath + gameDataProjectFilePath;
if (File.Exists (filePath)) {
string dataAsJson = File.ReadAllText (filePath);
gameData = JsonUtility.FromJson<GameData> (dataAsJson);
} else
{
gameData = new GameData();
}
}
private void SaveGameData()
{
string dataAsJson = JsonUtility.ToJson (gameData);
string filePath = Application.dataPath + gameDataProjectFilePath;
File.WriteAllText (filePath, dataAsJson);
}
}
3. Serialization and Game Data
In this live training session we will learn how to extend our basic Quiz Game created in session one to include: loading game data from an external json file, editor scripting to create a window for editing game data and very basic saving of player progress.
Quiz Game 2 - Serialization and Game Data [3/7] Live 2016/11/25
PlayerProgress
public class PlayerProgress
{
public int highestScore = 0;
}
DataController
using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections;
using System.IO; // The System.IO namespace contains functions related to loading and saving files
public class DataController : MonoBehaviour
{
private RoundData[] allRoundData;
private PlayerProgress playerProgress;
private string gameDataFileName = "data.json";
void Start()
{
DontDestroyOnLoad(gameObject);
LoadGameData();
LoadPlayerProgress();
SceneManager.LoadScene("MenuScreen");
}
public RoundData GetCurrentRoundData()
{
// If we wanted to return different rounds, we could do that here
// We could store an int representing the current round index in PlayerProgress
return allRoundData[0];
}
public void SubmitNewPlayerScore(int newScore)
{
// If newScore is greater than playerProgress.highestScore, update playerProgress with the new value and call SavePlayerProgress()
if(newScore > playerProgress.highestScore)
{
playerProgress.highestScore = newScore;
SavePlayerProgress();
}
}
public int GetHighestPlayerScore()
{
return playerProgress.highestScore;
}
private void LoadGameData()
{
// Path.Combine combines strings into a file path
// Application.StreamingAssets points to Assets/StreamingAssets in the Editor, and the StreamingAssets folder in a build
string filePath = Path.Combine(Application.streamingAssetsPath, gameDataFileName);
if(File.Exists(filePath))
{
// Read the json from the file into a string
string dataAsJson = File.ReadAllText(filePath);
// Pass the json to JsonUtility, and tell it to create a GameData object from it
GameData loadedData = JsonUtility.FromJson<GameData>(dataAsJson);
// Retrieve the allRoundData property of loadedData
allRoundData = loadedData.allRoundData;
}
else
{
Debug.LogError("Cannot load game data!");
}
}
// This function could be extended easily to handle any additional data we wanted to store in our PlayerProgress object
private void LoadPlayerProgress()
{
// Create a new PlayerProgress object
playerProgress = new PlayerProgress();
// If PlayerPrefs contains a key called "highestScore", set the value of playerProgress.highestScore using the value associated with that key
if(PlayerPrefs.HasKey("highestScore"))
{
playerProgress.highestScore = PlayerPrefs.GetInt("highestScore");
}
}
// This function could be extended easily to handle any additional data we wanted to store in our PlayerProgress object
private void SavePlayerProgress()
{
// Save the value playerProgress.highestScore to PlayerPrefs, with a key of "highestScore"
PlayerPrefs.SetInt("highestScore", playerProgress.highestScore);
}
}
GameController
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
using System.Collections.Generic;
public class GameController : MonoBehaviour
{
public SimpleObjectPool answerButtonObjectPool;
public Text questionText;
public Text scoreDisplay;
public Text timeRemainingDisplay;
public Transform answerButtonParent;
public GameObject questionDisplay;
public GameObject roundEndDisplay;
public Text highScoreDisplay;
private DataController dataController;
private RoundData currentRoundData;
private QuestionData[] questionPool;
private bool isRoundActive = false;
private float timeRemaining;
private int playerScore;
private int questionIndex;
private List<GameObject> answerButtonGameObjects = new List<GameObject>();
void Start()
{
dataController = FindObjectOfType<DataController>(); // Store a reference to the DataController so we can request the data we need for this round
currentRoundData = dataController.GetCurrentRoundData(); // Ask the DataController for the data for the current round. At the moment, we only have one round - but we could extend this
questionPool = currentRoundData.questions; // Take a copy of the questions so we could shuffle the pool or drop questions from it without affecting the original RoundData object
timeRemaining = currentRoundData.timeLimitInSeconds; // Set the time limit for this round based on the RoundData object
UpdateTimeRemainingDisplay();
playerScore = 0;
questionIndex = 0;
ShowQuestion();
isRoundActive = true;
}
void Update()
{
if (isRoundActive)
{
timeRemaining -= Time.deltaTime; // If the round is active, subtract the time since Update() was last called from timeRemaining
UpdateTimeRemainingDisplay();
if (timeRemaining <= 0f) // If timeRemaining is 0 or less, the round ends
{
EndRound();
}
}
}
void ShowQuestion()
{
RemoveAnswerButtons();
QuestionData questionData = questionPool[questionIndex]; // Get the QuestionData for the current question
questionText.text = questionData.questionText; // Update questionText with the correct text
for (int i = 0; i < questionData.answers.Length; i ++) // For every AnswerData in the current QuestionData...
{
GameObject answerButtonGameObject = answerButtonObjectPool.GetObject(); // Spawn an AnswerButton from the object pool
answerButtonGameObjects.Add(answerButtonGameObject);
answerButtonGameObject.transform.SetParent(answerButtonParent);
answerButtonGameObject.transform.localScale = Vector3.one;
AnswerButton answerButton = answerButtonGameObject.GetComponent<AnswerButton>();
answerButton.SetUp(questionData.answers[i]); // Pass the AnswerData to the AnswerButton so the AnswerButton knows what text to display and whether it is the correct answer
}
}
void RemoveAnswerButtons()
{
while (answerButtonGameObjects.Count > 0) // Return all spawned AnswerButtons to the object pool
{
answerButtonObjectPool.ReturnObject(answerButtonGameObjects[0]);
answerButtonGameObjects.RemoveAt(0);
}
}
public void AnswerButtonClicked(bool isCorrect)
{
if (isCorrect)
{
playerScore += currentRoundData.pointsAddedForCorrectAnswer; // If the AnswerButton that was clicked was the correct answer, add points
scoreDisplay.text = playerScore.ToString();
}
if(questionPool.Length > questionIndex + 1) // If there are more questions, show the next question
{
questionIndex++;
ShowQuestion();
}
else // If there are no more questions, the round ends
{
EndRound();
}
}
private void UpdateTimeRemainingDisplay()
{
timeRemainingDisplay.text = Mathf.Round(timeRemaining).ToString();
}
public void EndRound()
{
isRoundActive = false;
dataController.SubmitNewPlayerScore(playerScore);
highScoreDisplay.text = dataController.GetHighestPlayerScore().ToString();
questionDisplay.SetActive(false);
roundEndDisplay.SetActive(true);
}
public void ReturnToMenu()
{
SceneManager.LoadScene("MenuScreen");
}
}
GameDataEditor
using UnityEngine;
using UnityEditor;
using System.Collections;
using System.IO;
public class GameDataEditor : EditorWindow
{
public GameData gameData;
private string gameDataProjectFilePath = "/StreamingAssets/data.json";
[MenuItem ("Window/Game Data Editor")]
static void Init()
{
EditorWindow.GetWindow (typeof(GameDataEditor)).Show ();
}
void OnGUI()
{
if (gameData != null)
{
SerializedObject serializedObject = new SerializedObject (this);
SerializedProperty serializedProperty = serializedObject.FindProperty ("gameData");
EditorGUILayout.PropertyField (serializedProperty, true);
serializedObject.ApplyModifiedProperties ();
if (GUILayout.Button ("Save data"))
{
SaveGameData();
}
}
if (GUILayout.Button ("Load data"))
{
LoadGameData();
}
}
private void LoadGameData()
{
string filePath = Application.dataPath + gameDataProjectFilePath;
if (File.Exists (filePath)) {
string dataAsJson = File.ReadAllText (filePath);
gameData = JsonUtility.FromJson<GameData> (dataAsJson);
} else
{
gameData = new GameData();
}
}
private void SaveGameData()
{
string dataAsJson = JsonUtility.ToJson (gameData);
string filePath = Application.dataPath + gameDataProjectFilePath;
File.WriteAllText (filePath, dataAsJson);
}
}
4. Loading Game Data via JSON
In this live training session we will learn how to extend our basic Quiz Game created in session one to include: loading game data from an external json file, editor scripting to create a window for editing game data and very basic saving of player progress.
Quiz Game 2 - Loading Game Data via JSON [4/7] Live 2016/11/25
PlayerProgress
public class PlayerProgress
{
public int highestScore = 0;
}
DataController
using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections;
using System.IO; // The System.IO namespace contains functions related to loading and saving files
public class DataController : MonoBehaviour
{
private RoundData[] allRoundData;
private PlayerProgress playerProgress;
private string gameDataFileName = "data.json";
void Start()
{
DontDestroyOnLoad(gameObject);
LoadGameData();
LoadPlayerProgress();
SceneManager.LoadScene("MenuScreen");
}
public RoundData GetCurrentRoundData()
{
// If we wanted to return different rounds, we could do that here
// We could store an int representing the current round index in PlayerProgress
return allRoundData[0];
}
public void SubmitNewPlayerScore(int newScore)
{
// If newScore is greater than playerProgress.highestScore, update playerProgress with the new value and call SavePlayerProgress()
if(newScore > playerProgress.highestScore)
{
playerProgress.highestScore = newScore;
SavePlayerProgress();
}
}
public int GetHighestPlayerScore()
{
return playerProgress.highestScore;
}
private void LoadGameData()
{
// Path.Combine combines strings into a file path
// Application.StreamingAssets points to Assets/StreamingAssets in the Editor, and the StreamingAssets folder in a build
string filePath = Path.Combine(Application.streamingAssetsPath, gameDataFileName);
if(File.Exists(filePath))
{
// Read the json from the file into a string
string dataAsJson = File.ReadAllText(filePath);
// Pass the json to JsonUtility, and tell it to create a GameData object from it
GameData loadedData = JsonUtility.FromJson<GameData>(dataAsJson);
// Retrieve the allRoundData property of loadedData
allRoundData = loadedData.allRoundData;
}
else
{
Debug.LogError("Cannot load game data!");
}
}
// This function could be extended easily to handle any additional data we wanted to store in our PlayerProgress object
private void LoadPlayerProgress()
{
// Create a new PlayerProgress object
playerProgress = new PlayerProgress();
// If PlayerPrefs contains a key called "highestScore", set the value of playerProgress.highestScore using the value associated with that key
if(PlayerPrefs.HasKey("highestScore"))
{
playerProgress.highestScore = PlayerPrefs.GetInt("highestScore");
}
}
// This function could be extended easily to handle any additional data we wanted to store in our PlayerProgress object
private void SavePlayerProgress()
{
// Save the value playerProgress.highestScore to PlayerPrefs, with a key of "highestScore"
PlayerPrefs.SetInt("highestScore", playerProgress.highestScore);
}
}
GameController
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
using System.Collections.Generic;
public class GameController : MonoBehaviour
{
public SimpleObjectPool answerButtonObjectPool;
public Text questionText;
public Text scoreDisplay;
public Text timeRemainingDisplay;
public Transform answerButtonParent;
public GameObject questionDisplay;
public GameObject roundEndDisplay;
public Text highScoreDisplay;
private DataController dataController;
private RoundData currentRoundData;
private QuestionData[] questionPool;
private bool isRoundActive = false;
private float timeRemaining;
private int playerScore;
private int questionIndex;
private List<GameObject> answerButtonGameObjects = new List<GameObject>();
void Start()
{
dataController = FindObjectOfType<DataController>(); // Store a reference to the DataController so we can request the data we need for this round
currentRoundData = dataController.GetCurrentRoundData(); // Ask the DataController for the data for the current round. At the moment, we only have one round - but we could extend this
questionPool = currentRoundData.questions; // Take a copy of the questions so we could shuffle the pool or drop questions from it without affecting the original RoundData object
timeRemaining = currentRoundData.timeLimitInSeconds; // Set the time limit for this round based on the RoundData object
UpdateTimeRemainingDisplay();
playerScore = 0;
questionIndex = 0;
ShowQuestion();
isRoundActive = true;
}
void Update()
{
if (isRoundActive)
{
timeRemaining -= Time.deltaTime; // If the round is active, subtract the time since Update() was last called from timeRemaining
UpdateTimeRemainingDisplay();
if (timeRemaining <= 0f) // If timeRemaining is 0 or less, the round ends
{
EndRound();
}
}
}
void ShowQuestion()
{
RemoveAnswerButtons();
QuestionData questionData = questionPool[questionIndex]; // Get the QuestionData for the current question
questionText.text = questionData.questionText; // Update questionText with the correct text
for (int i = 0; i < questionData.answers.Length; i ++) // For every AnswerData in the current QuestionData...
{
GameObject answerButtonGameObject = answerButtonObjectPool.GetObject(); // Spawn an AnswerButton from the object pool
answerButtonGameObjects.Add(answerButtonGameObject);
answerButtonGameObject.transform.SetParent(answerButtonParent);
answerButtonGameObject.transform.localScale = Vector3.one;
AnswerButton answerButton = answerButtonGameObject.GetComponent<AnswerButton>();
answerButton.SetUp(questionData.answers[i]); // Pass the AnswerData to the AnswerButton so the AnswerButton knows what text to display and whether it is the correct answer
}
}
void RemoveAnswerButtons()
{
while (answerButtonGameObjects.Count > 0) // Return all spawned AnswerButtons to the object pool
{
answerButtonObjectPool.ReturnObject(answerButtonGameObjects[0]);
answerButtonGameObjects.RemoveAt(0);
}
}
public void AnswerButtonClicked(bool isCorrect)
{
if (isCorrect)
{
playerScore += currentRoundData.pointsAddedForCorrectAnswer; // If the AnswerButton that was clicked was the correct answer, add points
scoreDisplay.text = playerScore.ToString();
}
if(questionPool.Length > questionIndex + 1) // If there are more questions, show the next question
{
questionIndex++;
ShowQuestion();
}
else // If there are no more questions, the round ends
{
EndRound();
}
}
private void UpdateTimeRemainingDisplay()
{
timeRemainingDisplay.text = Mathf.Round(timeRemaining).ToString();
}
public void EndRound()
{
isRoundActive = false;
dataController.SubmitNewPlayerScore(playerScore);
highScoreDisplay.text = dataController.GetHighestPlayerScore().ToString();
questionDisplay.SetActive(false);
roundEndDisplay.SetActive(true);
}
public void ReturnToMenu()
{
SceneManager.LoadScene("MenuScreen");
}
}
GameDataEditor
using UnityEngine;
using UnityEditor;
using System.Collections;
using System.IO;
public class GameDataEditor : EditorWindow
{
public GameData gameData;
private string gameDataProjectFilePath = "/StreamingAssets/data.json";
[MenuItem ("Window/Game Data Editor")]
static void Init()
{
EditorWindow.GetWindow (typeof(GameDataEditor)).Show ();
}
void OnGUI()
{
if (gameData != null)
{
SerializedObject serializedObject = new SerializedObject (this);
SerializedProperty serializedProperty = serializedObject.FindProperty ("gameData");
EditorGUILayout.PropertyField (serializedProperty, true);
serializedObject.ApplyModifiedProperties ();
if (GUILayout.Button ("Save data"))
{
SaveGameData();
}
}
if (GUILayout.Button ("Load data"))
{
LoadGameData();
}
}
private void LoadGameData()
{
string filePath = Application.dataPath + gameDataProjectFilePath;
if (File.Exists (filePath)) {
string dataAsJson = File.ReadAllText (filePath);
gameData = JsonUtility.FromJson<GameData> (dataAsJson);
} else
{
gameData = new GameData();
}
}
private void SaveGameData()
{
string dataAsJson = JsonUtility.ToJson (gameData);
string filePath = Application.dataPath + gameDataProjectFilePath;
File.WriteAllText (filePath, dataAsJson);
}
}
5. Loading and Saving via Editor Script
In this live training session we will learn how to extend our basic Quiz Game created in session one to include: loading game data from an external json file, editor scripting to create a window for editing game data and very basic saving of player progress.
Quiz Game 2 - Loading and Saving via Editor Script [5/7] Live 2016/11/25
PlayerProgress
public class PlayerProgress
{
public int highestScore = 0;
}
DataController
using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections;
using System.IO; // The System.IO namespace contains functions related to loading and saving files
public class DataController : MonoBehaviour
{
private RoundData[] allRoundData;
private PlayerProgress playerProgress;
private string gameDataFileName = "data.json";
void Start()
{
DontDestroyOnLoad(gameObject);
LoadGameData();
LoadPlayerProgress();
SceneManager.LoadScene("MenuScreen");
}
public RoundData GetCurrentRoundData()
{
// If we wanted to return different rounds, we could do that here
// We could store an int representing the current round index in PlayerProgress
return allRoundData[0];
}
public void SubmitNewPlayerScore(int newScore)
{
// If newScore is greater than playerProgress.highestScore, update playerProgress with the new value and call SavePlayerProgress()
if(newScore > playerProgress.highestScore)
{
playerProgress.highestScore = newScore;
SavePlayerProgress();
}
}
public int GetHighestPlayerScore()
{
return playerProgress.highestScore;
}
private void LoadGameData()
{
// Path.Combine combines strings into a file path
// Application.StreamingAssets points to Assets/StreamingAssets in the Editor, and the StreamingAssets folder in a build
string filePath = Path.Combine(Application.streamingAssetsPath, gameDataFileName);
if(File.Exists(filePath))
{
// Read the json from the file into a string
string dataAsJson = File.ReadAllText(filePath);
// Pass the json to JsonUtility, and tell it to create a GameData object from it
GameData loadedData = JsonUtility.FromJson<GameData>(dataAsJson);
// Retrieve the allRoundData property of loadedData
allRoundData = loadedData.allRoundData;
}
else
{
Debug.LogError("Cannot load game data!");
}
}
// This function could be extended easily to handle any additional data we wanted to store in our PlayerProgress object
private void LoadPlayerProgress()
{
// Create a new PlayerProgress object
playerProgress = new PlayerProgress();
// If PlayerPrefs contains a key called "highestScore", set the value of playerProgress.highestScore using the value associated with that key
if(PlayerPrefs.HasKey("highestScore"))
{
playerProgress.highestScore = PlayerPrefs.GetInt("highestScore");
}
}
// This function could be extended easily to handle any additional data we wanted to store in our PlayerProgress object
private void SavePlayerProgress()
{
// Save the value playerProgress.highestScore to PlayerPrefs, with a key of "highestScore"
PlayerPrefs.SetInt("highestScore", playerProgress.highestScore);
}
}
GameController
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
using System.Collections.Generic;
public class GameController : MonoBehaviour
{
public SimpleObjectPool answerButtonObjectPool;
public Text questionText;
public Text scoreDisplay;
public Text timeRemainingDisplay;
public Transform answerButtonParent;
public GameObject questionDisplay;
public GameObject roundEndDisplay;
public Text highScoreDisplay;
private DataController dataController;
private RoundData currentRoundData;
private QuestionData[] questionPool;
private bool isRoundActive = false;
private float timeRemaining;
private int playerScore;
private int questionIndex;
private List<GameObject> answerButtonGameObjects = new List<GameObject>();
void Start()
{
dataController = FindObjectOfType<DataController>(); // Store a reference to the DataController so we can request the data we need for this round
currentRoundData = dataController.GetCurrentRoundData(); // Ask the DataController for the data for the current round. At the moment, we only have one round - but we could extend this
questionPool = currentRoundData.questions; // Take a copy of the questions so we could shuffle the pool or drop questions from it without affecting the original RoundData object
timeRemaining = currentRoundData.timeLimitInSeconds; // Set the time limit for this round based on the RoundData object
UpdateTimeRemainingDisplay();
playerScore = 0;
questionIndex = 0;
ShowQuestion();
isRoundActive = true;
}
void Update()
{
if (isRoundActive)
{
timeRemaining -= Time.deltaTime; // If the round is active, subtract the time since Update() was last called from timeRemaining
UpdateTimeRemainingDisplay();
if (timeRemaining <= 0f) // If timeRemaining is 0 or less, the round ends
{
EndRound();
}
}
}
void ShowQuestion()
{
RemoveAnswerButtons();
QuestionData questionData = questionPool[questionIndex]; // Get the QuestionData for the current question
questionText.text = questionData.questionText; // Update questionText with the correct text
for (int i = 0; i < questionData.answers.Length; i ++) // For every AnswerData in the current QuestionData...
{
GameObject answerButtonGameObject = answerButtonObjectPool.GetObject(); // Spawn an AnswerButton from the object pool
answerButtonGameObjects.Add(answerButtonGameObject);
answerButtonGameObject.transform.SetParent(answerButtonParent);
answerButtonGameObject.transform.localScale = Vector3.one;
AnswerButton answerButton = answerButtonGameObject.GetComponent<AnswerButton>();
answerButton.SetUp(questionData.answers[i]); // Pass the AnswerData to the AnswerButton so the AnswerButton knows what text to display and whether it is the correct answer
}
}
void RemoveAnswerButtons()
{
while (answerButtonGameObjects.Count > 0) // Return all spawned AnswerButtons to the object pool
{
answerButtonObjectPool.ReturnObject(answerButtonGameObjects[0]);
answerButtonGameObjects.RemoveAt(0);
}
}
public void AnswerButtonClicked(bool isCorrect)
{
if (isCorrect)
{
playerScore += currentRoundData.pointsAddedForCorrectAnswer; // If the AnswerButton that was clicked was the correct answer, add points
scoreDisplay.text = playerScore.ToString();
}
if(questionPool.Length > questionIndex + 1) // If there are more questions, show the next question
{
questionIndex++;
ShowQuestion();
}
else // If there are no more questions, the round ends
{
EndRound();
}
}
private void UpdateTimeRemainingDisplay()
{
timeRemainingDisplay.text = Mathf.Round(timeRemaining).ToString();
}
public void EndRound()
{
isRoundActive = false;
dataController.SubmitNewPlayerScore(playerScore);
highScoreDisplay.text = dataController.GetHighestPlayerScore().ToString();
questionDisplay.SetActive(false);
roundEndDisplay.SetActive(true);
}
public void ReturnToMenu()
{
SceneManager.LoadScene("MenuScreen");
}
}
GameDataEditor
using UnityEngine;
using UnityEditor;
using System.Collections;
using System.IO;
public class GameDataEditor : EditorWindow
{
public GameData gameData;
private string gameDataProjectFilePath = "/StreamingAssets/data.json";
[MenuItem ("Window/Game Data Editor")]
static void Init()
{
EditorWindow.GetWindow (typeof(GameDataEditor)).Show ();
}
void OnGUI()
{
if (gameData != null)
{
SerializedObject serializedObject = new SerializedObject (this);
SerializedProperty serializedProperty = serializedObject.FindProperty ("gameData");
EditorGUILayout.PropertyField (serializedProperty, true);
serializedObject.ApplyModifiedProperties ();
if (GUILayout.Button ("Save data"))
{
SaveGameData();
}
}
if (GUILayout.Button ("Load data"))
{
LoadGameData();
}
}
private void LoadGameData()
{
string filePath = Application.dataPath + gameDataProjectFilePath;
if (File.Exists (filePath)) {
string dataAsJson = File.ReadAllText (filePath);
gameData = JsonUtility.FromJson<GameData> (dataAsJson);
} else
{
gameData = new GameData();
}
}
private void SaveGameData()
{
string dataAsJson = JsonUtility.ToJson (gameData);
string filePath = Application.dataPath + gameDataProjectFilePath;
File.WriteAllText (filePath, dataAsJson);
}
}
6. Game Data Editor GUI
In this live training session we will learn how to extend our basic Quiz Game created in session one to include: loading game data from an external json file, editor scripting to create a window for editing game data and very basic saving of player progress.
Quiz Game 2 - Game Data Editor GUI [6/7] Live 2016/11/25
PlayerProgress
public class PlayerProgress
{
public int highestScore = 0;
}
DataController
using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections;
using System.IO; // The System.IO namespace contains functions related to loading and saving files
public class DataController : MonoBehaviour
{
private RoundData[] allRoundData;
private PlayerProgress playerProgress;
private string gameDataFileName = "data.json";
void Start()
{
DontDestroyOnLoad(gameObject);
LoadGameData();
LoadPlayerProgress();
SceneManager.LoadScene("MenuScreen");
}
public RoundData GetCurrentRoundData()
{
// If we wanted to return different rounds, we could do that here
// We could store an int representing the current round index in PlayerProgress
return allRoundData[0];
}
public void SubmitNewPlayerScore(int newScore)
{
// If newScore is greater than playerProgress.highestScore, update playerProgress with the new value and call SavePlayerProgress()
if(newScore > playerProgress.highestScore)
{
playerProgress.highestScore = newScore;
SavePlayerProgress();
}
}
public int GetHighestPlayerScore()
{
return playerProgress.highestScore;
}
private void LoadGameData()
{
// Path.Combine combines strings into a file path
// Application.StreamingAssets points to Assets/StreamingAssets in the Editor, and the StreamingAssets folder in a build
string filePath = Path.Combine(Application.streamingAssetsPath, gameDataFileName);
if(File.Exists(filePath))
{
// Read the json from the file into a string
string dataAsJson = File.ReadAllText(filePath);
// Pass the json to JsonUtility, and tell it to create a GameData object from it
GameData loadedData = JsonUtility.FromJson<GameData>(dataAsJson);
// Retrieve the allRoundData property of loadedData
allRoundData = loadedData.allRoundData;
}
else
{
Debug.LogError("Cannot load game data!");
}
}
// This function could be extended easily to handle any additional data we wanted to store in our PlayerProgress object
private void LoadPlayerProgress()
{
// Create a new PlayerProgress object
playerProgress = new PlayerProgress();
// If PlayerPrefs contains a key called "highestScore", set the value of playerProgress.highestScore using the value associated with that key
if(PlayerPrefs.HasKey("highestScore"))
{
playerProgress.highestScore = PlayerPrefs.GetInt("highestScore");
}
}
// This function could be extended easily to handle any additional data we wanted to store in our PlayerProgress object
private void SavePlayerProgress()
{
// Save the value playerProgress.highestScore to PlayerPrefs, with a key of "highestScore"
PlayerPrefs.SetInt("highestScore", playerProgress.highestScore);
}
}
GameController
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
using System.Collections.Generic;
public class GameController : MonoBehaviour
{
public SimpleObjectPool answerButtonObjectPool;
public Text questionText;
public Text scoreDisplay;
public Text timeRemainingDisplay;
public Transform answerButtonParent;
public GameObject questionDisplay;
public GameObject roundEndDisplay;
public Text highScoreDisplay;
private DataController dataController;
private RoundData currentRoundData;
private QuestionData[] questionPool;
private bool isRoundActive = false;
private float timeRemaining;
private int playerScore;
private int questionIndex;
private List<GameObject> answerButtonGameObjects = new List<GameObject>();
void Start()
{
dataController = FindObjectOfType<DataController>(); // Store a reference to the DataController so we can request the data we need for this round
currentRoundData = dataController.GetCurrentRoundData(); // Ask the DataController for the data for the current round. At the moment, we only have one round - but we could extend this
questionPool = currentRoundData.questions; // Take a copy of the questions so we could shuffle the pool or drop questions from it without affecting the original RoundData object
timeRemaining = currentRoundData.timeLimitInSeconds; // Set the time limit for this round based on the RoundData object
UpdateTimeRemainingDisplay();
playerScore = 0;
questionIndex = 0;
ShowQuestion();
isRoundActive = true;
}
void Update()
{
if (isRoundActive)
{
timeRemaining -= Time.deltaTime; // If the round is active, subtract the time since Update() was last called from timeRemaining
UpdateTimeRemainingDisplay();
if (timeRemaining <= 0f) // If timeRemaining is 0 or less, the round ends
{
EndRound();
}
}
}
void ShowQuestion()
{
RemoveAnswerButtons();
QuestionData questionData = questionPool[questionIndex]; // Get the QuestionData for the current question
questionText.text = questionData.questionText; // Update questionText with the correct text
for (int i = 0; i < questionData.answers.Length; i ++) // For every AnswerData in the current QuestionData...
{
GameObject answerButtonGameObject = answerButtonObjectPool.GetObject(); // Spawn an AnswerButton from the object pool
answerButtonGameObjects.Add(answerButtonGameObject);
answerButtonGameObject.transform.SetParent(answerButtonParent);
answerButtonGameObject.transform.localScale = Vector3.one;
AnswerButton answerButton = answerButtonGameObject.GetComponent<AnswerButton>();
answerButton.SetUp(questionData.answers[i]); // Pass the AnswerData to the AnswerButton so the AnswerButton knows what text to display and whether it is the correct answer
}
}
void RemoveAnswerButtons()
{
while (answerButtonGameObjects.Count > 0) // Return all spawned AnswerButtons to the object pool
{
answerButtonObjectPool.ReturnObject(answerButtonGameObjects[0]);
answerButtonGameObjects.RemoveAt(0);
}
}
public void AnswerButtonClicked(bool isCorrect)
{
if (isCorrect)
{
playerScore += currentRoundData.pointsAddedForCorrectAnswer; // If the AnswerButton that was clicked was the correct answer, add points
scoreDisplay.text = playerScore.ToString();
}
if(questionPool.Length > questionIndex + 1) // If there are more questions, show the next question
{
questionIndex++;
ShowQuestion();
}
else // If there are no more questions, the round ends
{
EndRound();
}
}
private void UpdateTimeRemainingDisplay()
{
timeRemainingDisplay.text = Mathf.Round(timeRemaining).ToString();
}
public void EndRound()
{
isRoundActive = false;
dataController.SubmitNewPlayerScore(playerScore);
highScoreDisplay.text = dataController.GetHighestPlayerScore().ToString();
questionDisplay.SetActive(false);
roundEndDisplay.SetActive(true);
}
public void ReturnToMenu()
{
SceneManager.LoadScene("MenuScreen");
}
}
GameDataEditor
using UnityEngine;
using UnityEditor;
using System.Collections;
using System.IO;
public class GameDataEditor : EditorWindow
{
public GameData gameData;
private string gameDataProjectFilePath = "/StreamingAssets/data.json";
[MenuItem ("Window/Game Data Editor")]
static void Init()
{
EditorWindow.GetWindow (typeof(GameDataEditor)).Show ();
}
void OnGUI()
{
if (gameData != null)
{
SerializedObject serializedObject = new SerializedObject (this);
SerializedProperty serializedProperty = serializedObject.FindProperty ("gameData");
EditorGUILayout.PropertyField (serializedProperty, true);
serializedObject.ApplyModifiedProperties ();
if (GUILayout.Button ("Save data"))
{
SaveGameData();
}
}
if (GUILayout.Button ("Load data"))
{
LoadGameData();
}
}
private void LoadGameData()
{
string filePath = Application.dataPath + gameDataProjectFilePath;
if (File.Exists (filePath)) {
string dataAsJson = File.ReadAllText (filePath);
gameData = JsonUtility.FromJson<GameData> (dataAsJson);
} else
{
gameData = new GameData();
}
}
private void SaveGameData()
{
string dataAsJson = JsonUtility.ToJson (gameData);
string filePath = Application.dataPath + gameDataProjectFilePath;
File.WriteAllText (filePath, dataAsJson);
}
}
7. Question and Answer
In this live training session we will learn how to extend our basic Quiz Game created in session one to include: loading game data from an external json file, editor scripting to create a window for editing game data and very basic saving of player progress.
Quiz Game 2 - Questions and Answers [7/7] Live 2016/25/11
PlayerProgress
public class PlayerProgress
{
public int highestScore = 0;
}
DataController
using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections;
using System.IO; // The System.IO namespace contains functions related to loading and saving files
public class DataController : MonoBehaviour
{
private RoundData[] allRoundData;
private PlayerProgress playerProgress;
private string gameDataFileName = "data.json";
void Start()
{
DontDestroyOnLoad(gameObject);
LoadGameData();
LoadPlayerProgress();
SceneManager.LoadScene("MenuScreen");
}
public RoundData GetCurrentRoundData()
{
// If we wanted to return different rounds, we could do that here
// We could store an int representing the current round index in PlayerProgress
return allRoundData[0];
}
public void SubmitNewPlayerScore(int newScore)
{
// If newScore is greater than playerProgress.highestScore, update playerProgress with the new value and call SavePlayerProgress()
if(newScore > playerProgress.highestScore)
{
playerProgress.highestScore = newScore;
SavePlayerProgress();
}
}
public int GetHighestPlayerScore()
{
return playerProgress.highestScore;
}
private void LoadGameData()
{
// Path.Combine combines strings into a file path
// Application.StreamingAssets points to Assets/StreamingAssets in the Editor, and the StreamingAssets folder in a build
string filePath = Path.Combine(Application.streamingAssetsPath, gameDataFileName);
if(File.Exists(filePath))
{
// Read the json from the file into a string
string dataAsJson = File.ReadAllText(filePath);
// Pass the json to JsonUtility, and tell it to create a GameData object from it
GameData loadedData = JsonUtility.FromJson<GameData>(dataAsJson);
// Retrieve the allRoundData property of loadedData
allRoundData = loadedData.allRoundData;
}
else
{
Debug.LogError("Cannot load game data!");
}
}
// This function could be extended easily to handle any additional data we wanted to store in our PlayerProgress object
private void LoadPlayerProgress()
{
// Create a new PlayerProgress object
playerProgress = new PlayerProgress();
// If PlayerPrefs contains a key called "highestScore", set the value of playerProgress.highestScore using the value associated with that key
if(PlayerPrefs.HasKey("highestScore"))
{
playerProgress.highestScore = PlayerPrefs.GetInt("highestScore");
}
}
// This function could be extended easily to handle any additional data we wanted to store in our PlayerProgress object
private void SavePlayerProgress()
{
// Save the value playerProgress.highestScore to PlayerPrefs, with a key of "highestScore"
PlayerPrefs.SetInt("highestScore", playerProgress.highestScore);
}
}
GameController
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
using System.Collections.Generic;
public class GameController : MonoBehaviour
{
public SimpleObjectPool answerButtonObjectPool;
public Text questionText;
public Text scoreDisplay;
public Text timeRemainingDisplay;
public Transform answerButtonParent;
public GameObject questionDisplay;
public GameObject roundEndDisplay;
public Text highScoreDisplay;
private DataController dataController;
private RoundData currentRoundData;
private QuestionData[] questionPool;
private bool isRoundActive = false;
private float timeRemaining;
private int playerScore;
private int questionIndex;
private List<GameObject> answerButtonGameObjects = new List<GameObject>();
void Start()
{
dataController = FindObjectOfType<DataController>(); // Store a reference to the DataController so we can request the data we need for this round
currentRoundData = dataController.GetCurrentRoundData(); // Ask the DataController for the data for the current round. At the moment, we only have one round - but we could extend this
questionPool = currentRoundData.questions; // Take a copy of the questions so we could shuffle the pool or drop questions from it without affecting the original RoundData object
timeRemaining = currentRoundData.timeLimitInSeconds; // Set the time limit for this round based on the RoundData object
UpdateTimeRemainingDisplay();
playerScore = 0;
questionIndex = 0;
ShowQuestion();
isRoundActive = true;
}
void Update()
{
if (isRoundActive)
{
timeRemaining -= Time.deltaTime; // If the round is active, subtract the time since Update() was last called from timeRemaining
UpdateTimeRemainingDisplay();
if (timeRemaining <= 0f) // If timeRemaining is 0 or less, the round ends
{
EndRound();
}
}
}
void ShowQuestion()
{
RemoveAnswerButtons();
QuestionData questionData = questionPool[questionIndex]; // Get the QuestionData for the current question
questionText.text = questionData.questionText; // Update questionText with the correct text
for (int i = 0; i < questionData.answers.Length; i ++) // For every AnswerData in the current QuestionData...
{
GameObject answerButtonGameObject = answerButtonObjectPool.GetObject(); // Spawn an AnswerButton from the object pool
answerButtonGameObjects.Add(answerButtonGameObject);
answerButtonGameObject.transform.SetParent(answerButtonParent);
answerButtonGameObject.transform.localScale = Vector3.one;
AnswerButton answerButton = answerButtonGameObject.GetComponent<AnswerButton>();
answerButton.SetUp(questionData.answers[i]); // Pass the AnswerData to the AnswerButton so the AnswerButton knows what text to display and whether it is the correct answer
}
}
void RemoveAnswerButtons()
{
while (answerButtonGameObjects.Count > 0) // Return all spawned AnswerButtons to the object pool
{
answerButtonObjectPool.ReturnObject(answerButtonGameObjects[0]);
answerButtonGameObjects.RemoveAt(0);
}
}
public void AnswerButtonClicked(bool isCorrect)
{
if (isCorrect)
{
playerScore += currentRoundData.pointsAddedForCorrectAnswer; // If the AnswerButton that was clicked was the correct answer, add points
scoreDisplay.text = playerScore.ToString();
}
if(questionPool.Length > questionIndex + 1) // If there are more questions, show the next question
{
questionIndex++;
ShowQuestion();
}
else // If there are no more questions, the round ends
{
EndRound();
}
}
private void UpdateTimeRemainingDisplay()
{
timeRemainingDisplay.text = Mathf.Round(timeRemaining).ToString();
}
public void EndRound()
{
isRoundActive = false;
dataController.SubmitNewPlayerScore(playerScore);
highScoreDisplay.text = dataController.GetHighestPlayerScore().ToString();
questionDisplay.SetActive(false);
roundEndDisplay.SetActive(true);
}
public void ReturnToMenu()
{
SceneManager.LoadScene("MenuScreen");
}
}
GameDataEditor
using UnityEngine;
using UnityEditor;
using System.Collections;
using System.IO;
public class GameDataEditor : EditorWindow
{
public GameData gameData;
private string gameDataProjectFilePath = "/StreamingAssets/data.json";
[MenuItem ("Window/Game Data Editor")]
static void Init()
{
EditorWindow.GetWindow (typeof(GameDataEditor)).Show ();
}
void OnGUI()
{
if (gameData != null)
{
SerializedObject serializedObject = new SerializedObject (this);
SerializedProperty serializedProperty = serializedObject.FindProperty ("gameData");
EditorGUILayout.PropertyField (serializedProperty, true);
serializedObject.ApplyModifiedProperties ();
if (GUILayout.Button ("Save data"))
{
SaveGameData();
}
}
if (GUILayout.Button ("Load data"))
{
LoadGameData();
}
}
private void LoadGameData()
{
string filePath = Application.dataPath + gameDataProjectFilePath;
if (File.Exists (filePath)) {
string dataAsJson = File.ReadAllText (filePath);
gameData = JsonUtility.FromJson<GameData> (dataAsJson);
} else
{
gameData = new GameData();
}
}
private void SaveGameData()
{
string dataAsJson = JsonUtility.ToJson (gameData);
string filePath = Application.dataPath + gameDataProjectFilePath;
File.WriteAllText (filePath, dataAsJson);
}
}