CREATING A SAVE/LOAD SYSTEM

Detailed tutorial on how to create a save/load system that does not use the default save functions.

  • Pasty
  • 10/11/2009 03:56 AM
  • 4471 views
Creating a Save/Load System


Update History:

6/12/2011: Added version data and resubmitted to fix spacing errors in the code blocks.

Compatible with:

Game Maker 6.x Full
Game Maker 7 Pro
Game Maker 8 Pro
Game Maker 8.1 Standard

Table of Contents:

- Intro
- The Process
- Saving Variables
- The "Save" script
- The "Load" script

Intro

Hello. This is a tutorial on how to create a save system in Game Maker. Note that this does not cover making a saving interface; that has nothing to do with this process.

In order to utilize this tutorial without a problem, you'll need to have the Pro/Standard edition of Game Maker, due to the following mechanisms:

- Data structures

You'll also need to be able to understand the Game Maker Language (GML).

The Process

Okay, here's how this works. When you save, you define the variables you want to save by adding them to a list. Then, you'll take this list and write it to a file using ds_list_write(). This is so that you have a seemingly encrypted file that the layman player will not really want to mess with. This effectively narrows your pool of people who could hack your savefile to those who have GM 7.0 Pro or those who know how the GM data structures work, because they could use ds_list_write() to decompile your save file. This pool is a great deal smaller than, well, everybody who can read and interpret numbers. It's not a viable substitute for true savefile encryption, and I don't recommend it for any function of a game that is communal, such as online play or a highscore table, since any of the above individuals could easily create an application that decompiles the save file and lets a player change values and recompile.

In loading, you do the exact opposite.

Saving Variables

When you're saving variables using this method, you should definitely determine which ones you need and which ones aren't really necessary. Get a piece of paper and go through your variables. You really want to save what's important. For role-playing games, important structures are character statistics, and location and other playtime data. You don't want to waste your time saving enemy AI structures or anything like that.

The "Save" script

Let's get started. Here's what my initial scrSaveGame() script looks like:

var savelist, savestr, file;


savelist = ds_list_create();

//header

ds_list_add(savelist, GameManager.logist_savecount + 1);

ds_list_add(savelist, objPlaytime.strHours);
ds_list_add(savelist, objPlaytime.strMins);
ds_list_add(savelist, objPlaytime.strSecs);
ds_list_add(savelist, GameManager.logist_location);
ds_list_add(savelist, GameManager.logist_chapter);
ds_list_add(savelist, GameManager.logist_percent);
ds_list_add(savelist, GameManager.logist_money);


This is just creating the list and saving what I like to call the "header." Look, and you'll see that all of the data in that sample (time, location, money, etc) are things you'd display when the player is trying to decide which save file to choose. I like to put this information before any other information in the save file so that it's readily available and I don't have to waste time loading the entire save file - I can just write a header script that only goes as far as the last entry in the header.

This version omits a "slot" variable that gets passed to the script as an argument. When making this system in a game that has more than one save slot, you just want to identify the file as being that slot number somehow and look for that file when you're loading.

