View Profile 3p0ch

n/a, Male

Location not disclosed

Joined on 2/13/10

Exp Points:
652 / 710
Exp Rank:
Vote Power:
5.04 votes
Town Watch
Global Rank:
B/P Bonus:

Saving with IndexedDB in Phaser

Posted by 3p0ch - 10 days ago

This post pertains to Phaser 3; I haven't tried this with Phaser 2.

While working on the Phaser Game Jam, I discovered that if you use localStorage to save game data on NewGrounds then bad things can happen. Every game on NewGrounds saves to the same space in the player's computer's memory because all the games come from the same host site. So if one game saves a key/value pair of "MaxHP" = 5279 because they have FF7 style hit points and the player goes and plays another game that sets "MaxHP" = 11 because it's old school D&D, then the second game will overwrite the first game's MaxHP of 5279 and leave the player with 11 HP. I've tested that out and confirmed that two different games can read each others' values set in localStorage.

You could work around this by making your keys in localStorage all start with your game number (if you view your game in preview after it's uploaded to NewGrounds and look for the number at the end of the URL) so no other games are likely to overwrite your values. But, there still exists a function called localStorage.clear() that a well-meaning but unaware programmer might put in their game to let the player do what they think will just be reset the save data for their own game, and that scares me.

Using IndexedDB is I think a better solution because it allows you to keep all of your game's data in its own area in the IndexedDB structure on the player's computer and avoids these issues. But I haven't seen any good instructions online describing how to implement IndexedDB saving in a Phaser game. I admit that I'm a hack who just started learning JavaScript when the Jam started and am no pro, but I still managed to get an implementation of IndexedDB saving working in my game and am sharing it because using localStorage for NewGrounds saves seems like an innocuous looking thing that underneath the surface is unacceptably prone to badness.

If you don't want to read all the TL,DR stuff, then just put this into your index.html file just after starting JavaScript and you'll be able to store data you want to save in the saveData object and save it by calling writeSaveData() at appropriate points in your game; the data will be loaded whenever the player starts the game.

// The number below should be your game's number on NewGrounds
// But if people forget to change it, I'ma have u spam 12345
var idbRequest = indexedDB.open("12345");
var idb = null;
var saveData;
idbRequest.onupgradeneeded = newIdb;
idbRequest.onsuccess       = openIdb;
function newIdb() {
function openIdb() {
  idb = idbRequest.result;
  idb.transaction("saveData").objectStore("saveData").get(1).onsuccess = function(event) {
    saveData = event.target.result;
    if (saveData == null) {saveData = new Object();}
function writeSaveData() {
  if (idb != null) {
    idb.transaction("saveData", "readwrite").objectStore("saveData").put(saveData, 1);

Be aware that if you try to access a property of saveData that hasn't been saved to yet (for example if you're lazy like I was and only wrote values to a LevelNumber property after the player finished the level without bothering to initialize them to zero or smth when the game starts) then you'll get an error, but you can workaround that by looking for values with statements like

completion[1] = saveData.FirstLevel || null;

instead of just

completion[1] = saveData.FirstLevel;

If you want to write to an object property based on the value of a variable and don't know how to refer to saveData.variableName, it's done by saying saveData[variableName]

If you want to read all the TL,DR stuff on IndexedDB then look here, here, and even here just for good measure. The most important thing to be aware of in case you try to make a bunch of fancy modifications to what I'm showing is that IndexedDB acts asynchronously, so if you're used to programs that just run straight through and/or think that you'll be able to insert a

while(stillLoading) { } // Not that a genius like myself would ever actually try that and not like the outcome :)

statement, then consider yourself warned to read up on asynchronous processes. In particular, don't try to save data right when the game loads -- put the code above at the very beginning of the JavaScript in index.html and wait until all the preloading stuff is done before you try to write saveData to the player's computer and you should be fine.

To be on the safe side, don't call writeSaveData() consecutively without leaving a little bit of time for the write to happen before trying to write again. There are probably more elegant ways of handling IndexedDB's asynchronous processing, but I don't know them and probably won't learn them in enough time to be useful for this Game Jam.

Finally, in case you have a Unity background and have noticed that using localStorage with Phaser doesn't have the same issues as Unity's implementation of playerPrefs when you update your game: the problem with updating is not inherent to IndexedDB and is something specific to Unity, and the implementation I'm showing here for Phaser doesn't have the same problem of wiping out save data if you update, so you can safely update your game without wiping out everyone's save data. I'ma post a solution to the problem Unity has in not too long (I hope) but I got distracted by the Phaser Game Jam.