Get the full commented source code of

HTML5 Suika Watermelon Game

Talking about Serious Scramblers game, Game development, HTML5, Javascript, Phaser and TypeScript.

If you’re following me for some weeks, you should have noticed I am sticking to TypeScript, and one of the first things to do when you are about to learn a new language is to port some of your scripts in such language.

More than ten years ago I also wrote about 10 tips to approach a new language, maybe you can have a look at it.

Back to TypeScript, let’s port Serious Scramblers prototype.

If you don’t know how to configure your system to start conding and publishing with Phaser and TypeScript, I wrote a four steps guide about the migration from JavaScript to TypeScript, check steps 123 and 4.

Look at the prototype we are going to build:

Tap and hold left or right to move the character left or right. Once you move, platforms will scroll up. Reach the top of the stage, and it’s game over.

Fall from platform to platform without falling too down, if you reach the bottom of the stage, it’s game over.

To ensure maximum code reusability, the source code is split in 6 TypeScript file and one HTML file.

I commented every line of the source code:

index.html

The webpage which hosts the game, just the bare bones of HTML and main.ts is called.

Also look at the thegame div, this is where the game will run.

<!DOCTYPE html>
<html>
    <head>
        <style type = "text/css">
            body {
                background: #000000;
                padding: 0px;
                margin: 0px;
            }
        </style>
        <script src = "scripts/main.ts"></script>
    </head>
    <body>
        <div id = "thegame"></div>
    </body>
</html>

main.ts

The main TypeScript file, the one called by index.html.
Here we import most of the game libraries and define both Scale Manager object and Physics object.

Here we also initialize the game itself.

// MAIN GAME FILE

// modules to import
import Phaser from 'phaser';
import { PreloadAssets } from './preloadAssets';
import { PlayGame} from './playGame';
import { GameOptions } from './gameOptions';

// object to initialize the Scale Manager
const scaleObject: Phaser.Types.Core.ScaleConfig = {
    mode: Phaser.Scale.FIT,
    autoCenter: Phaser.Scale.CENTER_BOTH,
    parent: 'thegame',
    width: 750,
    height: 1334
}

// obhect to initialize Arcade physics
const physicsObject: Phaser.Types.Core.PhysicsConfig = {
    default: 'arcade',
    arcade: {
        gravity: {
            y: GameOptions.gameGravity
        }
    }    
}

// game configuration object
const configObject: Phaser.Types.Core.GameConfig = {
    type: Phaser.AUTO,
    backgroundColor:0x444444,
    scale: scaleObject,
    scene: [PreloadAssets, PlayGame],
    physics: physicsObject
}

// the game itself
new Phaser.Game(configObject);

preloadAssets.ts

Class to preload all assets used in the game.

// CLASS TO PRELOAD ASSETS

// this class extends Scene class
export class PreloadAssets extends Phaser.Scene {

    // constructor    
    constructor() {
        super({
            key: 'PreloadAssets'
        });
    }

    // preloading assets, the good old way
    preload(): void {
        this.load.image('hero', 'assets/hero.png');
        this.load.image('platform', 'assets/platform.png');
	}

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

        // call PlayGame class
        this.scene.start('PlayGame');
	}
}

gameOptions.ts

Game options which can be changed to tune the gameplay are stored in a separate module, ready to be reused.

// CONFIGURABLE GAME OPTIONS

export const GameOptions = {

    // first platform vertical position. 0 = top of the screen, 1 = bottom of the screen
    firstPlatformPosition: 1 / 10,

    // game gravity, which only affects the hero
    gameGravity: 1200,

    // hero speed, in pixels per second
    heroSpeed: 300,

    // platform speed, in pixels per second
    platformSpeed: 190,

    // platform length range, in pixels
    platformLengthRange: [50, 150],

    // platform horizontal distance range from the center of the stage, in pixels
    platformHorizontalDistanceRange: [0, 250],

    // platform vertical distance range, in pixels
    platformVerticalDistanceRange: [150, 300]
}

playGame.ts

The game itself, the biggest class, game logic is stored here.

// THE GAME ITSELF

