Mikey Hooks is a great game. Fullstop.
I liked so much the way the player used the hook, and I built prototypes using Flash + Box2D, Flash + Nape (Nape seems to be discontinued), Phaser + Box2D and Unity.
No matter the language, engine or framework you are using to build your hooks, they will all rely on a constraint called distance joint.
A constraint on a system is a parameter that the system must obey. In this case, the constraint is a distance joint which says “no matter what happens, there must be a given distance between two bodies”.
And that’s all:
Click on a box and hold to create a distance joint from the ball to the box working like a hook and start swinging by clicking and holding on different boxes.
The code is just a matter of checking if we clicked on a box, then create the joint until the mouse is released or a collision occurred.
Here it is, fully commented:
let game; let gameOptions = { gravity: 1, // game gravity terrainObjects: 20, // amount of terrain objects ballRadius: 20, // radius of the ball constraintSpeed: 4, // constraint shrinkage speed minBoxSize: 50, // minimum box size maxBoxSize: 200 // maximum box size } const WALL = 0; const BALL = 1; window.onload = function() { // game configuration let gameConfig = { type: Phaser.AUTO, scale: { mode: Phaser.Scale.FIT, autoCenter: Phaser.Scale.CENTER_BOTH, parent: "thegame", width: 1334, height: 750 }, scene: playGame, physics: { default: "matter", matter: { gravity: { y: gameOptions.gravity }, debug: true } } } game = new Phaser.Game(gameConfig); window.focus(); } class playGame extends Phaser.Scene{ constructor(){ super("PlayGame"); } create(){ // I want physics world to be updated 30 times per second this.matter.world.update30Hz(); // adding world bounds. Basically four walls this.matter.world.setBounds(10, 10, game.config.width - 20, game.config.height - 20); // placing some random static boxes labeled as WALL for (let i = 0; i < gameOptions.terrainObjects; i++){ let posX = Phaser.Math.Between(0, game.config.width); let posY = Phaser.Math.Between(0, game.config.height); let width = Phaser.Math.Between(gameOptions.minBoxSize, gameOptions.maxBoxSize); let height = Phaser.Math.Between(gameOptions.minBoxSize, gameOptions.maxBoxSize); let poly = this.matter.add.rectangle(posX, posY, width, height, { isStatic: true }); poly.label = WALL; } // adding a bouncing ball labeled as BALL this.ball = this.matter.add.circle(game.config.width / 2, game.config.height / 2, gameOptions.ballRadius, { restitution: 0.5 }); this.ball.label = BALL; // event listeners this.input.on("pointerdown", this.fireHook, this); this.input.on("pointerup", this.releaseHook, this); // no ropes at the beginning this.rope = null; // when the ball collides on something, we'll remove the hook this.matter.world.on("collisionstart", function(e, b1, b2){ if(b2.label == BALL){ this.releaseHook(); } }, this) } // method to fire the hook fireHook(e){ // getting all bodies let bodies = this.matter.world.localWorld.bodies; // looping through bodies for(let i = 0; i < bodies.length; i++){ // getting body vertices let vertices = bodies[i].parts[0].vertices; // do the vertices contain the pointer AND the body is labeled as WALL? if(Phaser.Physics.Matter.Matter.Vertices.contains(vertices, e.position) && bodies[i].label == WALL){ // calculate the distance between the ball and the body let distance = Phaser.Math.Distance.Between(this.ball.position.x, this.ball.position.y, bodies[i].position.x, bodies[i].position.y) // is the distance greater than ball radius? if(distance > gameOptions.ballRadius){ // add the constraint this.rope = this.matter.add.constraint(this.ball, bodies[i], distance, 0); } break; } } } // method to remove the hook releaseHook(){ // is there a constraint? Remove it if(this.rope){ this.matter.world.removeConstraint(this.rope); this.rope = null; } } // method to be executed at every frame update(){ // is there a constraint? Shrink it if(this.rope){ this.rope.length -= gameOptions.constraintSpeed; } } };
At the moment the hook always starts from the center of the box, but I am working on a more interesting way using raycasting… stay tuned and meanwhile download the source code of this example.