Saving and loading in your game
From Sidvind
Contents |
[edit] Overview
A common feature in todays games is the ability to save ones game and load it later. This can sometimes be somewhat tricky since we often have lots of objects and states. To save our current state we have to take all this data and store it in a file. However, we cant just save everything. Lots of data can be recalculated, like vertices, normals, terrains and textures. Other data like pointers cant be saved because they would most likely point to an invalid location when loaded. In order to save our objects we will be using a method called serialization and implement a function in each class that should be saved to serialize itself and output to a stream.
This method could also be used to send objects over the network, just rework the save and load methods a bit.
[edit] Serialization
Serialization is a way to flatten out data and store it. Let all savable object derive from a common base class, but they already are right? In the base class declare 2 pure virtual methods save and load, and a method returning the class type.
| Code: Object.h |
class Object {
public:
static Object* loadFromFilePtr(FILE* fPtr);
virtual void save(FILE* fPtr) = 0;
virtual Object* load(FILE* fPtr) = 0;
EClassType classType(){ return _classType; }
protected:
EClassType _classType;
}
|
As you also see I have a static function loadFromFilePtr. This is the magic function we call when we want to load something. I'll get back too it later.
[edit] Saving
Say we have a level containing objects and we want to store this level. aLevel->save(aFilePtr) would be called. The level would then call the save method on all it's objects and a kind of recursion has started. If an object contains one or more object that should be saved it should then call the save method on that object. For a level the save method might look something like this:
| Code: Level.cpp |
void Level::save(FILE* fPtr){
_header.id[0] = 'F';
_header.id[1] = 'O';
_header.version = LEVEL_VERSION;
_header.name_length = strlen(_name);
_header.nr_of_objects = _points.size();
_header.offset_name = sizeof (Header)+1;
_header.offset_object- = _header.offset_name + _header.name_length + 1;
fwrite (&_classType, 1, sizeof(EClassType), fPtr);
fwrite (&_header, 1, sizeof (Header), fPtr);
// Write name
fseek(fPtr, _header.offset_name, SEEK_SET);
fwrite (_name, 1, strlen(_name), fPtr);
// Write objects
fseek(fPtr, _header.offset_points, SEEK_SET);
for ( it=_objs.begin(); it != _objs.end(); it++ ){
(*it)->save(fPtr);
}
}
|
Note: I have left out some declarations, Level contains a struct Header and a char* name. The header is needed to check for a valid file and for different versions of the game. When loading the level the header is loaded and validated first. If it isn't valid the file doesn't contain the level or it's corrupt.
This is how a vector might be stored, also note that it stores the class type! That is important when loading.
| Code: Level.cpp |
void Vector3::save(FILE* fPtr){
fwrite (&_classType, 1, sizeof(EClassType), fPtr);
fwrite (&x, 1, sizeof(float), fPtr);
fwrite (&y, 1, sizeof(float), fPtr);
fwrite (&z, 1, sizeof(float), fPtr);
}
|
[edit] Loading
This is where we use loadFromFilePtr. To load our level we do the following:
| Code: Loading |
FILE *file = fopen ("test.level", "wb");
Level* aLevel = Object::loadFromFilePtr(fPtr);
fclose(file);
|
I didn't check if the file could be loaded, but you should! Never assume it did! loadFromFilePtr reads the classtype from the file and allocates the appropriate object, and then calls load to construct the object, like this:
| Code: Object.cpp |
Object* Object::loadFromFilePtr(FILE* fPtr){
EClassType type = CT_Unknown;
fread( &type, 1, sizeof(EClassType), fPtr);
switch(type){
case CT_Level:
return (new Level)->load(fPtr);
break;
case CT_Vector:
return (new Vector)->load(fPtr);
default:
printf("UNKNON TYPE: %d\nGoing to die badly =/\n",type);
return NULL;
break;
}
}
|
| Code: Level.cpp |
Object* Level::load(FILE* fPtr){
Header header;
/* Fill the header with data */
fread (&header, 1, sizeof (Header), fPtr);
/* Check if its a valid file */
if ( header.id[0] != 'F' || header.id[1] != 'O' || header.version != LEVEL_VERSION ){
printf("Not a valid file...\n");
} else {
// Read name
fseek (fPtr, header.offset_name, SEEK_SET);
fread (_name, 1, header.name_length, fPtr);
// Read objects
fseek (fPtr, header.offset_objs, SEEK_SET);
Vector3* v;
for ( int i=0; i<header.nr_of_objs; i++){
_objs.push(Object::loadFromFilePtr(filePtr));
}
}
return this;
}
|
| Code: Vector.cpp |
Object* Vector3::load(FILE* fPtr){
fread (&x, 1, sizeof(float), fPtr);
fread (&y, 1, sizeof(float), fPtr);
fread (&z, 1, sizeof(float), fPtr);
return this;
}
|
Note: Since we already read the classtype the classes should not read it again.
Hopefully you should have a basic idea of how to implement saving and loading in you game. This is only a basic method and there is a lot of improvement to be done but that is not covered here.

