Get the full commented source code of

HTML5 Suika Watermelon Game

Talking about Word Game game, Box2D, Game development, HTML5, Javascript and Phaser.

If you liked the series about word games and want to see how you can build a prototype of a game like worDrop, which was played more than 4 million times during Flash era, here is a quick HTML5 prototype.

Keep in mind that at the moment I am not recognizing words, but you can easily do it following this post, there is not game over condition – it should be when the stack of boxes becomes too high – and there’s no object pooling.

Moreover, letters appear in a random way but you should make some letters to appear more frequently, according to the language of your game.

But it’s a good start to build physics driven word games.

Look at the prototype:

Click on letters to build a word, and click on the word itself, or the bottom of the canvas, to remove letters.

And this is the source code, completely commented:

let game;

let gameOptions = {

    // world scale to convert Box2D meters to pixels
    worldScale: 30,

    gameGravity: 8,

    // amount of letters when the game starts
    startingLetters: 16,

    // letter size, in pixels
    letterSize: 100,

    // delay between two letters, in mmilliseconds
    letterDelay: 1500
}

window.onload = function() {
    let gameConfig = {
        type: Phaser.AUTO,
        scale: {
            mode: Phaser.Scale.FIT,
            autoCenter: Phaser.Scale.CENTER_BOTH,
            parent: "thegame",
            width: 750,
            height: 1334
        },
        scene: playGame
    }
    game = new Phaser.Game(gameConfig);
    window.focus();
}
class playGame extends Phaser.Scene {
    constructor() {
        super("PlayGame");
    }
    create() {

        // world gravity, as a Vec2 object. It's just a x, y vector
        let gravity = planck.Vec2(0, 8);

        // this is how we create a Box2D world
        this.world = planck.World(gravity);

        // createBox is a method I wrote to create a box, see how it works at line 55
        this.createBox(game.config.width / 2, game.config.height - 80, game.config.width, 160, false);
        this.createBox(20, game.config.height / 2, 40, game.config.height, false);
        this.createBox(game.config.width - 20, game.config.height / 2, 40, game.config.height, false);

        // time event to place the first letters
        this.startingTimer = this.time.addEvent({
            delay: 200,
            callbackScope: this,
            callback: function() {
                this.createBox(Phaser.Math.Between(100, game.config.width - 100), -100, gameOptions.letterSize, gameOptions.letterSize, true);
                if (this.startingTimer.repeatCount ==0) {

                    // time event to place the remaining letters
                    this.inGameLetters = this.time.addEvent({
                        delay: gameOptions.letterDelay,
                        callbackScope: this,
                        callback: function() {
                            this.createBox(Phaser.Math.Between(100, game.config.width - 100), -100, 100, 100, true);
                        },
                        loop: true
                    });
                }
            },
            repeat: gameOptions.startingLetters
        });


        // text to display the word
        this.wordText = this.add.text(game.config.width / 2, game.config.height - 80, "", {
            font: "64px arial",
            fill: "#000000"
        });
        this.wordText.setOrigin(0.5)

        // input listener
        this.input.on("pointerdown", this.writeWord, this);
    }

    writeWord(event) {

        // if we are clicking on the bottom of the canvas...
        if (event.y > game.config.height - 160) {

            // delete the word
            this.wordText.text = "";

            // loop through all bodies
            for (let body = this.world.getBodyList(); body; body = body.getNext()) {

                // get body userData
                let userData = body.getUserData();

                // if the body sprite is semi transparent...
                if (userData.sprite.alpha == 0.25) {

                    // destroy the sprite
                    userData.sprite.destroy();

                    // destroy the letter
                    userData.letter.destroy();

                    // destroy the body itself
                    this.world.destroyBody(body);
                }
            }
        }

        // if we are NOT clicking on the bottom of the canvas...
        else {

            // loop through all bodies
            for (let body = this.world.getBodyList(); body; body = body.getNext()) {

                // loop through all fixtures
                for (let fixture = body.getFixtureList(); fixture; fixture = fixture.getNext()) {

                    // if the fixture contains the input coordinate...
                    if (fixture.testPoint(new planck.Vec2(this.toWorldScale(event.x), this.toWorldScale(event.y)))) {

                        // get body userData
                        let userData = body.getUserData();

                        // if the sprite is fully opaque and the body is dynamic (this means it's a letter)
                        if (userData.sprite.alpha == 1 && body.isDynamic()) {

                            // set the sprite to semi transparent
                            userData.sprite.alpha = 0.25;

                            // update text string
                            this.wordText.text += userData.letter.text;
                        }
                    }
                }
            }
        }
    }

