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

Written in October 1, 2020 - Games

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 everytime. 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 a 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.