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.