Many games require only simple saving and loading functionality. Perhaps all you need to save about a player is what level they’re up to and their high score — in which case you don’t need an article like this.

Other games have a moderately difficult time saving game state. These games, like The Legend of Zelda, require saving lots of information about the player – your level, experience, items, progress through quests – but only very limited data about the world (whether a secret was uncovered). If that describes your game, read this article just in case.

Other games still have a terrible time saving state. Not only do they need to save the player in the same manner as above, but also need to save loads of data about the world as well. Which dungeons and enemies are cleared, how the world was damaged or affected by the player, which items were bought or traded or left in other locations, etc. The Botanist is one of those games. Let’s read on:

Saving data in general

Let’s start with the simplest case in the introduction: saving just a few bits of game information. The question is not really how to get at the data, which is easy to just “pluck” out of the game manually in the save() method. The question is: where to save the data? With Phaser.js, and essentially any HTML5/JS/Canvas/WebGL game, you basically have two viable options: 1) in localStorage or 2) on a centralized server.

localStorage is where we’ll start. The Botanist will eventually save to a centralized server so you can hop between your laptop and your phone, for instance, but we have to start somewhere and I’m leaving the backend out of this for now.

Be aware of the limitations of localStorage:

1) localStorage can only store strings, so you have to JSON.stringify() everything before storage and JSON.parse() when you read it back.

2) localStorage is a key/value store only. It is possible but annoying to run very simple queries against data you have in localStorage, if it’s planned for and structured in advance. It is not feasible to run complex queries against your data. (I mean, you can, but do it as an experiment, not in a game.)

3) There’s a 10MB storage limit in localStorage before the user is asked to allot more storage space to your app

4) It persists only to the browser - deleting “site data” (sometimes lumped in with “deleting cookies”) will delete the save, and you’ll not be able to access your save from a different computer

The upside is that localStorage is supported everywhere that canvas is, it’s super simple to use, and we’re ok with all those limitations (for now…)

The localStorage API is dead simple:

// save an item
localStorage.setItem("item-key", "item data");
// retrieve an item
localStorage.getItem("item-key");
// remove an item
localStorage.removeItem("item-key");
// remove all items
localStorage.clear();

In the most simple case, you could do the following:

localStorage.setItem("reached-level", game.highestLevelReached);
localStorage.setItem("high-score", game.highScore);

We’ll try to save everything under one key, though, so we’ll build an object :

// package everything you need into an object.
var saveObject = {
	level: 5,
	highScore: 3742
};

// localStorage only works with strings, so JSON.stringify first.
localStorage.setItem("save", JSON.stringify(saveObject));

Saving a player

Let’s move on to the second case above: saving a player. I wish we could just JSON.stringify the player object, but it’s so complex and has so many internally used variables, objects, methods, and circular references that this is impossible.

Instead I’ve structured my player class - in fact, my entire character base class, which will come in handy later - to be “easy to serialize”. I’ve organized the custom properties, options, and methods that define each character’s behavior into a handful of different semantic groups. I’ve made sure that there are no circular references, and that any callback methods are specified by name rather than a closure. (The callback names are resolved at runtime so there’s no fuss with serialization).

This makes it easy, trivial almost, to define simple and sensible serialize and unserialize methods. Serialize just builds an object from a handful of keys, and unserialize restores them.

Character.prototype.serialize = function(savePosition) {
	if (savePosition === undefined) savePosition = true;
	var fields = [
		'speech',
		'battle',
		'health',
		'motion',
		'target',
		'quests',
		'items',
		'options',
		'log'
	];

	if (savePosition) {
		fields.push('x');
		fields.push('y');
	}

	var obj = {};

	for (var i in fields) {
		var field = fields[i];
		obj[field] = this[field];
	}

	return JSON.stringify(obj);
};

/**
 * This is a class method, not an instance method!
 *
 * @param state string | object the state to unserialize into a character
 *
 * @return Character instance, class depending on the state restored
 */
Character.Unserialize = function(state) {
	// We should be able to accept an object or a string.
	if (typeof state === 'string') {
		state = JSON.parse(state);
	}

	// Default class name
	var className = 'Character';

	// Class name can be specified in the serialized data.
	if (state.options.className) {
		className = state.options.className;
	}

	// Call our character factory to make a new instance of className
	var instance = Character.Factory(
		className,
		game, // Game reference. Required
		0, // x-pos. Required, but overridden by unserialize
		0, // y-pos. Required, but overridden by unserialize
		{} // options. Required, but overridden by unserialize
	);

	// Copy our saved state into the new object
	for (var i in state) {
		instance[i] = state[i];
	}

	return instance;
};

Now your game’s save method can simply store the serialized player, and unserialize the player on load. You can also add any other cherry picked game-level keys (like high score, or unlocked levels) to the object you’re saving, like so:

Phaser.Game.prototype.serialize = function() {
	var saveObject = {};
	saveObject.player = this.player.serialize();
	saveObject.highScore = this.highScore;
	saveObject.level = this.level;
	return JSON.stringify(saveObject);
};

Note that if you only have one Character or Player class, you don’t need to deal with className or a factory pattern. But some design decisions result in multiple child classes that need to be dealt with.

In The Botanist’s case, we use different classes for different character types. We do this so that different character types can have different scripted behavior, but all still inherit from the same base character class that does things like manage inventory or do battle.

