Saving and loading in your game

From Sidvind

Jump to: navigation, search

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.

-->
Views
Personal tools
Navigation
Toolbox