Build a HTML5 game like “Pocket Snap” using Phaser and Matter physics – Step 2: add a landing spot using sensors.

Talking about Pocket Snap game, Game development, HTML5, Javascript and Phaser.

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.