Importando arquivo de tileset externo no Phaser JS com Webpack - Skate Platformer Game Devlog #07

Escrito em October 8, 2020 - Jogos

Super Ollie vs Pebble Corp.

No Game Devlog de hoje, vou mostrar como importar tilesets externos no Phaser JS, mas primeiro vamos tirar isso do caminho: Esqueça o nome "Skate Platformer", o nome do nosso jogo vai ser Super Ollie vs Pebble Corp. A gente não planejava dar um nome ao jogo agora, mas a ideia simplesmente veio nas nossas cabeças e achamos que o nome é perfeito. Deixa eu te perguntar uma coisa, qual é o pior inimigo do skatista? Indiscutivelmente essas pequenas pedrinhas, certo? E se no futuro houver uma empresa do mal que quer proibir o skate em todos os lugares, como essa empresa se chamaria? Pebble Corp, é claro. Pebble é o nome em inglês daquelas pedrinhas que geralmente você encontra em praias e tal. Agora com isso fora do caminho, vamos continuar com o post.

Importando tilesets externos

Tiled é uma ferramenta open source poderosa para construir mapas 2D suportados por muitas engines de jogos diferentes, incluindo Phaser JS, que é o framework que eu estou usando para esse jogo.

Há um pequeno problema, ao carregar mapas do Tiled no Phaser JS, o tileset precisa estar “embutido” no mapa, caso contrário, o Phaser JS não consegue carregá-lo. Isso é muito frustrante porque sempre que eu faço alterações em um tileset, eu tenho que ir em todos os meus arquivos de mapa e “reembutir” o tileset, isso acaba demorando muito se o projeto tiver muitos mapas e tilesets diferentes.

Embutir Tileset
Embutindo um tileset no Tiled

Para resolver este problema, decidi encontrar uma solução para embutir automaticamente os tilesets com o Webpack. Para fazer isso, comparei o arquivo JSON do mesmo mapa antes e depois de embutir um tileset, e ficou assim:

Tileset embutido vs não embutido
Tileset embutido vs não embutido

Como você pode ver, em vez de ter a propriedade source apontando para um arquivo externo, tipo "source": "../tilesets/test_tileset.json", ele contém todo o conteúdo desse arquivo mais um atributo firstgid, então tudo o que eu preciso fazer é percorrer todos os mapas do Tiled, ler a propriedade tilesets, carregar cada arquivo tileset e colocar o seu conteúdo em uma nova array na propriedade tilesets.

Foi assim que eu fiz:

const CopyWebpackPlugin = require('copy-webpack-plugin');
const { promises: fs } = require('fs');
const stageFiles = await fs.readdir(STAGES_PATH);
// get only the JSON files
const stageJsons = stageFiles
    .filter((stage) => stage.split('.')[1] === 'json');

const copyWebpackPluginPatterns = [];
stageJsons.forEach((stage) => {
    copyWebpackPluginPatterns.push({
        from: `${STAGES_PATH}/${stage}`,
        to: '../assets/stages',
        transform: (fileBuffer, elPath) => {
            const manifestString = fileBuffer.toString();
            const stageData = JSON.parse(manifestString);
            const newTilesets = stageData.tilesets.map((tileset) => {
                // firstgid is an important id reference for Tiled
                const { firstgid } = tileset;
                if (!tileset.source) {
                    // if there's no source, it means the Tileset
                    // is already embedded
                    return tileset;
                }

                // on my setup, stages are in root/stages/
                // and the tilesets are in root/tilesets/
                // so we need to remove "../tilesets/" from the path
                // and load it based on the root dir of the tilesets
                const filePath = tileset.source.replace('../tilesets/', '');
                // eslint-disable-next-line import/no-dynamic-require
                const tilesetData = require(`${TILESETS_PATH}/${filePath}`);
                if (!tilesetData) {
                    console.error('Could not find file', `${TILESETS_PATH}/${filePath}`);
                    return tileset;
                }

                return {
                    ...tilesetData,
                    firstgid,
                };
            });
            stageData.tilesets = newTilesets;

            return Buffer.from(JSON.stringify(stageData));
        },
    });
});

module.exports = {
    plugins: [
        new CopyWebpackPlugin({
            patterns: [
                ...copyWebpackPluginPatterns,
            ],
        }),
    ],
};

Como você pode ver, precisamos do plugin copy-webpack-plugin para fazê-lo funcionar, copiando todos os mapas do diretório assets/stages para o diretório dist/assets/stages, e ao copiá-lo, definimos uma função transform que carregará os tilesets externos e os embutirá nos mapas Tiled. Adicione este código ao seu arquivo de configuração do Webpack se quiser obter o mesmo resultado.

Criando a tela inicial

Isso foi realmente muito fácil e direto, especialmente porque é praticamente o mesmo código do Game Devlog anterior.

Primeiro eu carrego o mapa na função create:

const map = scene.make.tilemap({ key: 'title_screen' });
const tileset = map.addTilesetImage('title_screen', 'city_tileset');
const { width, height } = this.cameras.main;

const background = map.createDynamicLayer('background', tileset, 0, 0);
const ground = map.createDynamicLayer('ground', tileset, 0, 0);
const foreground = map.createDynamicLayer('foreground', tileset, 0, 0);

[background, ground, foreground]
    .forEach((dynamicLayer) => {
        dynamicLayer.setY(
            height - background.height
        );

        // set this property to make the the layers move in different speeds
        dynamicLayer.setScrollFactor(dynamicLayer.parallaxSpeedX);
    });

// we're going to use that in the update function
this.parallaxCounter = 0;

Agora tudo o que fiz foi mover a câmera até a posição y = 640 e, quando ela chegar lá, mudar a posição de volta paray = 0, e repetir isso indefinidamente.

this.parallaxCounter += 1;
if (this.parallaxCounter === 640) {
    this.parallaxCounter = 0;
}

this.cameras.main.scrollX = this.parallaxCounter;

E por hoje é isso! Espero que tenha gostado, se quiser, deixe um comentário e considere se inscrever no meu canal no YouTube.

Tags:


Publicar um comentário

Comentários

Nenhum comentário.