coding

Como gerar sprite sheets automaticamente para o seu jogo feito com Phaser JS

Escrito em 22 de junho de 2021 - 🕒 3 min. de leitura

Em um de meus primeiros devlogs do jogo Super Ollie, eu expliquei como automatizar a geração de sprite sheets para o Phaser 3 usando Webpack, mas acho que seria mais útil se eu tivesse um script externo para fazer isso, para que qualquer pessoa pudesse usá-lo.

Mas porque não simplesmente usar um daqueles sites que combinam arquivos PNG em um sprite sheet? Bem, porque o Phaser não precisa apenas do sprite sheet em PNG, mas também de um arquivo atlas em JSON com todos os detalhes do sprite sheet, como o nome do frame e as dimensões da imagem.

Sprites feito por kenney.nl
Sprites feito por kenney.nl

Para isso, vou usar o package free-tex-packer-core feito pelo odrick. O uso básico em sua documentação é o seguinte:

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);
    }
}

Isso por si só já é super útil, mas eu quero melhorar isso e gerar sprite sheets para todos os meus sprites, e a estrutura como tenho meus arquivos e pastas é:

- 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

Com essa estrutura de diretórios, eu posso ter o seguinte script que percorrerá todos os diretórios no meu diretório de sprites e pegará as imagens dentro de cada um deles:

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('.').at(-1);
            // 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]);

Este script irá criar os arquivos my-sprite.png e my-sprite.json para cada um de seus sprites em sua pasta de sprites ao executar node generate-sprites.js, ou node generate-sprites.js hero para gerar sprites sheets para apenas um sprite, neste caso o sprite hero.

Espero que esse post tenha sido útil para você, até a próxima.

Tags:


Publicar um comentário

Comentários

Nenhum comentário.