BlogGames

Automatically generate sprite sheet atlas files for your Phaser JS game with a Node.js script

Written in June 22, 2021 - 🕒 2 min. read

In one of my first game devlogs, I explained how to automate the sprite sheet generation for Phaser 3 using Webpack, but I think it would be more helpful if I had an external script to do that, so anyone can use it.

But why not simply use those websites that mesh together PNG files into a sprite sheet? Well because Phaser not only needs the PNG sprite sheet, but also an atlas JSON file with all the sprite sheet details like frame name and dimensions.

Assets by kenney.nl
Assets by kenney.nl

For this I’m going to use the free-tex-packer-core package made by odrick. The basic usage in its documentation looks like the following:

const { packAsync } = require('free-tex-packer-core');

const images = [
    { path: "img1.png", contents: fs.readFileSync("./img1.png") },
    { path: "img2.png", contents: fs.readFileSync("./img2.png") },
    { path: "img3.png", contents: fs.readFileSync("./img3.png") }
];

const options = { allowRotation: false };

async function packImages() {
    try {
        const files = await packAsync(images, options);
        for (let item of files) {
            console.log(item.name, item.buffer);
        }
    } catch (error) {
        console.log(error);
    }
}

That by itself is already super helpful, but I want to take the extra step and be able to generate sprite sheets for all my sprites, and the way I have my files and folder is:

- original-files
  - sprites
    - hero
      - hero_walking_1.png
      - hero_walking_2.png
      - hero_walking_3.png
      - hero_jumping_1.png
      - hero_jumping_2.png
    - enemy
      - enemy_walking_1.png
      - enemy_walking_2.png

With a directories structure like the one above, I can have the following script that will loop through all directories in my sprite directory and get all the images inside each one of them:

const { packAsync } = require('free-tex-packer-core');
const path = require('path');
const { readFileSync, readdirSync, writeFileSync } = require('fs');

const SOURCE_SPRITES_PATH = path.resolve(__dirname, 'original-files', 'sprites');
const SPRITES_PATH = path.resolve(__dirname, 'assets', 'sprites');

async function generateAtlasFiles(assetName) {
    let spritesFolders;

    // use the parameter or get all folders from a directory
    if (assetName) {
        spritesFolders = [assetName];
    } else {
        spritesFolders = readdirSync(SOURCE_SPRITES_PATH);
    }

    for (const spritesFolder of spritesFolders) {
        const spritesFiles = readdirSync(path.resolve(__dirname, SOURCE_SPRITES_PATH, spritesFolder));
        const images = [];
        spritesFiles.forEach((spritesFile) => {
            images.push({
                path: spritesFile,
                contents: readFileSync(
                    path.resolve(__dirname, SOURCE_SPRITES_PATH, spritesFolder, spritesFile)
                ),
            });
        });

        await packImages(images, spritesFolder);
    }
}

async function packImages(images, spriteName) {
    try {
        const files = await packAsync(images, { allowRotation: false });

        // let's save the png and json files
        for (const item of files) {
            // get the file extension
            const fileExt = item.name.split('.').pop();
            // save the file in the sprites directory
            await writeFileSync(
                path.resolve(__dirname, SPRITES_PATH, `${spriteName}.${fileExt}`),
                item.buffer
            );
        }
    } catch (error) {
        console.log(error);
    }
}

// process.argv[2] is the argument from the command when you run `node script.js arg`
generateAtlasFiles(process.argv[2]);

This script will create the files my-sprite.png and my-sprite.json for each of your sprites in your sprite folder when running node generate-sprites.js, or node generate-sprites.js hero to generate sprites sheets for only one sprite, in this case, the hero sprite.

I hope this blog post was useful for you, have a good one!

Tags:


Post a comment

Comments

No comments yet.