As per my first post, what I need I to set my mind on one thing I want to develop and just go for it, so here we go.
What I love about modern game and anime storytelling is how, even though the world building is complete from the start with incredible detail, the story will start at its smallest unit possible – a country. A village. Sometimes one family. The viewers start as blissfully ignorant as the story’s characters and piece what’s happening in the world alongside them.
Well, I’m not a master storyteller not have my world fully built yet – but I figured I myself could start game dev from a small place. So, for <whatever the title of this game will be> , our focus will be the friendship of two characters – a knight running missions and the city’s blacksmith.
This game will, therefore, have few screens, lots of scenes – Our headquarters will have the knight and the blacksmith hanging out at the forge; here the blacksmith will use gathered resources to craft new equipment or improve old ones; the knight or the blacksmith can then equip the new items. From here, the player decides if they want to proceed with the knight’s story or collect some more items to craft better gear. So, with the smallest, smallest setup I can think of, what I want is this:

Now, the first thing I want to know as a web developer: how is data shared across all screens and saved on checkpoints? In web dev centralizing the data means setting up a store and saving stuff in there; making it persistent is making a 30$/month DB instance in a private subnet somewhere.
Because thinking of a persistent Cloud Save method sounds a bit overkill to me right now, I’ll learn the easiest part first: sharing the save data across the various scenes in Unity.
As always, if something is confusing about Game Dev, chances are Brackeys has made a tutorial on YouTube to help us understand the basics (I’m sad he hasn’t been posting recently).
As it turns out, PlayerPrefs handles data well but only in small quantities, and XML or JSON can just manually be edited and anyone can cheat player progress. So we write our own binary files!
The only thing we need is to write whatever data we want to save/load in a format that our Binary Formatter understand. As brackeys puts it, this means that our data need to be made of serializable (= basic type) variables. This means string, int, bool, float, but not Vector3, Color, ScriptableObject.
So for a character, for example, let’s say we want to save their level and current experience. Data object first:
[System.Serializable]
public class PlayerData {
public int level;
public int xp;
// Parameterless constructor for creating default instances
public PlayerData()
{
level = 1;
xp = 0;
}
}
Now we only need a SaveSystem script that knows how to load this from/write this to a file. This is a class we create as a singleton, as we want a single instance of it handling data throughout the game (as opposed to different instances reading from/writing to the same or multiple slots in the same session and creating a mess)
using UnityEngine;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System;
public class SaveSystem
{
// Singleton pattern: private static instance
private static SaveSystem _instance;
// Public property to access the singleton instance
public static SaveSystem Instance
{
get
{
// Create instance if it doesn't exist (lazy initialization)
if (_instance == null)
{
_instance = new SaveSystem();
}
return _instance;
}
}
// Private constructor prevents creating instances from outside
private SaveSystem() { }
public void SavePlayer(PlayerData playerData) {
BinaryFormatter formatter = new BinaryFormatter();
string path = Application.persistentDataPath + "/player.data";
FileStream stream = new FileStream(path, FileMode.Create);
formatter.Serialize(stream, playerData);
stream.Close();
}
public PlayerData LoadPlayer(int slotIndex) {
string path = Application.persistentDataPath + "/player.data";
if (File.Exists(path)) {
BinaryFormatter formatter = new BinaryFormatter();
FileStream stream = new FileStream(path, FileMode.Open);
PlayerData data = formatter.Deserialize(stream) as PlayerData;
stream.Close();
return data;
} else {
return null;
}
}
public void DeletePlayer() {
string path = Application.persistentDataPath + "/player" + GetCurrentSlotIndex() + ".data";
if (File.Exists(path)) {
File.Delete(path);
}
Debug.Log("Save file deleted: " + path);
}
}
And for the most basic setup, that’s it! From here our path is pretty much laid out in front of us: before we load whatever our MainScene is, the SaveSystem needs to load player data first. When saving and quitting, our application writes to the file first and then quits (or goes back to the title screen). Now, the UI shenanigans that I went through to test this took a while, so I’ll skip them (this is a dev log, not a tutorial, sorry reader). For the basics, I made a screen that showed level and xp, added xp every second, and had a “Save” and a “Save and Quit” button.
Extras
Because I also wanted to introduce multiple save slots and the whole New Game / Load Game screen too, I also took a look at another tutorial by Shaped by Rain Studios and incorporated a few things from that. They even address the “players can edit the json file” problem by adding encryption to it, so it’s really worth a look! So I made a Title Screen with some UI to choose the slots and added some slot selection logic to the SaveSystem above.