Menu

Save Files

Load the player's inventory or how many levels has completed. All this data can be stored using save files.

It doesn't matter what kind of game you are developing. At some point you will need to store some data to be used the next time the game is executed. Save your progress.

The most simple example is a "config" file. Usually a text file that has all the video/sound/user preferences and the game read it every time it starts. Things like screen resolution, music volume, hotkeys, etc.

This type of files can look like this:

[video]
width = 1920
height = 1080
vsync = true
gamma = 0.95

[sound]
music = 70
effect = 80

[hotkeys]
moveUP = 10
moveDOWN = 14
...

Simple structured text files. You can find tons of config parsers to read and write simple data in c++ or any other language.

And use it in the game code:

// Read the configuration file
ConfigFile config;

// If not exist -> create a new one using default values
if (!config.read("config.txt"))
{
	config.set("width", 1024);
	config.set("height", 728);
	...
	
	// Write the file on disk
	config.write();
}


// Load resolution values
int width  = config.get("width");
int height = config.get("height");

// Create the window
window.create(width, height);

Another option would be using XML or JSON data files as I commented about in a previous article.

Now ask yourself, ¿This solution will work as well with save files for my game?

The answer is YES and NO.

Yes, because any kind of data can be stored in this type of files, plain text, xml, json...

No, because it's vulnerable to user modification.

One of the most important rules in computer security is All input is evil until proven otherwise

Let's say you have created an RPG game and you are using XML files to store save files. Things like player level, inventory, gold, etc. This file is keep in [My Documents/RPG GAME] folder, long side the config.txt file.

Any user can go to the folder, open the savefile.xml with any text editor and see this:

<?xml version="1.0" encoding="UTF-8" ?>

<Data>
    <Player>
        <Level>5</Level>
        <Gold>500</Gold>
        <Inventory>
			<Item>Sword</Item>
			<Item>Shield</Item>
		</Inventory>
    </Player>
</Data>

As you can see, it's very easy to modify this file and change values. Giving yourself a million gold or increase your level. Cheating the game and ruining the experience.

That's why you need to protect your save files. I'm not talking about some crazy super-encryption or anything like that. Just a type of file that store data in a not human readable way.

The answer: Binary Files

#include <iostream>
#include <fstream>
#include <string>

class Player
{
private:
	int   level;
	float gold;

public:
	Player() { level = gold = 0; }
	Player(int lvl, float g, std::string &n, std::vector &items)
	{
		level = lvl;
		gold  = g;
		name = n;
		vInventory = items;
	}
	
	void display()
	{
		std::cout << "Level: " << std::to_string(level) << std::endl;
		std::cout << "Gold: " << std::to_string(gold) << std::endl;
	}

	void load(std::ifstream &file)
	{
		file.read(reinterpret_cast<char*>(&level), sizeof(level));
		file.read(reinterpret_cast<char*>(&gold), sizeof(gold));
	}

	void save(std::ofstream &file)
	{
		file.write(reinterpret_cast<const char*>(&level), sizeof(level));
		file.write(reinterpret_cast<const char*>(&gold), sizeof(gold));
	}
};

int main()
{
	Player A(5, 500.f);

	// save player A
	{
		std::ofstream file;
		file.open("save.dat", std::ios::out | std::ios::binary);
		A.save(file);
		file.close();
	}

	// copy player A data into B from the savefile
	Player B;
	std::ifstream file;
	file.open("save.dat", std::ios::in | std::ios::binary);
	B.load(file);
	file.close();

	B.display();
}

The code above shows how to save and load data from a custom class to/from a binary file.

And if you open the save.dat file with a text editor it will look something like this:

     C              ÌÌÌÌÌÌÌÌ  CÌÌÌÌÌÌÌÌ   ÌÌÌ̘‹í3Üù ë™
     ìù iU
    ˆLM ëL è‹í3         àý~    “¹°¡           °ù $(]’0ú ø ìpý3    ôù ]W
  ú j3mw àý~@ú ™Àw àý~­Jw         àý~            ú     ÿÿÿÿÅXÄwx‘ë     Xú Õ˜ÀwÇ  àý~                Ç  àý~ 

The file no longer can be modify by any user without corrupting them or making it unreadable for the game.

The Player class only has two variables but can be expanded at will. But the code will become very messy when strings or vectors are added to the class.

In other words, this method is valid for small and simple data structures. But there is a better method to create simple and complex binary savefiles with cleaner and efficient code.

Boost Serialization has everything we talk about earlier. The whole library is very useful, but the serialization part really makes the edge for writing and reading custom save files with any STL data structure like strings, or vectors.

Next I will write the same code as before but using Boost Serialization method

#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <boost/archive/binary_iarchive.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/serialization/utility.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/serialization/version.hpp>

class Player
{
private:
	int   level;
	float gold;
	std::string name;
	std::vector vInventory;

public:

	friend class boost::serialization::access;
	template<class Archive>
	void serialize(Archive & ar, const unsigned int ver)
	{
		// load/save the following variables
		ar & level;
		ar & gold;
		ar & name;
		ar & vInventory;

		// also can be write like this
		// ar & level & gold & name & vInventory;
	}

	Player() { level = gold = 0; }
	Player(int lvl, float g, std::string n, std::vector items)
	{
		level = lvl;
		gold = g;
		name = n;
		vInventory = items;
	}

	void display()
	{
		std::cout << "Level: " << std::to_string(level) << std::endl;
		std::cout << "Gold: " << std::to_string(gold) << std::endl;
		std::cout << "Name: " << name << std::endl;
		std::cout << "Items: " << std::to_string(vInventory.size()) << std::endl;
	}
};

int main()
{
	Player A(5, 500.f, "player", { 1, 10, 5, 6 });

	// save player A
	{
		std::ofstream file("save.dat", std::ios_base::binary | std::ios_base::out);
		boost::archive::binary_oarchive oa(file);

		oa << A;

		// archive and stream closed when destructors are called
	}

	// copy player A data into B from the savefile
	Player B;
	{
		std::ifstream file("save.dat", std::ios_base::binary | std::ios_base::in);
		boost::archive::binary_iarchive ia(file);

		ia >> B;
	}

	B.display();
}	

You can try this code yourself. Copy-Paste and remember to tell your compiler the include directories for Boost.

It's easy to understand and very straight forward to implement. The most important part is this:

friend class boost::serialization::access;
template<class Archive>

void serialize(Archive & ar, const unsigned int ver)
{
	...
}

You can serialize any kind of class or struct you want simply by copying the function above inside your data structure. And then especify what variables want to be saved. In this example, we serialize all the variables inside the Player class, but it's not mandatory to do so.

I strongly recommend using this method of serialization for creating save files. Easy to understand, fast to implement and has good performance.

In summary, use plain text for configuration files or user preferances. And binary serialization for save files or sensible data you want to store.


Read more:






AVAILABLE ON STEAM





Icons made by Lorc. Available on Game-Icons.net