coding

Automatically update the embedded Tileset for all your Tiled maps a Node.js script

Written in October 2, 2021 - 🕒 3 min. read

Creating maps with Tiled is great, and you can use them in pretty much any game engine today, including Phaser JS.

To load a map with Phaser JS is simple, first, you load all the assets in the preload function on your game scene, then you create a new map with this.make.tilemap() and voilà it is done. Check the following code snippet from Ourcade:

preload()
{
    // load the PNG file
    this.load.image('base_tiles', 'assets/base_tiles.png')
    // load the JSON file
    this.load.tilemapTiledJSON('tilemap', 'assets/base_tiles.json')
}

create()
{
    // create the Tilemap
    const map = this.make.tilemap({ key: 'tilemap' })
    // add the tileset image we are using
    const tileset = map.addTilesetImage('standard_tiles', 'base_tiles')
    // create the layers we want in the right order
    map.createStaticLayer('Background', tileset)
}

But sadly it is not all sunshine and rainbows, in order for that to work, you need your Tiled tileset to be embedded into the map.

Embed Tileset
Embedding a tileset on Tiled

The problem with that is when you have many maps, they will each have their own version of the tileset, so you make any changes to one of these, all other maps won’t be aware of it.

I actually already talked about this one this blog one year ago, in my Game Devlog #7, but that solution was to programmatically embed an external tileset into a map, today I will get a tileset that is already embedded in one map and copy it to all my other maps, let’s see how.

The code

My script will receive a parameter, which is going to be the name of the map where the tileset needs to be copied from, so the goal is to run the script as node copy-tileset-data.js nameOfTheMapFile.

First I will import all the needed utils from fs and declare my maps directory with path.resolve:

const { readdirSync, readFileSync, writeFileSync } = require('fs');
const path = require('path');

const TILESETS_PATH = path.resolve(
    __dirname,
    '..',
    'content',
    'assets',
    'sprites',
    'maps'
);

// Call the function with the script parameter
copyTilesetData(process.argv[2]);

async function copyTilesetData(tilesetName = null) {
    // TODO
}

Now for the copyTilesetData function, I will loop through all my maps folders (maps_dir_1, maps_dir_2) and search for the map with the target tileset, when I found it, I will read the file with readFileSync and save it’s tilesets attribute to a local variable to use later.

Then I will make another loop to go to each of the other maps and load the tileset into them and saving it to the same path using writeFileSync.

async function copyTilesetData(tilesetName = null) {
    if (!tilesetName) {
        console.error('No Tileset passed');
        return;
    }

    const allFiles = [];
    let sourceTilesetData = [];
    // eslint-disable-next-line no-restricted-syntax
    for (const tilesetPath of ['maps_dir_1', 'maps_dir_2']) {
        const filePath = path.resolve(TILESETS_PATH, tilesetPath);
        // eslint-disable-next-line no-await-in-loop
        const spritesFiles = readdirSync(filePath);
        // eslint-disable-next-line no-restricted-syntax
        for (const spritesFile of spritesFiles) {
            if (spritesFile === `${tilesetName}.json`) {
                // eslint-disable-next-line no-await-in-loop
                const jsonData = JSON.parse(await readFileSync(path.resolve(filePath, spritesFile)));
                sourceTilesetData = jsonData.tilesets;
            } else {
                allFiles.push(path.resolve(filePath, spritesFile));
            }
        }
    }

    // eslint-disable-next-line no-restricted-syntax
    for (const jsonFile of allFiles) {
        // eslint-disable-next-line no-await-in-loop
        const jsonData = JSON.parse(await readFileSync(
            jsonFile
        ));

        jsonData.tilesets = sourceTilesetData;

        // eslint-disable-next-line no-await-in-loop
        await writeFileSync(
            jsonFile,
            JSON.stringify(jsonData, null, 2)
        );
    }
}

And that’s it! I hope this script helps you the same way it helps me. See you in the next one!

Tags:


Post a comment

Comments

No comments yet.