Build a HTML5 hook like the one featured in “Mikey Hooks” using Phaser and Planck.js port of Box2D

Read all posts about "" game

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.

Get the most popular Phaser 3 book

Through 202 pages, 32 source code examples and an Android Studio project you will learn how to build cross platform HTML5 games and create a complete game along the way.

Get the book

215 GAME PROTOTYPES EXPLAINED WITH SOURCE CODE
// 1+2=3
// 100 rounds
// 10000000
// 2 Cars
// 2048
// A Blocky Christmas
// A Jumping Block
// A Life of Logic
// Angry Birds
// Angry Birds Space
// Artillery
// Astro-PANIC!
// Avoider
// Back to Square One
// Ball Game
// Ball vs Ball
// Ball: Revamped
// Balloon Invasion
// BallPusher
// Ballz
// Bar Balance
// Bejeweled
// Biggification
// Block it
// Blockage
// Bloons
// Boids
// Bombuzal
// Boom Dots
// Bouncing Ball
// Bouncing Ball 2
// Bouncy Light
// BoxHead
// Breakout
// Bricks
// Bubble Chaos
// Bubbles 2
// Card Game
// Castle Ramble
// Chronotron
// Circle Chain
// Circle Path
// Circle Race
// Circular endless runner
// Cirplosion
// CLOCKS - The Game
// Color Hit
// Color Jump
// ColorFill
// Columns
// Concentration
// Crossy Road
// Crush the Castle
// Cube Jump
// CubesOut
// Dash N Blast
// Dashy Panda
// Deflection
// Diamond Digger Saga
// Don't touch the spikes
// Dots
// Down The Mountain
// Drag and Match
// Draw Game
// Drop Wizard
// DROP'd
// Dudeski
// Dungeon Raid
// Educational Game
// Elasticity
// Endless Runner
// Erase Box
// Eskiv
// Farm Heroes Saga
// Filler
// Flappy Bird
// Fling
// Flipping Legend
// Floaty Light
// Fuse Ballz
// GearTaker
// Gem Sweeper
// Globe
// Goat Rider
// Gold Miner
// Grindstone
// GuessNext
// Helicopter
// Hero Emblems
// Hero Slide
// Hexagonal Tiles
// HookPod
// Hop Hop Hop Underwater
// Horizontal Endless Runner
// Hundreds
// Hungry Hero
// Hurry it's Christmas
// InkTd
// Iromeku
// Jet Set Willy
// Jigsaw Game
// Knife Hit
// Knightfall
// Legends of Runeterra
// Lep's World
// Line Rider
// Lumines
// Magick
// MagOrMin
// Mass Attack
// Math Game
// Maze
// Meeblings
// Memdot
// Metro Siberia Underground
// Mike Dangers
// Mikey Hooks
// Nano War
// Nodes
// o:anquan
// One Button Game
// One Tap RPG
// Ononmin
// Pacco
// Perfect Square!
// Perfectionism
// Phyballs
// Pixel Purge
// PixelField
// Planet Revenge
// Plants Vs Zombies
// Platform
// Platform game
// Plus+Plus
// Pocket Snap
// Poker
// Pool
// Pop the Lock
// Pop to Save
// Poux
// Pudi
// Pumpkin Story
// Puppet Bird
// Pyramids of Ra
// qomp
// Quick Switch
// Racing
// Radical
// Rebuild Chile
// Renju
// Rise Above
// Risky Road
// Roguelike
// Roly Poly
// Run Around
// Rush Hour
// SameGame
// SamePhysics
// Save the Totem
// Security
// Serious Scramblers
// Shrink it
// Sling
// Slingy
// Snowflakes
// Sokoban
// Space Checkers
// Space is Key
// Spellfall
// Spinny Gun
// Splitter
// Spring Ninja
// Sproing
// Stabilize!
// Stack
// Stairs
// Stick Hero
// String Avoider
// Stringy
// Sudoku
// Super Mario Bros
// Surfingers
// Survival Horror
// Talesworth Adventure
// Tetris
// The Impossible Line
// The Moops - Combos of Joy
// The Next Arrow
// Threes
// Tic Tac Toe
// Timberman
// Tiny Wings
// Tipsy Tower
// Toony
// Totem Destroyer
// Tower Defense
// Trick Shot
// Tunnelball
// Turn
// Turnellio
// TwinSpin
// vvvvvv
// Warp Shift
// Way of an Idea
// Whack a Creep
// Wheel of Fortune
// Where's my Water
// Wish Upon a Star
// Word Game
// Wordle
// Worms
// Yanga
// Yeah Bunny
// Zhed
// zNumbers