Do you like my tutorials?

Then consider supporting me on Ko-fi

Talking about Mikey Hooks game, Box2D, Game development, HTML5, Javascript and Phaser.

I really enjoyed Mikey Hooks game, now available only through GameClub, and I also really enjoyed to prototype the hook physics with a lot of languages in my Mikey Hooks tutorial series. After AS3 + Box2D, AS3 + Nape, Phaser 2 + Box2D, Unity + C# and Phaser 3 + Matter, now it’s time to build the hook with Phaser 3 and Box2D powered by Planck.js.

Like in previous parts of the series, it’s just a matter of working with distance hooks.

Look at the prototype:

Click on a box and hold to create a distance joint from the player to the box working like a hook and start swinging by clicking and holding on different boxes.

Let me spend a couple of words about the way it works:

1 – Some random boxes are created

2 – When the player clicks or taps, we check for bodies under the finger/pointer using an AABB (Axis Aligned Bounding Box) query.

3 – If there is a box under the pointer/finger, a distance hook is created between the player and the box. Player body also needs to be awakened.

4 – If the player holds the mouse/finger, we shorten a bit the distance joint and increase a bit the linear velocity of the player, giving the feeling of a swinging rope.

5 – When the player releases the mouse/finger, the joint is destroyed and we wait for another input to start from point 2.

Have a look at the source code, with comments on the lines of code which manage the hook:

let game;
let gameOptions = {
    worldScale: 30
}
window.onload = function() {
    let gameConfig = {
        type: Phaser.AUTO,
        scale: {
            mode: Phaser.Scale.FIT,
            autoCenter: Phaser.Scale.CENTER_BOTH,
            parent: "thegame",
            width: 600,
            height: 600
        },
        scene: playGame
    }
    game = new Phaser.Game(gameConfig);
    window.focus();
}
class playGame extends Phaser.Scene {
    constructor() {
        super("PlayGame");
    }
    create() {
        let gravity = planck.Vec2(0, 3);
        this.world = planck.World(gravity);
        for (let i = 0; i < 5; i ++) {
            this.createBox(Phaser.Math.Between(100, game.config.width - 100), Phaser.Math.Between(100, game.config.height - 200), Phaser.Math.Between(30, 50), Phaser.Math.Between(30, 50), false);
        }
        this.createBox(game.config.width / 2, game.config.height - 10, game.config.width, 20, false);
        this.hero = this.createBox(game.config.width / 2, game.config.height - 20, 20, 20, true);
        this.input.on("pointerdown", this.fireHook, this);
        this.input.on("pointerup", this.releaseHook, this);
        this.jointGraphics = this.add.graphics();
        this.joint = null;
    }
    fireHook(e) {

        // queryAABB scans all world bodies to check if they overlap the AABB rectangle
        this.world.queryAABB(planck.AABB(planck.Vec2(this.toWorldScale(e.x), this.toWorldScale(e.y)), planck.Vec2(this.toWorldScale(e.x), this.toWorldScale(e.y))), function(fixture) {
            let body = fixture.getBody();

            // this is how we create a distance joint
            this.joint = this.world.createJoint(planck.DistanceJoint({
                bodyA: this.hero,
                bodyB: body,
                anchorA: this.hero.getWorldCenter(),
                anchorB: body.getWorldCenter(),
                collideConnected: true
            }));

            // we must awake the body
            this.hero.setAwake(true);

            // return false to exit the AABB query without looking for another fixture
            return false;
        }.bind(this));
    }
    toWorldScale(n) {
        return n / gameOptions.worldScale;
    }
    toPixels(n) {
        return n * gameOptions.worldScale;
    }
    releaseHook() {
        if (this.joint) {

            // this is how we destroy a joint
            this.world.destroyJoint(this.joint);
            this.joint = null;
        }
    }
    createBox(posX, posY, width, height, isDynamic) {
        let box = this.world.createBody();
        if (isDynamic) {
            box.setDynamic();
        }
        box.createFixture(planck.Box(this.toWorldScale(width / 2 ), this.toWorldScale(height / 2)));
        box.setPosition(planck.Vec2(this.toWorldScale(posX), this.toWorldScale(posY)));
        box.setMassData({
            mass: 1,
            center: planck.Vec2(),
            I: 1
        });
        var color = new Phaser.Display.Color();
        color.random();
        color.brighten(50).saturate(100);
        let userData = this.add.graphics();
        userData.lineStyle(2, color.color, 1);
        userData.strokeRect(- width / 2, - height / 2, width, height);
        box.setUserData(userData);
        return box;
    }
    update(t, dt) {
        this.world.step(dt / 1000 * 2);
        this.world.clearForces();
        for (let b = this.world.getBodyList(); b; b = b.getNext()) {
            let bodyPosition = b.getPosition();
            let bodyAngle = b.getAngle();
            let userData = b.getUserData();
            userData.x = this.toPixels(bodyPosition.x);
            userData.y = this.toPixels(bodyPosition.y);
            userData.rotation = bodyAngle;
        }
        this.jointGraphics.clear();
        if (this.joint) {

            // get joint length and shorten it a bit
            this.joint.setLength(this.joint.getLength() - 0.1);

            // get hero velocity
            let velocity = this.hero.getLinearVelocity();

            // increase hero velocity
            this.hero.setLinearVelocity(planck.Vec2(velocity.x * 1.01, velocity.y * 1.01));

            // draw the joint
            let anchorA = this.joint.getAnchorA();
            let anchorB = this.joint.getAnchorB();
            this.jointGraphics.lineStyle(2, 0xffffff, 1);
            this.jointGraphics.moveTo(this.toPixels(anchorA.x), this.toPixels(anchorA.y));
            this.jointGraphics.lineTo(this.toPixels(anchorB.x), this.toPixels(anchorB.y));
            this.jointGraphics.strokePath();
        }
    }
};

And this is another great and fun prototype built in a bunch of lines. Download the source code.

Never miss an update! Subscribe, and I will bother you by email only when a new game or full source code comes out.