00:00
00:00
3p0ch
If you like hard games try my Daxolissian System series

plasmid @3p0ch

Cat

Scientist

Read the manual & try stuff

So Cal

Joined on 2/13/10

Level:
13
Exp Points:
1,650 / 1,880
Exp Rank:
38,330
Vote Power:
5.48 votes
Audio Scouts
1
Rank:
Portal Security
Global Rank:
23,500
Blams:
50
Saves:
375
B/P Bonus:
8%
Whistle:
Normal
Medals:
4,624
Supporter:
3y 3m 14d

Saving to IndexedDB in HaxeFlixel

Posted by 3p0ch - May 1st, 2020


Two reasons for this post:

1) I might use this later in an effort to pull off a NewGrounds collab for a huge meta-game where all of the games interact with each other, and this would be how HaxeFlixel games would interact with others in the collab.

2) Even if a collab never happens, HaxeFlixel's built-in save functionality uses LocalStorage which is unsafe for reasons that I discussed in my news feed post on Phaser where I described how to make Phaser use IndexedDB instead of LocalStorage. This would also be nice if you wanted to make a game series and let different games in the series know what the player did on other games in the series - just store everything in the same IndexedDB database (you can set the name of the database to use as described later) and the games will be able to read each other. While that should also be doable with LocalStorage, the risk of something bad happening to the data over a relatively long time between installments of a game hosted on a gaming site should be much lower with IndexedDB than LocalStorage.


I'm dropping some code below that you can use as your Main.hx file. This code will make a public static Main.saveData anonymous structure variable that you can modify however you want to keep all your save data (so it would have stuff like saveData.hp, saveData.playerName, saveData.someBigArray, etc) that you can modify from anywhere in your game with Main.saveData.someField = someValue. If you don't want to put all of the save data in one anonymous structure (like if you'll have some crazy terabyte size saves or smth and want to be able to read and write small bits at a time) that's fine too, just declare as many public static variables as you like where it currently has saveData in the list of class variables and also in the new() function where it sets default values. Once you have that set up, you can use the Main.readSave() and Main.writeSave() functions to read or write a variable (by default the saveData structure) to your IndexedDB save.


If you want to do that, follow these steps.

1) Use the code at the very end of this post as your Main.hx file

2) Adjust the saveData structure (just under where the Main class definition starts) to whatever you want, or use a bunch of variables instead of putting everything in saveData if you want

3) Adjust the default values of saveData or the other variables you made (just under the where the new() function starts)

4) Adjust the name for your IndexedDB database just after that... if you're just making a standalone game then I suggest you use the number for your game that shows up in the URL of your browser when you load your game's project page in NewGrounds (but make it a string in HaxeFlixel) so you get a unique identifier

5 only if you made new variables instead of just using saveData) Where it has

      readRequest = readSave("saveData");
      readRequest.onsuccess = function() {
        // If there was no previously saved data then stick with
        // the default values set earlier
        if (readRequest.result != null) {
          saveData = readRequest.result;
        }
        startGame();
      }
      readRequest.onerror = function() {
        startGame();
      }

make a bunch of repeats of that to read each of the variables you want to read, and only call startGame() for the last one. (Don't want to make a whole bunch of variables so much anymore, now do you? >:)

6) To save the current data from anywhere within your game, just use Main.writeSave() which will default to saving the saveData structure, or Main.writeSave(key:String, value) if you want to use your own variables and save them as key / value pairs in IndexedDB.

7) If you want to read from the IndexedDB save data while you're in the middle of your game, do it like this (you can omit "saveData" from the Main.readSave call if you want because it will default to that):

      var readRequest:Request;

      readRequest = Main.readSave("saveData");
      readRequest.onsuccess = function() {
        saveData = readRequest.result;
        // continue the game by opening a new state or smth
      }
      readRequest.onerror = function() {
        // handle the error
      }

Probably what you want to do is either 1) have a FlxState where the player sees a screen that lets them load their game, and the only way they can leave that screen is when the readRequest.onsuccess or readRequest.onerror event kicks in and makes the game go to another FlxState, or 2) just let this Main.hx file load everything when the player starts the game, and never try to read the save data from IndexedDB in the middle of the game.


Ok, that's enough talking, here's the Main.hx code

package;

import js.html.idb.Request;
import js.html.idb.Transaction;
import js.html.idb.ObjectStore;
import js.html.idb.OpenDBRequest;
import flixel.FlxGame;
import openfl.display.Sprite;
import js.Browser;

class Main extends Sprite {
  // Variable(s) for saved game data
  public static var saveData:{
    HP:Int,
    PlayerName:String
  }
  
  // Variables that will be used for IndexedDB operations
  static var idbName:String;
  static var openRequest:OpenDBRequest;
  static var readRequest:Request;
  static var db:Dynamic;
  static var store:ObjectStore;
  static var tx:Transaction;

  public function new() {
    super();

    // Set saved data to default values
    saveData = {"hp" : 1,
                "playerName" : "Yo mama"};

    // Set the name of your IndexedDB database
    idbName = "testDB";

    // Get the IndexedDB database set up
    openRequest = Browser.window.indexedDB.open(idbName);

    // If this is the first time the player ran the game,
    // the database will need to create an object store
    openRequest.onupgradeneeded = function() {
      db = openRequest.result;
      store = db.createObjectStore("thisObjectStore");
    }

    // If the object store exists (either because the game was
    // previously played, or because it was created by the
    // onupgradeneeded function) open the db and
    // read the saved data
    openRequest.onsuccess = function() {
      db = openRequest.result;
      readRequest = readSave("saveData");
      readRequest.onsuccess = function() {
        // If there was no previously saved data then stick with
        // the default values set earlier
        if (readRequest.result != null) {
          saveData = readRequest.result;
        }
        startGame();
      }
      readRequest.onerror = function() {
        startGame();
      }
    }
  }

  /*  Returns a Request from reading the save data
      The parameter is the Key of the value to read
      Defaults to reading saveData

      You should use it in your code like this:
      var readRequest:Request;
      readRequest = Main.readSave("saveData");
      readRequest.onsuccess = function() {
        saveData = readRequest.result;
        // continue the game by opening a new state or smth
      }
      readRequest.onerror = function() {
        // handle the error
      }
  */
  public static function readSave(?saveKey:String = "saveData"):Request {
    var returnValue:Dynamic = null;

    tx = db.transaction("thisObjectStore", "readonly");
    store = tx.objectStore("thisObjectStore");
    return store.get(saveKey);
  }

  /*  Writes data to IndexedDB
      Parameters are the Key (string) and Value (whatever
        data type) to be saved
      Defaults to writing the saveData variable

      This function doesn't bother with returning a Request
      because it shouldn't be necessary in this case
  */
  public static function writeSave(?saveKey:String, ?saveValue:Dynamic) {
    if (saveKey == null) {
      saveKey = "saveData";
      saveValue = Main.saveData;
    }
    tx = db.transaction("thisObjectStore", "readwrite");
    store = tx.objectStore("thisObjectStore");
    store.put(saveValue, saveKey);
  }

  public function startGame() {
    addChild(new FlxGame(0, 0, PlayState));
  }
}

Tags:

Comments

Comments ain't a thing here.