coding

Loading user-created Tiled stages on Phaser JS - Skate Platformer Game Devlog #11

Written in November 11, 2020 - 🕒 3 min. read

Hey guys, in today’s devlog I’m going to make something really cool - dynamically upload external Tiled map files created by the user.

First, let’s all get on the same page on how Phaser works. Phaser runs in an HTML canvas element that is attached to whichever HTML element ID you put on Phaser’s parent settings option, so if I want a browser-related action like uploading a file using the HTML file-input element, I have to manually create that element and trigger it when needed.

const fileInput = new HtmlFileInput({
    scene: this,
    spriteKey: 'file-input',
});

const callBackFunction = () => {
    // trigget the HTML file input click
    fileInput.fileInput.click();
}

But what the hell is this HtmlFileInput? It is basically a class that in its constructor I dynamically create an HTML input element and attach it to the page’s DOM. The styling part is there to make sure the element is not visible to the user.

class HtmlFileInput {
    constructor({
        scene,
        spriteKey,
    }) {
        // Phaser's parent HTML element
        const mainElement = scene.sys.game.canvas.parentElement;

        // create html input type
        const fileInput = document.createElement('input');
        fileInput.setAttribute('type', 'file');
        fileInput.setAttribute('accept', '.json');
        fileInput.setAttribute('id', spriteKey);

        // TODO this stylings
        fileInput.style.position = 'absolute';
        fileInput.style.top = 0;
        fileInput.style.display = 'block';
        fileInput.style.marginTop = '-400px';

        mainElement.appendChild(fileInput);
        this.setFileInput(fileInput);
        this.setScene(scene);
    }

    setFileInput = (fileInput) => {
        this.fileInput = fileInput;
    }

    setScene = (scene) => {
        this.scene = scene;
    }
}

export default HtmlFileInput;

I know this feels like a hack, but this is the proper way of doing it on Phaser 3, just don forget to remove the element from the DOM when you’re done with it by calling fileInput.fileInput.remove().

Now I need a way to parse the .json file uploaded by the user and make it available on Phaser’s “cached file manager”, and the best way to do that is by using Phaser’s preload function. First, let’s take a look at how the actual callback function works.

const callBackFunction = (data) => {
    fileInput.textfield.onchange = (e) => {
        const reader = new FileReader();
        reader.addEventListener('load', (event) => {
            const result = JSON.parse(reader.result);
            this.scene.start('CustomStageLoadingScene', {
                stageKey: 'custom_stage',
                stageData: result,
                nextScene: 'GameScene',
            });
        });
        reader.readAsText(e.target.files[0]);
    };

    fileInput.textfield.click();
}

As you can see, I am using FileReader to load the file and passing it as a parameter to a new scene called CustomStageLoadingScene that only exists as an aux scene to dynamically load the phase sent by the user.

class CustomStageLoadingScene extends Scene {
    constructor() {
        super('CustomStageLoadingScene');
    }

    init(data) {
        // data sent as a second parameter when calling the scene
        // will be available here
        const { stageKey, stageData, nextScene } = data;
        this.stageKey = stageKey;
        this.stageData = stageData;
        this.nextScene = nextScene;
    }

    preload() {
        const { stageKey, stageData } = this;
        // make the JSON data available to Phaser's internal cache
        this.load.tilemapTiledJSON(stageKey, stageData);
    }

    create() {
        const { stageKey, nextScene } = this;
        // start the next scene
        this.scene.start(nextScene, { stageKey, stageName: 'custom' });
    }
}

Now that I have the data set with this.load.tilemapTiledJSON(), I can create the new stage with scene.make.tilemap({ key: stageKey }). Pretty neat, right?

And that’s all for today, please don’t forget to like and subscribe to my channel, and if these devlogs are of any use to you, let me know in the comments below. Thanks and until next time.

Tags:


Post a comment

Comments

No comments yet.