Build a HTML5 game like “Pocket Snap” using Phaser and Matter physics – Step 2: add a landing spot using sensors.
When playing with the first step of Pocket Snap prototype, you surely missed a target where to try to make balls land.
The original game features some “U” shaped targets, which remainds a bit “Trick Shot” tutorial series, which was built using Phaser 2 and Box2D, and will get updated soon with the new Plank.js library.
Meanwhile, let’s stick to Matter physics and see what I did with it:
Tap and hold to charge, release to fire the ball.
Try to make the ball land into the target.
The game restarts a couple of seconds after you fired the ball with some random parameters.
If you managed to hit the target, you will see a “Yeah” message.
At the bottom of the “U” shape there is a sensor. Sensors work just like normal bodies triggering collisions, but they are ethereal.
Look at the commented source code:
let game;
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,
physics: {
default: "matter",
matter: {
gravity: {
y: 1
},
debug: true
}
}
}
game = new Phaser.Game(gameConfig);
window.focus();
}
class playGame extends Phaser.Scene{
constructor(){
super("PlayGame");
}
create(){
// matter settings
this.matter.world.update30Hz();
this.matter.world.setBounds(10, 10, game.config.width - 20, game.config.height - 20);
// random cannon properties
let angle = Phaser.Math.Between(60, 75);
let width = Phaser.Math.Between(20, 30);
let length = Phaser.Math.Between(120, 250);
let tickness = Phaser.Math.Between(10, 12);
let position = new Phaser.Math.Vector2(game.config.width / 6, 550);
// bottom body
let bottomWidth = width + tickness * 2;
let bottomBody = this.matter.add.rectangle(position.x, position.y, bottomWidth, tickness, this.setProperties(true, angle));
// some trigonometry useful to find the origins of cannon side bodies
let bottomCathetus = (width + tickness) / 2;
let sideCathetus = (length + tickness) / 2;
let hypotenuse = Math.sqrt(Math.pow(bottomCathetus, 2) + Math.pow(sideCathetus, 2));
let bottomAngle = Phaser.Math.RadToDeg(Math.asin(sideCathetus / hypotenuse));
// side body 1
let firstSideOrigin = this.moveBy(position, hypotenuse, 90 - bottomAngle - angle)
this.matter.add.rectangle(firstSideOrigin.x, firstSideOrigin.y, tickness, length, this.setProperties(true, angle));
// side body 2
let secondSideOrigin = this.moveBy(position, hypotenuse, bottomAngle - 90 - angle)
this.matter.add.rectangle(secondSideOrigin.x, secondSideOrigin.y, tickness, length, this.setProperties(true, angle));
// trigger
let triggerOrigin = this.moveBy(position, (length - (width * 3 - tickness) / 2), -angle)
let trigger = this.matter.add.rectangle(triggerOrigin.x, triggerOrigin.y, width, width, this.setProperties(false, angle));
// cannon ball
let ballOrigin = this.moveBy(position, (width + length - (width * 3 - tickness) / 2), -angle)
this.matter.add.circle(ballOrigin.x, ballOrigin.y, width / 2, this.setProperties(false, angle, "ball"));
// constraint
let constraintLength = length + (tickness - width * 3) / 2;
this.constraint = this.matter.add.constraint(bottomBody, trigger, constraintLength, 1);
this.constraintFireLength = constraintLength + width;
this.constrainMinLength = width / 2 + tickness / 2;
// random target properties
let targetWidth = Phaser.Math.Between(80, 120);
let targetHeight = Phaser.Math.Between(120, 180);
let targetTickness = Phaser.Math.Between(10, 20);
let targetPosition = new Phaser.Math.Vector2(Phaser.Math.Between(game.config.width / 4 * 3, game.config.width / 5 * 4), game.config.height - Phaser.Math.Between(40, 80));
// target
this.matter.add.rectangle(targetPosition.x, targetPosition.y, targetWidth + targetTickness * 2, targetTickness, {
isStatic: true
});
this.matter.add.rectangle(targetPosition.x + (targetWidth + targetTickness) / 2, targetPosition.y - (targetHeight + targetTickness) / 2, targetTickness, targetHeight, {
isStatic: true
});
this.matter.add.rectangle(targetPosition.x - (targetWidth + targetTickness) / 2, targetPosition.y - (targetHeight + targetTickness) / 2, targetTickness, targetHeight, {
isStatic: true
});
// this is the sensor used
this.matter.add.rectangle(targetPosition.x, targetPosition.y - targetTickness, targetWidth, targetTickness, {
isStatic: true,
isSensor: true,
label: "goal"
});
// a text to show if the player hits the target
this.yeahText = this.add.text(game.config.width / 2, 200, "YEAH !!", {
fontFamily: "Arial",
fontSize: 64,
color: "#00ff00"
})
this.yeahText.setOrigin(0.5);
this.yeahText.setVisible(false);
// check for collision between the sensor and the ball
this.matter.world.on("collisionstart", function(event, bodyA, bodyB){
if((bodyA.label == "ball" && bodyB.label == "goal") || (bodyA.label == "goal" && bodyB.label == "ball")){
// show the text if the ball hits the sensor
this.yeahText.visible = true;
}
}, this);
// listeners and flags
this.input.on("pointerdown", this.charge, this);
this.input.on("pointerup", this.fire, this);
this.charging = false
}
// charge
charge(){
this.charging = true;
}
// fire: look how stiffness changes, then restart the game
fire(){
this.charging = false;
this.constraint.stiffness = 0.02
this.constraint.length = this.constraintFireLength;
this.time.addEvent({
delay: 3500,
callbackScope: this,
callback: function(){
this.scene.start("PlayGame");
},
});
}
// we reduce constraint length if charging
update(){
if(this.charging && this.constraint.length > this.constrainMinLength){
this.constraint.length -= 1;
}
}
// utility method to create an object with body properties
setProperties(isStatic, angle, label){
if(label == undefined){
label = "";
}
let radians = Phaser.Math.DegToRad(90 - angle);
return {
isStatic: isStatic,
angle: radians,
friction: 0,
label: label,
restitution: 0.4
}
}
// utility method to move a point by "distance" pixels in "degrees" direction
moveBy(point, distance, degrees){
let radians = Phaser.Math.DegToRad(degrees);
return new Phaser.Math.Vector2(point.x + distance * Math.cos(radians), point.y + distance * Math.sin(radians));
}
};
We managed to create targets and react when the player fires the ball into the hole, next time we’ll see how to move targets, meanwhile download the source code.