The Botanist therefore uses a “Factory” pattern. A Factory is simply a helper method that allows you to create a class from its class name stored in string form. You can accomplish this quite simply:

var className = 'StandStillNPC';
var Class = window[className];
var instance = new Class();
return instance;

But, of course, you’ll generally want to add some extra logic around validation and initialization.

This Factory method makes it easy for us to serialize lots of different types of NPCs and enemies. We need nothing more than the class name and the stored properties to fully unserialize any of our various character types.

Saving the entire game

What I’ve written so far covers probably 90% of app store games, since most games are built narcissistically around the player or main character - it makes sense that the information we need to save is almost entirely in the player object.

But some games need to save state for more than just the player. They need to save state for all the NPCs, all the maps and dungeons, all the items in the game. This is our third scenario in the introduction, and the case for The Botanist.

The first step is to identify which types of data need to be saved. Phrased differently: decide which classes will need serialize and unserialize methods. In The Botanist, the following classes have serialize methods:

  • Character (parent class of both Player, Enemy, and NPC alike)
  • Item
  • Stage (or “map”, which manages the NPCs, Enemies, and Items in its game area)
  • Game (which, of course, stores all of the above)

Serializing a Stage is almost as easy as serializing a character. We look through the important properties taking copies of them, with the consideration that arrays of NPCs and items need to be iterated through in order to have their members individually serialized. A one-line map call takes care of this, otherwise Stage’s serialize method looks the same as Character’s. (We could abstract this behavior into, say, a Collection class, or extend Phaser’s Group class to add a serialize method, but I’m lazy.)

// serialize each of the NPCs and Items in turn
saveObject.npcs = this.npcs.map(function(npc) { return npc.serialize(); });
saveObject.items = this.items.map(function(item) { return item.serialize(); });

We can also avoid nested-string-hell by JSON.parseing each item. Above, saveObject.npcs will be an array of strings, not objects, and then Stage will get serialized as a string itself and stored in Game’s string form, so using the above will work but result in a lot of backslashes in the final form. A simple mod fixes this: just parse each item after serializing it. Sure, we’re stringifying and then parsing extraneously, but it keeps our output clean and it also gives us objects by value rather than objects by reference (the poor man’s “clone object” in JavaScript).

// serialize each of the NPCs and Items in turn
saveObject.npcs = this.npcs.map(function(npc) { return JSON.parse(npc.serialize()); });
saveObject.items = this.items.map(function(item) { return JSON.parse(item.serialize()); });

Now that everything’s an object, rather than nested strings, the one JSON.stringify call at the Game level will collapse the whole thing down into one piece of JSON. This is a tiny nit-picky optimization and doesn’t really matter, but I like it.

Our Item class is easier to serialize (rather, unserialize) than Characters and Stages as we only have one item class and don’t need to bother with a factory pattern or storing the class name. The Item class’ serialize method therefore looks exactly the same as Character’s (with different properties obviously), and the Unserialize class method for Item is simpler in that we don’t call Character.Factory or Item.Factory but rather, simply new Item.

Finally, the Game class’ save and load methods orchestrate the whole thing. (PSA: It’s a good idea to “namespace” the data you’re committing to localStorage, just in case you ever have to embed your game in a third party site who may also use localStorage. Just prefix all the localStorage item keys you use with your app’s name.)

The Game class also has serialize and unserialize methods. The difference between serialize and save is an important semantic difference: “serialize” means “turn into a string representation” whereas “save” means “actually do the saving”. I won’t show you serialize here because by now you should know how to write that method for the Game class – it’s the same as the above. But here’s save and load:

/**
 * Save the game state.
 *
 * If no key is given, will save as "save-default"
 * If a key is give, will prepend with "save-"
 */
Phaser.Game.prototype.save = function(key) {
	if (key === undefined) key = 'default';
	localStorage.setItem('save-'+key, this.serialize());
};

/**
 * Load a game state.
 *
 * If no key is given, will load "save-default"
 * If a key is give, will prepend with "save-"
 */
Phaser.Game.prototype.loadSave = function(key) {
	if (key === undefined) key = 'default';
	var state = localStorage.getItem('save-'+key);
	if (state) {
		this.unserialize(state);
	}
};

You can see above that the save and load methods take a save name, that defaults to “default”. This approach lets users play different saves in the future, but also gives us a sensible default behavior.

And we get a bonus: autosave! It’s just save on an interval, with the save key “autosave”. Loading an autosave is accomplished by running game.loadSave('autosave').

Phaser.Game.prototype.autosaveOn = function(interval) {
	if (interval === undefined) interval = 5000;
	var self = this;
	this.autosaveTimer = setInterval(function() {
		console.log("Autosaving...");
		self.save('autosave');
	}, interval);
};

Phaser.Game.prototype.autosaveOff = function() {
	if (this.autosaveTimer) {
		clearInterval(this.autosaveTimer);
		this.autosaveTimer = null;
	}
};

Finally, if you have a game even bigger than this that requires saving many more things: you can create a base “Serializable” class that reads from the instance’s “serializeFields” property. The class’ serialize method can then recursively iterate through all its children, automatically serializing any child object that has a serialize method. Use this approach in only the most dire of situations.

Happy saving!