coding

Falling off platforms with Arcade Physics on Phaser JS - Skate Platformer Game Devlog #06

Written in October 1, 2020 - 🕒 3 min. read

Hey guys! Today’s Game Devlog is a bit long, 15 minutes, but bare with me because there are a lot of interesting things going on in the game.

Stage Selection

To facilitate testing new stages, I decided to add a basic stage selection for the game, so I could test multiple stages in the same build instead of having to change a single file every time. Now whenever I add a new stage file to the stages folder, that stage will automatically be included in the game bundle, thanks to some custom Webpack settings.

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 GAME_STAGES = JSON.stringify(
    stageJsons.map((stage) => {
        // eslint-disable-next-line import/no-dynamic-require
        const stageData = require(`${STAGES_PATH}/${stage}`);
        const stageName = getStageNameFromMap(stageData);

        return {
            stageName,
            stageKey: stage.split('.')[0],
        };
    })
);

module.exports = {
    plugins: [
        new webpack.DefinePlugin({
            GAME_STAGES,
        }),
    ],
};

Then in the stage selection scene, you can access the stages via the GAME_STAGES constant, so I loop through the stages data and create the stage selector game object.

let posX = 20;
let posY = 50;
GAME_STAGES.forEach((stageData, index) => {
    const { stageKey, stageName } = stageData;
    const stageSelector = new StageSelector({
        scene: this,
        x: posX,
        y: posY,
        stageKey,
        stageName,
        onClickCallback: (data) => {
            this.scene.start('GameScene', data);
        },
    });

    this.add.text(
        posX + 8,
        posY + 8,
        `${index + 1}`.padStart(2, '0')
    ).setDepth(50);

    this.add.existing(stageSelector);
    stageSelectors.add(stageSelector);

    posX += 52;
    if ((index + 1) % 7 === 0) {
        posY += 52;
        posX = 20;
    }
});

Parallax Effect

The game wouldn’t be complete without a nice parallax effect, and to achieve that I decided to add some custom properties to the layers on Tiled, and read them in my scene’s update function to make the layer current position X or Y move accordingly with the camera movement.

update(time, delta) {
    const { dynamicLayers } = this.mapData;
    Object.values(dynamicLayers).forEach((dynamicLayer) => {
        dynamicLayer.layer.properties.forEach((property) => {
            const { name, value } = property;
            if (value !== 1) {
                if (name === 'parallaxSpeedX') {
                    dynamicLayer.setX(
                        this.cameras.main.scrollX * value
                    );
                } else if (name === 'parallaxSpeedY') {
                    dynamicLayer.setY(
                        this.cameras.main.scrollY * value
                    );
                }
            }
        });
    });
}

Note: After writing this post, I found out about the setScrollFactor function, so I can use that instead, which takes way less processing power than looping in the update function.

Falling Off Platforms

To make this I did a bit of researching, and the way it’s done is a bit hacky, but it works for now, so I decided to just roll with it.

First I check if the hero is in the ground, if there is an element the hero from below and if the down button is pressed. If so, then we disable the player bottom collision for 150 milliseconds.

// Handle hero going down through element
if (
    this.isHeroOnGround()
    && this.isDownJustDown()
    && this.touchingDownObject
) {
    this.body.checkCollision.down = false;
    this.scene.time.delayedCall(
        150,
        () => {
            this.body.checkCollision.down = true;
        }
    );
}

This is pretty straight forward, but how can we access the this.touchingDownObject attribute? Well that’s the biggest hacky part of this solution. First, we need to add a collider between the hero and the map layer with a callback that will check some stuff and set the this.touchingDownObject attribute for the hero.

this.physics.add.collider(
    dynamicLayers.ground,
    this.hero,
    (objectA, objectB) => {
        const [hero, element] = this.getHeroAndObject(objectA, objectB);

        if (
            hero.isHeroOnGround()
            && element.properties.collidesUp
            && element.properties.canFallThrough
        ) {
            hero.touchingDownObject = element; // TODO?
        } else {
            hero.touchingDownObject = null;
        }
    }
);

That’s it! I hope you guys enjoyed it and don’t forget to leave your comment if you liked this post.

Tags:


Post a comment

Comments

No comments yet.