Skip to content

Game File

Luke Mirman edited this page Sep 10, 2022 · 2 revisions

Introduction

The GameFile class is a generic class that handles reading/writing of data to and from the user's Application.persistentDataPath. Because it is generic you can use this infrastructure to represent any serializable class you write. Common use cases for this class are saving preferences or player save data.

Constructors

Constructor Format Parameter Description Examples
GameFile(string fileName, Encryption.Encryptor encryptor, FileType fileType = FileType.Encrypted) fileName - The name of the file to be written to the users disk. Do not include any extensions. Make sure to obey the file naming scheme of your target platforms.
encryptor - The method of encryption to use for creating the .dat file. See: Encryption
fileType - The default representation of this file. By default is encrypted but can save as Unencrypted for files such as preferences or if you otherwise want the user to see the raw file.
new GameFile<SaveFileData>("savefile1", Encryption.DefaultEncryptor);

new GameFile<PreferencesData>("preferences", Encryption.DefaultEncryptor, FileType.RawJson);

Properties

Property Name Type Description
Data T The data that is being held by this GameFile. After a read, this will represent the data that was read from the file. It can then be modified and the written back to file later.
ValidData bool Determines if the Data being held in this GameFile is valid. This is most relevant for the WriteFile() function which will reject a write attempt if the data is invalid, to prevent loss of data.

Methods

Method Name Description Returns
bool WriteFile() Writes the current Data to the user's disk. Fill fail if ValidData is not true. bool - True if the write was successful, false if it was not.
bool ReadFile() Load the file from the user's disk. If the file is successfully read will automatically mark ValidData to true. bool - True if the read was successful, false if it was not (such as the file not existing).
bool PeekFile(out T data) Inspect the data on the user's disk without actually loading it into the GameFile's Data. bool - True if the file was read and output to out T data, otherwise false.
bool DeleteFile() Delete the file on the user's disk. This does not reset the Data held in the GameFile. bool - True if there was a file and it was deleted, false otherwise.
bool ImportJsonToBytes() If there is a .json file for this GameFile import it into the .dat file. bool - True if there was a .json file and it was imported successfully, false otherwise.
bool ExportBytesToJson() If there is a .dat file for this GameFile export it to the .json file. bool - True if there was a .dat file and it was exported successfully, false otherwise.

Events

Event Name Invoke Condition
OnFileWritten(GameFile<T>); Invoked when the file is written to the user's system
OnFileRead(GameFile<T>); Invoked when the file is read from the user's system
OnFileDeleted(GameFile<T>); Invoked when the file is deleted from the user's system

Example - Save File

Representing Save File Data

The first order of business is to represent our data in a particular class. In this example we create the PlayerSaveFile which will house all of the important data we want to keep track of across play sessions.

You can populate this class with anything so long as it is serializable, including other classes.

Since it is going through a json serialization process you can also use attributes such as [JsonIgnore] to control what data gets written to the save file. This package uses Newtonsoft's Json .NET so see their documentation for more information about controlling the serialization process.

Sample Save File Code

[Serializable]
public class PlayerSaveFile
{
	public int playerLevel = 1;
	public string playerName = "Luke";
	public MissionData missionData = new MissionData();

	// This isn't a practical property to have, but is mainly a demonstration of using `[JsonIgnore]` to mark data as exempt.
	[JsonIgnore]
	public string AllCapsPlayerName => playerName.ToUpper();

	[Serializable]
	public class MissionData
	{
		public Dictionary<int, int> highScores = new Dictionary<int, int>();

		public void RecordHighScore(int missionKey, int score)
		{
			if (highScores.ContainsKey(missionKey))
			{
				highScores[missionKey] = Mathf.Max(highScores[missionKey], score);
			}
			else
			{
				highScores.Add(missionKey, score);
			}
		}
	}
}

Reading and Writing the Save File Data

Now that we have the PlayerSaveFile class to house our data we should now create another class that will utilize an instance of GameFile to read/write the data we set.

Keep in mind that this is merely an example and because of the compartmentalization of GameFile into instances you can do a more features such as creating multiple save files or handling multiple different files in a single class.

Sample Save File 'Manager'

public static class SaveFile
{
	// Our instance of GameFile, which we will act upon to manage our data.
	// You can create multiple of these or even an array of them to handle multiple user save files.
	private static readonly GameFile<PlayerSaveFile> FileInstance;

	// Initialize the instance immediately when this class is referenced
	// In your own implementation this could be handled in `Awake` if you are utilizing a MonoBehaviour.
	static SaveFile()
	{
		FileInstance = new GameFile<PlayerSaveFile>("savefile", Encryption.DefaultEncryptor);
	}

	// A shorthand way of referencing the game file's actual data.
	// Not strictly necessary but can be convenient.
	public static PlayerSaveFile CurrentSave => FileInstance.Data;

	// Writes the save file to the user's disk.
	public static void WriteSave()
	{
		FileInstance.WriteFile();
	}

	// Loads the save file from the user's disk.
	// Creates a new save file if there was no file read.
	[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
	public static void LoadSave()
	{
		bool didLoadFile = FileInstance.ReadFile();
		if (!didLoadFile)
		{
			ResetSave();
		}
	}

	// Creates a new save file and then writes it to the disk.
	public static void ResetSave()
	{
		FileInstance.Data = new PlayerSaveFile();
		FileInstance.ValidData = true;
		WriteSave();
	}

	// Delete the save file from the user's disk.
	public static void DeleteSave()
	{
		FileInstance.DeleteFile();
	}

	// Writes a .json file for the user data
	// This is useful for inspection of the actual data being written to the file.
	public static void ExportSave()
	{
		FileInstance.ExportBytesToJson();
	}
	
	// Imports a .json file and uses it for the user data.
	// A convenient process for testing is to use `ExportSave()`, modify it, and then use `ImportSave()` to test certain save state scenarios.
	public static void ImportSave()
	{
		bool didImportFile = FileInstance.ImportJsonToBytes();
		if (didImportFile && Application.isPlaying)
		{
			LoadSave();
		}
	}
}