Get the full commented source code of

HTML5 Suika Watermelon Game

Talking about Circular endless runner game, Game development, HTML5, Javascript, Phaser and TypeScript.

With Phaser 4 already in Beta and hopefully close to the final release, I wanted to test it and see if my old Phaser 3 scripts would run in the new version.

So I took my circular endless runner prototype, whose final version is available for free on this Gumroad page, and installed Phaser 4 beta.

All I had to do was a simple

npm install –save phaser@beta

And my package.json file was updated with latest beta version:

JSON
{
  "name": "ts_multicontrol",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "development": "webpack serve --open --config webpack.development.js",
    "distribution": "webpack --config webpack.distribution.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "phaser": "^4.0.0-beta.2"
  },
  "devDependencies": {
    "clean-webpack-plugin": "^4.0.0",
    "copy-webpack-plugin": "^9.0.1",
    "ts-loader": "^9.2.6",
    "typescript": "^4.9.5",
    "webpack": "^5.88.2",
    "webpack-cli": "^4.10.0",
    "webpack-dev-server": "^4.15.1"
  }
}

The source code remained the same used in HTML5 prototype of a circular endless runner built with Phaser – final version in TypeScript with more optimization post, I just added some lines to playGame.ts to add the blur filter as seen on this sandbox example, just to test it in a real world example.

playGame.ts

I had to add a //@ts-ignore to prevent errors as internal was not recognized, but everything worked fine.

TypeScript
// THE GAME ITSELF

// modules to import
import { GameOptions } from '../gameOptions';       // game options   
import { PhysicsCircle } from '../physicsCircle';   // physics circle
import { PhysicsSpike } from '../physicsSpike';     // physics spike

// PlayGame class extends Phaser.Scene class
export class PlayGame extends Phaser.Scene {

    constructor() {
        super({
            key : 'PlayGame'
        });
    }

    player          : PhysicsCircle;                                // the player
    spikeArray      : PhysicsSpike[];                               // group with all spikes
    trailEmitter    : Phaser.GameObjects.Particles.ParticleEmitter  // trail particle emitter

    // method to be called once the instance has been created
    create() : void {

        //@ts-ignore
        const parallelFilters = this.cameras.main.filters.internal.addParallelFilters();
        parallelFilters.top.addThreshold(0.5, 1);
        parallelFilters.top.addBlur();
        parallelFilters.blend.blendMode = Phaser.BlendModes.ADD;
        parallelFilters.blend.amount = 0;

        this.tweens.add({
            targets: parallelFilters.blend,
            amount: 5,
            duration: 1000,
            yoyo: true,
            repeat: -1,
            ease: 'Sine.easeInOut'
        });

        // set some custom data
        this.data.set({
            gameOver    : false,                                // is the game over?
            centerX     : this.game.config.width as number / 2, // horizontal center of the game
            centerY     : this.game.config.height as number / 2 // vertical center of the game
        })

        // place and resize big circle
        const bigCircle : Phaser.GameObjects.Sprite = this.add.sprite(this.data.get('centerX'), this.data.get('centerY'), 'bigcircle');
        bigCircle.displayWidth = GameOptions.bigCircleRadius * 2;
        bigCircle.displayHeight = GameOptions.bigCircleRadius * 2;

        // initialize spikeArray vector
        this.spikeArray = [];

        // place spikes
        for (let i : number = 0; i < 3 * GameOptions.spikesPerQuadrant; i ++) {
 
            // create a spike
            const spike : PhysicsSpike = new PhysicsSpike(this);
 
            // addi the spike to spike array
            this.spikeArray.push(spike);

            // determine the quadrant where to place the spike
            // 0 : bottom-right
            // 1 : bottom-left
            // 2 : top-left
            // 3 : top-right
            const quadrant : number = Math.floor(i % 3);

            // place the spike in the given quadrant.
            // I don't want spikes in the quadrant 2 at the beginning, because it's the quadrant where the player spawns
            spike.place(quadrant != 2 ? quadrant : 3);  
        }
 
        // create the player
        this.player = new PhysicsCircle(this);

        // create the particle trail emitter and make it follow the player
        this.trailEmitter = this.add.particles(this.player.x, this.player.y, 'particle', {
            
            lifespan    : 900,  // particle life span, in mmilliseconds
            alpha : {
                start   : 1,    // alpha start value, 0 = transparent; 1 = opaque
                end     : 0     // alpha end value
            },
            scale : {
                start   : 1,    // scale start value
                end     : 0.8   // scale end value
            },
            quantity    : 1,    // amount of particle to be fired
            frequency   : 150   // particle frequency, in milliseconds
        }).startFollow(this.player);
      
        // handle player input on touch or clicl
        this.input.on('pointerdown', () => {

            // make player jump
            this.player.jump();
        });
    }