// modules to import
import { GameOptions } from './gameOptions';
import PlayerSprite from './playerSprite';
import PlatformSprite from './platformSprite';

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

    // group which will contain all platforms
    platformGroup: Phaser.Physics.Arcade.Group;

    // the hero of the game
    hero: PlayerSprite;

    // here we store game width once for all
    gameWidth: number;

    // here we store game height once for all
    gameHeight: number;

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

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

        // save game width value
        this.gameWidth = this.game.config.width as number;

        // save game height value
        this.gameHeight = this.game.config.height as number;

        // create a new physics group
        this.platformGroup = this.physics.add.group();

        // create starting platform
        let platform: PlatformSprite = new PlatformSprite(this, this.gameWidth / 2, this.gameHeight * GameOptions.firstPlatformPosition, "platform");

        // add platform to platform group
        platform.addToGroup(this.platformGroup);

        // we are going to create 10 more platforms which we'll reuse to save resources
        for(let i = 0; i < 10; i ++) {

            // platform creation, as a member of platformGroup physics group
            let platform = new PlatformSprite(this, 0, 0, "platform");

            // add platform to platform group
            platform.addToGroup(this.platformGroup);

            // position the platform
            this.positionPlatform(platform);
        }

        // add the hero
        this.hero = new PlayerSprite(this, this.gameWidth / 2, 0, "hero");

        // input listener to move the hero
        this.input.on("pointerdown", this.moveHero, this);

        // input listener to stop the hero
        this.input.on("pointerup", this.stopHero, this);
    }

    // method to position a platform
    positionPlatform(platform: PlatformSprite):void {

        // vertical position
        platform.y = this.getLowestPlatform() + this.randomValue(GameOptions.platformVerticalDistanceRange);

        // horizontal position
        platform.x = this.gameWidth / 2 + this.randomValue(GameOptions.platformHorizontalDistanceRange) * Phaser.Math.RND.sign();

        // platform width
        platform.displayWidth = this.randomValue(GameOptions.platformLengthRange);
    }

    // method to get the lowest platform, returns the position of the lowest platform, in pixels
    getLowestPlatform(): number {

        // lowest platform value is initially set to zero
        let lowestPlatform = 0;

        // get all platforms
        let platforms: PlatformSprite[] = this.platformGroup.getChildren() as PlatformSprite[];

        // loop through all platforms
        for (let platform of platforms) {

            // get the highest value between lowestPlatform and platform y coordinate
            lowestPlatform = Math.max(lowestPlatform, platform.y);
        };

        // return lowest platform coordinate
        return lowestPlatform;
    }

    // method to toss a random value between two elements in an array
    randomValue(a: number[]): number {

        // return a random integer between the first and the second item of the array
        return Phaser.Math.Between(a[0], a[1]);
    }

    // method to move the hero
    moveHero(e: Phaser.Input.Pointer): void {

        // set hero velocity according to input horizontal coordinate
        this.hero.setVelocityX(GameOptions.heroSpeed * ((e.x > this.gameWidth / 2) ? 1 : -1));

        // is it the first move?
        if(this.hero.firstMove) {

            // it's no longer the first move
            this.hero.firstMove = false;

            // move platform group
            this.platformGroup.setVelocityY(-GameOptions.platformSpeed);
        }
    }

    // method to stop the hero
    stopHero(): void {

        // ... just stop the hero :)
        this.hero.setVelocityX(0);
    }

    // method to be executed at each frame
    update(): void {

        // handle collision between ball and platforms
        this.physics.world.collide(this.platformGroup, this.hero);

        // get all platforms
        let platforms: PlatformSprite[] = this.platformGroup.getChildren() as PlatformSprite[];

        // loop through all platforms
        for (let platform of platforms) {

            // if a platform leaves the stage to the upper side...
            if (platform.getBounds().bottom < 0) {

                // ... recycle the platform
                this.positionPlatform(platform);
            }
        }

        // if the hero falls down or leaves the stage from the top...
        if(this.hero.y > this.gameHeight || this.hero.y < 0) {

            // restart the scene
            this.scene.start("PlayGame");
        }
    }
}

playerSprite.ts

Class to define the player Sprite, the main actor of the game, the one players control.

// PLAYER SPRITE CLASS    

// player sprite extends Arcade Sprite class
export default class PlayerSprite extends Phaser.Physics.Arcade.Sprite {

    // is the first time player is moving
    firstMove: Boolean = true;

    // constructor
	constructor(scene: Phaser.Scene, x: number, y: number, key: string) {
		super(scene, x, y, key);

        // add the player to the scnee
        scene.add.existing(this);

        // add physics body to platform
        scene.physics.add.existing(this);
	}
}

platformSprite.ts

Class to define the platforms.

// PLATFORM SPRITE CLASS    

// platform sprite extends RenderTexture class
export default class PlatformSprite extends Phaser.Physics.Arcade.Sprite {

    // platform physics body
    body: Phaser.Physics.Arcade.Body;

    // constructor
    constructor(scene: Phaser.Scene, x: number, y: number, key: string) {
        super(scene, x, y, key);

        // add the platform to the scnee
        scene.add.existing(this);

        // add physics body to platform
        scene.physics.add.existing(this);
    }

    // method to add the platform to a group and set physics properties
    addToGroup(group: Phaser.Physics.Arcade.Group) {

        // add the platform to group
        group.add(this);

        // platform body does not react to collisions
        this.body.setImmovable(true);

        // platform body is not affected by gravity
        this.body.setAllowGravity(false);

    }
}

And now the porting is over. The prototype is quite uncomplete, so next time I am going to add multiple cameras, enemies and some stuff to collect.

Everything built with TypeScript of course. Download the source code of the entire project.

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