    // simple function to convert pixels to meters
    toWorldScale(n) {
        return n / gameOptions.worldScale;
    }

    // here we go with some Box2D stuff
    // arguments: x, y coordinates of the center, with and height of the box, in pixels
    // we'll conver pixels to meters inside the method
    createBox(posX, posY, width, height, isDynamic) {

        // this is how we create a generic Box2D body
        let box = this.world.createBody();
        if (isDynamic) {

            // Box2D bodies born as static bodies, but we can make them dynamic
            box.setDynamic();
        }

        // a body can have one or more fixtures. This is how we create a box fixture inside a body
        box.createFixture(planck.Box(width / 2 / gameOptions.worldScale, height / 2 / gameOptions.worldScale));

        // now we place the body in the world
        box.setPosition(planck.Vec2(posX / gameOptions.worldScale, posY / gameOptions.worldScale));

        // time to set mass information
        box.setMassData({
            mass: 1,
            center: planck.Vec2(),

            // I have to say I do not know the meaning of this "I", but if you set it to zero, bodies won't rotate
            I: 1
        });

        // now we create a graphics object representing the body
        let color = new Phaser.Display.Color();

        // draw the square
        let debugDraw = this.add.graphics();

        // a box has no letter by default
        let letter =  null;

        // if isDynamic is set to true
        if (isDynamic) {

            // assign the box a random letter
            letter = this.add.text(0, 0, String.fromCharCode(65+Math.floor(Math.random() * 26)), {
                font: "64px arial",
                fill: "#000000"
            })
            letter.setOrigin(0.5);

            // assign the box a random color
            color.random();
            color.brighten(50).saturate(100);
        }

        // if isDynamic is set to true, assign the box a grey color
        else {
            color.setTo(128, 128, 128);
        }

        // draw the rectangle
        debugDraw.fillStyle(color.color, 1);
        debugDraw.fillRect(- width / 2, - height / 2, width, height);

        // set box user data
        let userData = {
            sprite: debugDraw,
            letter: letter
        }

        // a body can have anything in its user data, normally it's used to store its sprite
        box.setUserData(userData);
    }

    update() {

        // advance the simulation by 1/20 seconds
        this.world.step(1 / 30);

        // crearForces  method should be added at the end on each step
        this.world.clearForces();

        // iterate through all bodies
        for (let body = this.world.getBodyList(); body; body = body.getNext()) {

            // get body position
            let bodyPosition = body.getPosition();

            // get body angle, in radians
            let bodyAngle = body.getAngle();

            // get body user data, the graphics object
            let userData =body.getUserData();

            // adjust graphic object position and rotation
            userData.sprite.x = bodyPosition.x * gameOptions.worldScale;
            userData.sprite.y = bodyPosition.y * gameOptions.worldScale;
            userData.sprite.rotation = bodyAngle;

            // if there is a letter on the box...
            if (userData.letter) {

                // adjust letter position and rotation
                userData.letter.x = userData.sprite.x;
                userData.letter.y = userData.sprite.y;
                userData.letter.rotation = bodyAngle;
            }
        }
    }
};

My worDrop game begins to take shape, let’s see if I manage to achieve 4 millions more plays, meanwhile 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.