    // method to be called when the game is over
    gameOver() : void {

        // set gameOver data to true
        this.data.set('gameOver', true);

        // shake the camera
        this.cameras.main.shake(800, 0.01);
                   
        // hide the player
        this.player.setVisible(false);

        // stop trail emitter
        this.trailEmitter.stop();

        // add a particle explosion effect
        this.add.particles(this.player.x, this.player.y, 'particle', {
            lifespan    : 900,  // particle life span, in milliseconds
            alpha : {
                start   : 1,    // alpha start value, 0 = transparent; 1 = opaque
                end     : 0     // alpha end value
            },
            scale : {
                start   : 0.6,  // scale start value
                end     : 0.02  // scale end value
            },
            speed: {
                min     : -150, // minimum speed, in pixels per second
                max     : 150   // maximum speed, in pixels per second
            },
        }).explode(100);

        // add a timer event
        this.time.addEvent({
            delay       : 2000,     // delay, in milliseconds
            callback    : () => {   // callback function
                
                // start PlayGame scene
                this.scene.start('PlayGame');
            }
        });
    }

    // metod to be called at each frame
    // time : time passed since the beginning, in milliseconds
    // deltaTime : time passed since last frame, in milliseconds
    update(time : number, deltaTime : number) {

        // if the game is over, do nothing
        if (this.data.get('gameOver')) {
            return;
        }

        // move the player, according to deltaTime
        this.player.move(deltaTime / 1000);

        // loop through all spikes
        this.spikeArray.forEach((spike : PhysicsSpike) => {

            // get angle difference between spike and player
            const angleDifference : number = Math.abs(Phaser.Math.Angle.ShortestBetween(spike.angle, this.player.currentAngle));
            
            // if the player is not approaching the spike and it's close enough...
            if (!spike.approaching && angleDifference < GameOptions.closeToSpike) {

                // player is now approaching the spike
                spike.approaching = true;     
            }

            // if the player is approaching the spike...
            if (spike.approaching) {

                // if spike triangle shape and player circle shape intersect...
                if (Phaser.Geom.Intersects.TriangleToCircle(spike.triangle, this.player.circle)) {
                    
                    // the game is over!
                    this.gameOver();
                }
                
                // if we are getting too far from the spike...
                if (angleDifference > GameOptions.farFromSpike) {

                    // recycle the spike making it disappear
                    spike.disappear();
                }
            }
        });
    }
}

And this is the result:

Tap or click to jump and avoid the spikes. You can perform double and even triple jump.

Also have a look at the glowing blur filter, which is useless but I added it just to test the game.

It is a bit too early to tell, but at the moment the transition from Phaser 3 to Phaser 4 seems to be painless, allowing us to use the new features without rewriting large portions of code.

I look forward to getting my hands on the final release, meanwhile get the full source code along with the entire wepack project.

Don’t know where to start developing with Phaser and TypeScript? I’ll explain it to you step by step in this free minibook.

Never miss an update! Subscribe, and I will bother you by email only when a new game or full source code comes out.