Saving and Loading User Data
Saving and loading data is a vital part of (almost) any game, however each game handles this in a different way. Similar to UI and lighting, it is difficult to have a truly plug-and-play off the shelf solution. I hope to provide a basic implementation for you that can be extended and modified with minimal effort to suit your individual circumstances. I want this article to cater to all levels of programming experience, so I will do my best to explain the logic behind each step. If you are unsure of what any function or variable means, I implore you to consult the Gamemaker manual, it is a treasure trove of information.
Firstly, this article will focus on saving user data, rather than typical ‘save files’; although the techniques here can certainly be applied to standard save files. What exactly is “user data” anyway? In general, every game has some kind of data which the player modifies which should be persistent between game sessions BUT does not belong in a save file. Typical examples include:
• Persistent Progress such as what unlockable items the player has unlocked, the player’s total level/XP etc.
• Settings such as volume, resolution, keybinds
• Game data such as game version
It would be very annoying if your graphical settings and unlocked cosmetics were reset every time you restart the game, right? This can be extended further. A pet peeve of mine is that in Slay the Spire, the game doesn’t remember which class you last played, so you have to manually select it again every time you play. There are many areas where data being kept between game sessions would improve the user experience, and JSON files are ideal for achieving this. JSON is a relatively human readable file format that is great for reading and writing data to that can be used by pretty much any program.
Gamemaker offers a few different ways to save/load data into/from files. We will be using buffers because they have better cross-platform compatibility, although you can use the file_text functions if you’d like. I believe that in general the code that works and that you understand is the best, “there is no real recipe” as it were. So, let’s create a couple of functions off the bat: one for saving a json file and one for loading a json file.
Gamemaker has built in functions for converting structs/arrays to and from JSON strings: json_stringify() and json_parse(). We’ll be using json_stringify here in the save function.
function save_json(_data, _name) {
var json_string = json_stringify(_data);
var buffer = buffer_create(string_byte_length(json_string) + 1, buffer_fixed, 1);
buffer_write(buffer, buffer_string, json_string);
buffer_save(buffer, _name);
buffer_delete(buffer);
}
This function takes two input arguments: _data is the struct/array you wish to save, and _name is the name of the file you will be saving to (eg. “user_data.json”).
Line by line, this function:
• Converts the input struct/array into a JSON string
• Creates a buffer with the correct parameters to store this string
• Writes the JSON string to the buffer
• Saves the buffer in the specified file (if it doesn’t already exist it will be created)
• Deletes the buffer as it is no longer needed
Now using json_parse in the load function:
function load_json(_name) {
var buffer = buffer_load(_name);
if (buffer != -1)
{
var json_string = buffer_read(buffer, buffer_string);
buffer_delete(buffer);
return json_parse(json_string);
}
else
{
return undefined;
}
}
This function takes one argument: _name which is the name of the file to read from. If you enter a file name that doesn’t exist it will return undefined.
Line by line, this function:
• Loads the specified file name into a buffer
• If it is a valid file, reads the text from the buffer.
• Deletes the buffer as it is no longer needed
• Parses the JSON string and converts it into a struct/array, returns the struct/array
This is all well and good, but how do we put this into practice?
If you have never used structs or constructors, this is your lucky day. Structs are incredibly versatile and user-friendly data structures that you can use to store any type of data you want, for example:
example_struct = {
var_1 : 1,
var_2 : "a",
var_3 = ["hello", "world"],
}
Constructors are a special type of function that allows you to generate a struct from a template, and even give it input arguments if desired. For more detailed information, check out the manual page, there are a lot of cool things you can do with constructors. For now, we will be using a constructor to create a special data structure that will hold all of our user data. Remember, data structure just means a particular way of storing information, whatever information you need to save in your particular game doesn’t affect this.
function user_data_struct() constructor {
// SET DEFAULT VALUES FOR VARIABLES THAT YOU WISH TO SAVE - MAKE THESE WHATEVER YOU WANT
var_1 = 1;
var_2 = "a";
var_3 = ["hello", "world"];
// LOAD VALUES
load_values = function(_data) {
var load_data = _data;
var fields = variable_struct_get_names(load_data);
for (var i = 0, len = array_length(fields); i < len; i++)
{
self[$ fields[i]] = load_data[$ fields[i]];
}
}
// UPDATE VALUE
update_value = function(_name, _value) {
self[$ _name] = _value;
save_json(self, "user_data.json");
}
}
This constructor is broken up into three parts:
Setting default values
First, we want to set default values for every piece of data we want to save. These will be the values used when a new player first plays the game. I have put example variables here, but you can add whatever you’d like, for example a variable that stores the music volume in the game.
Loading values from a file
Second, we have a method variable that loads in the data which has been read from the JSON file. A method variable sounds complicated but for our purposes it is actually super simple, it is just a function that is saved in a variable, therefore you call the function by calling the variable name instead, eg. load_values(); You can do more complex things with methods but you can check them out in the manual at your leisure. This function takes the output of the load_json function as its input, and cycles through and updates every variable with the value from load_json. We run this at the start of the game when loading the user data file.
Updating a value
Third, we have another method variable, this one updates a variable in the struct. Now if you are familiar with structs you might be thinking “hang on, why can’t I just do something like my_struct.var_1 = 7;”. The reason I have this method variable is so the save_json function is automatically called whenever you update a variable in the struct. This means you don’t have to ever worry about manually saving the user data, it automatically saves whenever the player updates any of the user data.
Before I bore you too much, lets get to the good part: how to implement this. Glad you asked.
Hopefully you already have some kind of “initialisation” room at the start of your game that does some initial setup, like creating a camera or setting some global variables. If you don’t, that’s ok, either create one or you could put this in your main menu screen, but you only really want to run this code once when the player launches the game so it is better off in an “initialisation” type room. If you have an existing “initialisation” object you can put this code in the create event, or create a new object and do the same.
/// CREATE CODE
global.user_data = new user_data_struct();
if (file_exists("user_data.json"))
{
var user_data = load_json("user_data.json");
global.user_data.load_values(user_data);
}
save_json(global.user_data, "user_data.json");
It is that simple. When the game starts you create a new user_data struct using the constructor we made earlier. Then we check if a user_data.json file exists (or whatever you have named it), and it so we load the file and load the values into the user_data struct. We then save the user data; this creates the file the first time the player runs the game, and ensures it is up to date after you launch the game.
Because of this and the automated saving whenever a value is updated, we don’t have to worry about losing sync between the save file and game data while the game is running. We will only have to interface with ONE variable (global.user_data) using ONE function (update_value). The flow goes like this:
• Create global variable with user data at game launch
• If save file exists, load data from save file into global variable
• Read from and update variables in the global variable
• Save file is automatically updated whenever global variable is updated
Here are some examples of how you update values in the user struct and how you can read variables from it to use in your game logic.
/// UPDATING A VALUE EXAMPLE
global.user_data.update_value("music_volume", 0.5);
/// READING A VALUE EXAMPLE
music_volume = global.user_data.music_volume;
This code can be modified however you’d like really. Want to manually save all the time and not automatically save? Chop out the save_json line in update_value. Want to use this for a regular save file instead? Go for it. Want to store structs full of arrays full of structs full of arrays? Go right ahead, json_stringify can handle it.
I hope you have found this guide helpful and entertaining.