dot big bang

Developer API
Menu
All
  • Public
  • Public/Protected
  • All

Saving your player's data so they can return to the game later and pick up where they left off is a great way to keep people playing your game! dot big bang currently allows you to persist data per player per game. This means that each of your games has a different set of save data for each player, or from a players perspective, they have a different save data per game they play.

This save data is an object associated with the game and player that can be filled with anything as long as it is JSON-serializable. Check out Persistence and Persistence.SaveData for more information.

You can also check out {@page Save Data} for a ready made save data system that we use in our game Doodle Safari.

Contents

  1. Saving Data
  2. Loading Data
  3. Handling Multiple Systems Saving

Saving Data

Saving data is as simple as calling the SaveData.write() method and passing an object through.

We'll go through some examples of how to do this and what sort of responses you can get from doing it.

Example 1 - A simple save call

start() {
    // Create an object to save.
    const myData = {
        name: "Bob",
        level: 1,
    }

    // Save it.
    this.game.saveData.write(myData);
}

Example 2 - A save call with a response

Next we'll save the data like we did above, but as we do so we'll hang onto the write id so we can check out the status of it afterwards.

private _writeId: number = 0;

start() {
    // Create an object to save.
    const myData = {
        name: "Bob",
        level: 1,
    }

    // Save it.
    this._writeId = this.game.saveData.write(myData);
}

// Successful write() calls will end up calling this.
onSaveDataSynced(writeId: number, saveDataInstance: SaveData }) {
    if (writeId == this._writeId) {
        console.log("Our save was successful!");
    }
}

// Failed write() calls will end up calling this.
onSaveDataSyncFailed(error: Error, writeId: number, saveDataInstance: SaveData) {
    if (writeId == this._writeId) {
        console.log(`Our save failed with the error: ${error}`);
    }
}

Loading Data

Loading data is also quite simple. Just call the SaveData.read() method and you will either get the previously saved object or null if nothing has been saved.

Example 3 - A simple read call

start() {
    // Load the saved data.
    const myData = this.game.saveData.read();

    // If you ran the previous examples you should have some saved data.
    if (myData) {
        console.log(`Loaded name: ${myData.name} and level: ${myData.level}`);
    }
    else {
        console.log("No save data exists yet!");
    }
}

Handling Multiple Systems Saving

In the examples above, each time SaveData.write() was called the entire save data was overwritten with the object passed in.

This could be very bad if you have multiple different systems each saving their own data. You don't want one system to overwrite the other.

Example 4 - Splitting data into buckets

To handle a case like this you should split your save data into a bucket per system and only let each system write over it's own bucket.

This example assumes the systems are searate scrips calling the save() method with their own system ID and data. Check out Cross Script Communication to see how that works. I also included a load() method to complete the example.

private _saveData: any;

start() {
    // Load all of the save data.
    this._saveData = this.game.saveData.read();
}

// Called by other scripts.
load(systemId: string): any {
    // Return the system's data or an empty object if it doesn't have any yet.
    return this._saveData[systemId] ?? {};
}

// Called by other scripts.
save(systemId: string, data: any) {
    // Overwrite or create the data for that system.
    this._saveData[systemId] = data;

    // Write all of the save data.
    this.game.saveData.write(this._saveData);
}

Example 5 - Reducing overhead

In the above example we save every time a system calls the save() method. This could result in a lot of calls and could be spread out over time instead.

With this code we could have any amount of save calls within a window of time and it would still only call SaveData.write() once per interval.

// Save every n seconds.
saveInterval = 5;

private _saveData: any;
private _saveQueued = false;
private _saveSecondsRemaining = 0;

start() {
    // Load all of the save data.
    this._saveData = this.game.saveData.read();

    // Start the save interval.
    this._saveSecondsRemaining = this.saveInterval;
}

tick() {
    // Decrement the save seconds remaining.
    this._saveSecondsRemaining -= this.game.frameDeltaTime;

    // Save enough time has passed and one has been queued.
    if (this._saveQueued && this._saveSecondsRemaining <= 0) {
        // Write all of the save data.
        this.game.saveData.write(this._saveData);

        // Reset the interval.
        this._saveSecondsRemaining = this.saveInterval;

        // Clear the queued flag.
        this._saveQueued = false;
    }
}

// Called by other scripts.
load(systemId: string): any {
    // Return the system's data or an empty object if it doesn't have any yet.
    return this._saveData[systemId] ?? {};
}

// Called by other scripts.
save(systemId: string, data: any) {
    // Overwrite or create the data for that system.
    this._saveData[systemId] = data;

    // Queue a save.
    this._saveQueued = true;
}