How to save and load multiple entities?

I am trying to make a code for a block based survival game. I have gotten far enough to where the player can place blocks. Here is what I need help with:

How can I get the game to write down each and every block the player places and remember where the player placed, as well as what texture/material the block had? Basically how can I create a save and load function for the player placed blocks?

You could do this by saving the game state in a JavaScript object, and then calling JSON.stringify on that object to generate a string. You can then store the string in the browser’s local storage.

To do the reverse, just check for a saved state in local storage, read it out and call JSON.parse to convert back to a JavaScript object.

Using local storage means the game state gets saved to a particular device. To do a cloud save, you can use something like Google Play Game Services. But that’s a fair bit more advanced. :smile:

I don’t really know how to save the game state. Example script or something?

What is the best way to save and load all of the blocks in a game similar to minecraft basically? As well as allow the player to break and place blocks and have them registered in the game to be saved?

Sorry, wasn’t my response sufficient? Consider a 3x3 Minecraft world (tiny, I know!):

var world = [ 
    [
        [ 0, 0, 0 ],
        [ 0, 0, 0 ],
        [ 0, 0, 0 ]
    ],
    [
        [ 0, 0, 0 ],
        [ 0, 1, 0 ],
        [ 0, 0, 0 ]
    ],
    [
        [ 1, 1, 1 ],
        [ 1, 1, 1 ],
        [ 1, 1, 1 ]
    ]
];

1 could represent a block and 0 a blank space.

To convert your world data structure to a string, do:

var saveData = JSON.stringify(world);

Then save it:

localStorage.setItem('blocks', saveData);

To load it:

var saveData = localStorage.getItem('blocks');

To restore it to you data structure:

world = JSON.parse(saveData);
1 Like

How would at clone a block at each specified position?

Consider a block to be a ‘template’. You can find it, clone it and place it wherever you like. For example:

    // Find the entity we've created in the Hierarchy with the name 'BlockTemplate'
    var blockTemplate = this.app.root.findByName('BlockTemplate');

    // Make a clone of it...
    var blockClone = blockTemplate.clone();

    // Add it to the scene hierarchy
    this.app.root.addChild(blockClone);

    // Set its position to somewhere in the scene
    blockClone.setPosition(x, y, z);

Thanks one last thing:

placedBlocks.push({Type: newBlock.model.material.name, posX: newBlock.getPosition().x, posY: newBlock.getPosition().y, posZ: newBlock.getPosition().z});

placedBlocks is an array storing each and every one of the blocks placed by the player. This is called every time the player places a block. Is there some form of a foreach loop I can use to spawn a block at each position?

In my previous post, I represent the ‘world’ in a 3-dimensional array. You can set block values like this:

world[y][x][z] = blockType;

So once I load my world definition, I could do:

// Find the entity we've created in the Hierarchy with the name 'BlockTemplate'
var blockTemplate = this.app.root.findByName('BlockTemplate');

for (var y = 0; y < world.length; y++) {
    for (var x = 0; x < world[y].length; x++) {
        for (var z = 0; z < world[y][x].length; z++) {
            var blockType = world[y][x][z];

            if (blockType === 1) {
                // Make a clone of it...
                var blockClone = blockTemplate.clone();

                // Add it to the scene hierarchy
                this.app.root.addChild(blockClone);

                // Set its position to somewhere in the scene
                blockClone.setPosition(x, y, z);
            }
        }
    }
}

Ok, now I am facing another error, I found a way to get the player to place blocks and save and load their positions. But now when the player breaks a block something very weird happens:

var placedBlocks = [];
//When player places block:
this.blockClone = app.root.root.findByName("Test Block");
var newBlock = this.blockClone.clone();
newBlock.model.materialAsset = app.assets.get(itemInHand.BlockTexture);
app.root.addChild(newBlock);
placedBlocks.push({Type: newBlock.model.material.name, posX: newBlock.getPosition().x, posY: newBlock.getPosition().y, posZ: newBlock.getPosition().z});
//When player breaks a block:
//result is the side of a block hit by a raycast:
var targetBlock = result.entity.getParent("Test Block");
var mat = targetBlock.model.material.name;
var pos = placedBlocks.indexOf({Type: mat, posX: targetBlock.getPosition().x, posY: targetBlock.getPosition().y, posZ: targetBlock.getPosition().y});
placedBlocks.splice(pos, 1);
console.error(placedBlocks);
targetBlock.destroy();

It destroys the correct block but removes the incorrect value from the placedBlocks array. This causes the blocks to spawn in the wrong positions when the game is loaded. How do I fix this?

A couple of comments:

Why are you passing a string as a parameter to getParent?

var targetBlock = result.entity.getParent("Test Block");

The function takes no parameters.

Secondly, indexOf does a stricly equality compare, so creating a new object and passing that to indexOf will always return -1. See this comment on StackOverflow:

I tried them, didn’t work. I just need to remove the specified block from the array.

ok, i did something. I got the game to save the position of a block using:

//Blocks to be saved:
var setBlocks = []
//When player places blocks:
setBlocks.push(newBlock.getPosition());
//To load blocks:
setBlocks.forEach(function(addBlock) {
    var blockTemplate = app.root.root.findByName('Test Block');
    var newBlockClone = blockTemplate.clone();
    newBlockClone.setPosition(addBlock);
    app.root.addChild(newBlockClone);
});

how do I add the texture of the block to that?

Instead of:

app.root.root.findByName('Test Block');

You probably want:

app.root.findByName('Test Block');

One too many roots! :slight_smile:

How do you add the texture of the block? Well, there are many ways to deal with this. You could define a script attribute per block texture. e.g.:

Blocks.attributes.add('grassTexture', { type: 'asset', assetType: 'texture', array: false }); 
Blocks.attributes.add('rockTexture', { type: 'asset', assetType: 'texture', array: false }); 
Blocks.attributes.add('iceTexture', { type: 'asset', assetType: 'texture', array: false }); 

Maybe you save block type as a number:

var BLOCKTYPE_GRASS = 0;
var BLOCKTYPE_ROCK = 1;
var BLOCKTYPE_ICE = 2;

When loading, you just read the integer denoting the block type and do:

var texture = null;
switch (blockType) {
    case BLOCKTYPE_GRASS:
        texture = this.grassTexture.resource;
        break;
    case BLOCKTYPE_ROCK:
        texture = this.rockTexture.resource;
        break;
    case BLOCKTYPE_ICE:
        texture = this.iceTexture.resource;
        break;
}

// Assuming a block only has one mesh instance here!
var material = newBlockClone.model.meshInstances[0].material;
material.diffuseMap = texture;
material.update();

There are many ways of dealing with this though.