Next, for the actual save data. As you can see below, this includes your current room, as well as any other relevant variables. Here, I use for loops to save the current state of the inventory as well as that of the characters (I have six characters, so that's why the second loop ends at six.)

ds_list_add(savelist, room);

ds_list_add(savelist, GameManager.partyCount);

for(i = 1; i < Database.totalItems; i+=1)
{
ds_list_add(savelist, GameManager.inventory[i,0]); //item id
ds_list_add(savelist, GameManager.inventory[i,1]); //quantity
}

for(k = 1; k <= 6; k+=1)
{
ds_list_add(savelist, Database.characters[k].isEnabled);
ds_list_add(savelist, Database.characters[k].partyPosition);
ds_list_add(savelist, Database.characters[k].hp);
ds_list_add(savelist, Database.characters[k].max_hp);
ds_list_add(savelist, Database.characters[k].base_atk);
ds_list_add(savelist, Database.characters[k].base_hit);
ds_list_add(savelist, Database.characters[k].base_def);
ds_list_add(savelist, Database.characters[k].base_skl);
ds_list_add(savelist, Database.characters[k].base_sdf);
ds_list_add(savelist, Database.characters[k].base_agl);
ds_list_add(savelist, Database.characters[k].equipment[RIGHTHAND]);
ds_list_add(savelist, Database.characters[k].equipment[LEFTHAND]);
ds_list_add(savelist, Database.characters[k].equipment[TORSO]);
ds_list_add(savelist, Database.characters[k].equipment[LEGS]);
ds_list_add(savelist, Database.characters[k].equipment[RELIC1]);
ds_list_add(savelist, Database.characters[k].equipment[RELIC2]);

for(i = 1; i <= Database.totalSkills; i+=1)
{
ds_list_add(savelist, Database.characters[k].usableSkills[i,0]); //skill id
ds_list_add(savelist, Database.characters[k].usableSkills[i,1]); //learned?
ds_list_add(savelist, Database.characters[k].usableSkills[i,2]);
ds_list_add(savelist, Database.characters[k].usableSkills[i,3]);
}
}


I find that using loops for the repeatable processes is a lot easier than just copying and pasting. It also helps you keep everything in order, which is the most important aspect of this process. At no time should you let any entries get out of order! It'll cross variables and, depending on the variables you cross, it may result in logic errors or, worse, game-stopping bugs (in the event you cross a string into a real variable.)

At the end of the file, all you have to do is write the list to a string and write that string to a file.

file = file_text_open_write("GameSave.sav");

savestr = ds_list_write(savelist);
file_text_write_string(file, savestr);
file_text_close(file);

ds_list_destroy(savelist);


Saving all of the variables I needed resulted in a file that was only 21 kb, so it's a lot more compact than the built-in save functions, and, since you know what goes into the files, you can have an option where you let your players carry save files across games, even if they're not the same (this is tricky, though.)

The "Load" script

To load the game, you do the exact opposite. Create a list you can put the decompiled string into, and feed the proper values into the right variables, one by one:

var loadlist, loadstr, file, roomID;


loadlist = ds_list_create();

if(!file_exists("GameSave.sav")) return -1; //returns -1 if the file does not exist

file = file_text_open_read("GameSave.sav");
loadstr = file_text_read_string(file);
file_text_close(file);

ds_list_read(loadlist, loadstr);


So, now you have the list in a readable format again. Now, you simply reassign the variables by setting the variables, IN ORDER, using the first entry from the list, then deleting that entry from the list:

GameManager.logist_savecount = ds_list_find_value(loadlist, 0);

ds_list_delete(loadlist,0);

objPlaytime.playHours = real(ds_list_find_value(loadlist, 0));
ds_list_delete(loadlist,0);
objPlaytime.playMins = real(ds_list_find_value(loadlist, 0));
ds_list_delete(loadlist,0);
objPlaytime.playSecs = real(ds_list_find_value(loadlist, 0));
ds_list_delete(loadlist,0);
GameManager.logist_location = ds_list_find_value(loadlist, 0);
ds_list_delete(loadlist,0);
GameManager.logist_chapter = ds_list_find_value(loadlist, 0);
ds_list_delete(loadlist,0);
GameManager.logist_percent = ds_list_find_value(loadlist, 0);
ds_list_delete(loadlist,0);
GameManager.logist_money = ds_list_find_value(loadlist, 0);
ds_list_delete(loadlist,0);


Note where I encapsulated certain variables in real(). This is necessary if you've got a real variable you've created and you're trying to assign a string to it, or vice versa.

When you finish reassigning all the variables, simply room_goto() the room from the save file and destroy the list.

room_goto(roomID);


ds_list_destroy(loadlist);


This concludes my tutorial on saving and loading games in Game Maker 7 Pro. If you have any questions, feel free to let me know.