The Horrors of Saving
Just before we released our private BETA almost 2 weeks ago, Tim requested that I prepare a ‘save’ option so that testers can save the game to play later, but also send the save files to me if there were glitches so that I can see what’s going on. It sounded like an essential feature for our first round of BETA releases.
Unfortunately, Game Maker’s in-built “game_save” function just wouldn’t do. While we used it for Caveman Craig and the Special Edition, there was a lot of additional coding that had to be done to re-instate some data that’s lost during the game save. Namely, data lists (ds_list functions) and instance ID’s.
In CC: Classic and CC: SE, I used a simple ‘switch’ that told the game that a game had just loaded, which tries to ‘guess’ the contents of DS lists, to avoid errors, etc. It was as simple as:
global.justLoaded=true;
game_save("myfile.sav");
global.justLoaded=false;
The variable is quickly switched to false after the game saves, so that no other object thinks the game has just loaded when in fact it had just saved. But when the game is loaded, the switch was missed, and so it gives all the objects a chance to ‘work their crap out’ to avoid errors. It was messy, but it worked.
Not so for Caveman Craig 2, which has twists and turns in the code everywhere you look that the game_save function just doesn’t account for. I released the BETA without a save script, and made a note of looking at this as soon as we got back to work.
There is a lot to do, but I think I’ve finalised my concept for a custom save script. The simple part is as follows:
When saving the game, I use this code:
with(all) event_user(15);
This tells every instance in the entire game to execute its 15th user event. If the object has nothing that it needs to ‘tell’ the save file (like the cursor object or something), I simply leave this event blank. Otherwise, the object writes code to the save file which involves creating a clone of itself, with all the variables set correctly.
The save file is then executed as a script when loading, which creates all the instances and sets their variables to as they were when the game was saved.
Seems pretty simple and solid, right? WELL. The tricky part is in the fact that instance ID’s change between games (from what I could tell, game maker’s “game_save” function does not preserve instance IDs). Caveman & dinosaurs interact a lot, and there are a lot of variables which contain IDs of instances with some significance to the object. Example, if a raptor is eating Craig, Craig has “raptorID” set to the Id of the raptor eating him. The raptor also has “targetID” set to Craig’s ID. In using game_save and game_load, this gets lost and suddenly Craig has no idea who’s eating him, and the raptor has no idea who he’s eating, resulting in way too many errors to try and work around.
So I came up with the idea for an “ID Translation List”. With this concept, there are two DS lists. An A list and a B list. The A list contains every ID of every instance in the game as it was when the game was saved. The B list contains the new ID of each instance when the game is loaded. Because the file is compiled as a script, and executed as a script, it can work this out in 2 parts.
Firstly, in the game save script I created, it creates some of its own lines of code in the save file before getting every instance to add theirs:
file_text_write_string(global.saveFile,"global.idTranslationA = ds_list_create();
global.idTranslationB = ds_list_create();
ds_list_add(global.idTranslationA,-4);
ds_list_add(global.idTranslationB,-4);
");
This creates the 2 lists, but also adds “-4″ (the special term ‘noone’ which does not change) to avoid errors when, for example, Craig is not being eaten by any raptor at all.
Additionally, the script now calls the user events from all instances twice. Once with the variable “idTranslate” set to false, and again with it set to true. This is to place the code into the file in the right order, as follows:
-> create each instance with basic variables
-> add each instance’s old and new ID into the translation list
then
-> set each instance’s ID variables such as ‘targetID’ or ‘raptorID’ correctly by using the translation list
It looks like this:if global.idTranslate=false
{
file_text_write_string(global.saveFile,"
_"+string(id)+" = instance_create("+string(x)+","+string(y)+","+string(object_index)+")
ds_list_add(global.idTranslationA,"+string(id)+");
ds_list_add(global.idTranslationB,_"+string(id)+".id);
_"+string(id)+".myHealth="+string(myHealth)+"
_"+string(id)+".myFood="+string(myFood)+"
_"+string(id)+".myStatus="+string(myStatus)+"
_"+string(id)+".hasspear="+string(hasspear)+"
_"+string(id)+".superclub="+string(superclub)+"
_"+string(id)+".sprite_index="+string(sprite_index)+"
_"+string(id)+".image_index="+string(image_index)+"
_"+string(id)+".image_speed="+string(image_index)+"
");
}
else
{
//Uses the translation list to set ID variables like targetID
file_text_write_string(global.saveFile,"
_"+string(id)+".targetID=ds_list_find_value(global.idTranslationB,ds_list_find_index(global.idTranslationA,"+string(targetID)+"));
_"+string(id)+".harvestID=ds_list_find_value(global.idTranslationB,ds_list_find_index(global.idTranslationA,"+string(harvestID)+"));
_"+string(id)+".raptorID=ds_list_find_value(global.idTranslationB,ds_list_find_index(global.idTranslationA,"+string(raptorID)+"));
_"+string(id)+".prevrockID=ds_list_find_value(global.idTranslationB,ds_list_find_index(global.idTranslationA,"+string(prevrockID)+"));
_"+string(id)+".tokenID=ds_list_find_value(global.idTranslationB,ds_list_find_index(global.idTranslationA,"+string(tokenID)+"));
");
}
This script is for Craig, by the way.
So far I have only tested interaction with Craig and an Othneilia + his carcass, and it seems to work correctly. There is a LOT of work and testing to be done, but I’m confident this is going to be the most solid way of saving and loading in Caveman Craig 2.
-Rhys
Food. Luck Good.
please tell us about the game and not this stupid stuff
This is about the game.
You’ll find a mix of both technical jargon articles and stuff about the game itself. Some people like one, some like the other.
Thanks for keeping us up to date, cant wait to play the game.
Interesting read; what I don’t understand is why I never had this problem with my own Game Maker games loading and saving. I certainly made frequent use of instance ID numbers. I think the problem only occurs if you’re using data structures. In other words, the instance id numbers do get saved, but the contents of a data structure do not.
Anyway… good work on the game, looking forward to the sequel.