Workflow and User Events - Grisgram/gml-raptor GitHub Wiki
I've tried to simplify the workflow for the complicated task of creating a savegame file as much as possible, and I ended up with only two user events that might need attention while the game is saved or loaded.
In addition to that, some objects might need to know when they are created, whether it is a regular object spawn or if the instance was just created during the game load workflow.
An example for this could be an object that creates additional child instances when it is created (like preparing some bullets so it can shoot or some animated objects surrounding it). If the object just gets restored from a save game, it does not need to create those children since they have to be part of the savegame anyway (like mentioned in Planning your Savegame) and will get created by the Savegame
system.
This is the moment, where you might want to query the SAVEGAME_LOAD_IN_PROGRESS
or SAVEGAME_SAVE_IN_PROGRESS
macros to skip that part in this case.
The next logical question is, "Ok, but when those child objects are also created for me, how can the Savegame
know that these 25 Bullets are my Bullets? What about the links between objects, restoring pointers to other instances?".
Savegame
does that!
But before I go into detail, let's look at the timing in the workflow, especially the two *_IN_PROGRESS
macros mentioned above. You need to know when they are true
or false
to understand how instance link save and restore works.
Saving | Loading |
SAVEGAME_SAVE_IN_PROGRESS = true |
SAVEGAME_LOAD_IN_PROGRESS = true |
onGameSaving (User Event 14) | onGameLoading (function) |
onGameSaving (function) | SAVEGAME_LOAD_IN_PROGRESS = false |
SAVEGAME_SAVE_IN_PROGRESS = false |
onGameLoaded (User Event 15) |
onGameSaved (function) | onGameLoaded (function) |
You can see, the present participle ...ing
(Sav_ing, Load_ing) functions are invoked while the progress variables are true
and the past tense ...ed
functions are invoked after the flags return to false
. So, from a timing view, after the process has completed, but before the control is given back to the game. From the call stack perspective, we are still in the save
or load
function respectively.
GameMaker assigns each object a unique id
when it gets created. This will very likely not be the same when the game is loaded back into memory and the objects are created. So we need some kind of reference that allows us to "re-link" objects after they have been loaded.
When saving, Savegame
detects all instance_ids in the structs to be saved.
Instead of blindly serializing object-in-object-in-object to the savegame, those instances are replaced by special markers in the file.
They look like "objref" : "##_savegame_ref_##.100006"
.
When the game is loaded, Savegame
builds a reference table, containing the "old id" (the number 100006 in the example above) and the "new id", which is the instance id from the object after instance_create_layer
or instance_create_depth
has been invoked by Savegame
and the new instance has been created.
This process is repeated until all objects are created.
It then loops over all created objects and replaces the marker with the instance of the new object to restore the link.
This code example shows how you can avoid creating unnecessary objects when the game is loaded and child objects will be created by the Savegame
engine.
// Create event
// Note: this is held in data!
// The objects `Bullet` and `AnimatedShield` are assumed to be also a Saveable objects!
data.my_bullets = array_create(25);
data.my_orbiting_shield = undefined;
// Take care, to create bullets only, if you are not currently restored from a savegame
if (!SAVEGAME_LOAD_IN_PROGRESS) {
var i = 0; repeat(array_length(data.my_bullets)) {
data.my_bullets[@ i] = instance_create_layer(x, y, "Instances", Bullet);
i++;
}
data.my_orbiting_shield = instance_create_layer(x, y, "Instances", AnimatedShield);
}
Next, you should read, how the Savegame System invokes constructors of your script classes when loading the game. Find it out in Invoking Constructors.