“Serious Scramblers” HTML5 prototype built with Phaser and Arcade physics ported to TypeScript

Read all posts about "" game

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.

Get the most popular Phaser 3 book

Through 202 pages, 32 source code examples and an Android Studio project you will learn how to build cross platform HTML5 games and create a complete game along the way.

Get the book

214 GAME PROTOTYPES EXPLAINED WITH SOURCE CODE
// 1+2=3
// 100 rounds
// 10000000
// 2 Cars
// 2048
// A Blocky Christmas
// A Jumping Block
// A Life of Logic
// Angry Birds
// Angry Birds Space
// Artillery
// Astro-PANIC!
// Avoider
// Back to Square One
// Ball Game
// Ball vs Ball
// Ball: Revamped
// Balloon Invasion
// BallPusher
// Ballz
// Bar Balance
// Bejeweled
// Biggification
// Block it
// Blockage
// Bloons
// Boids
// Bombuzal
// Boom Dots
// Bouncing Ball
// Bouncing Ball 2
// Bouncy Light
// BoxHead
// Breakout
// Bricks
// Bubble Chaos
// Bubbles 2
// Card Game
// Castle Ramble
// Chronotron
// Circle Chain
// Circle Path
// Circle Race
// Circular endless runner
// Cirplosion
// CLOCKS - The Game
// Color Hit
// Color Jump
// ColorFill
// Columns
// Concentration
// Crossy Road
// Crush the Castle
// Cube Jump
// CubesOut
// Dash N Blast
// Dashy Panda
// Deflection
// Diamond Digger Saga
// Don't touch the spikes
// Dots
// Down The Mountain
// Drag and Match
// Draw Game
// Drop Wizard
// DROP'd
// Dudeski
// Dungeon Raid
// Educational Game
// Elasticity
// Endless Runner
// Erase Box
// Eskiv
// Farm Heroes Saga
// Filler
// Flappy Bird
// Fling
// Flipping Legend
// Floaty Light
// Fuse Ballz
// GearTaker
// Gem Sweeper
// Globe
// Goat Rider
// Gold Miner
// Grindstone
// GuessNext
// Helicopter
// Hero Emblems
// Hero Slide
// Hexagonal Tiles
// HookPod
// Hop Hop Hop Underwater
// Horizontal Endless Runner
// Hundreds
// Hungry Hero
// Hurry it's Christmas
// InkTd
// Iromeku
// Jet Set Willy
// Jigsaw Game
// Knife Hit
// Knightfall
// Legends of Runeterra
// Lep's World
// Line Rider
// Lumines
// Magick
// MagOrMin
// Mass Attack
// Math Game
// Maze
// Meeblings
// Memdot
// Metro Siberia Underground
// Mike Dangers
// Mikey Hooks
// Nano War
// Nodes
// o:anquan
// One Button Game
// One Tap RPG
// Ononmin
// Pacco
// Perfect Square!
// Perfectionism
// Phyballs
// Pixel Purge
// PixelField
// Planet Revenge
// Plants Vs Zombies
// Platform
// Platform game
// Plus+Plus
// Pocket Snap
// Poker
// Pool
// Pop the Lock
// Pop to Save
// Poux
// Pudi
// Pumpkin Story
// Puppet Bird
// Pyramids of Ra
// qomp
// Quick Switch
// Racing
// Radical
// Rebuild Chile
// Renju
// Rise Above
// Risky Road
// Roguelike
// Roly Poly
// Run Around
// Rush Hour
// SameGame
// SamePhysics
// Save the Totem
// Security
// Serious Scramblers
// Shrink it
// Sling
// Slingy
// Snowflakes
// Sokoban
// Space Checkers
// Space is Key
// Spellfall
// Spinny Gun
// Splitter
// Spring Ninja
// Sproing
// Stabilize!
// Stack
// Stick Hero
// String Avoider
// Stringy
// Sudoku
// Super Mario Bros
// Surfingers
// Survival Horror
// Talesworth Adventure
// Tetris
// The Impossible Line
// The Moops - Combos of Joy
// The Next Arrow
// Threes
// Tic Tac Toe
// Timberman
// Tiny Wings
// Tipsy Tower
// Toony
// Totem Destroyer
// Tower Defense
// Trick Shot
// Tunnelball
// Turn
// Turnellio
// TwinSpin
// vvvvvv
// Warp Shift
// Way of an Idea
// Whack a Creep
// Wheel of Fortune
// Where's my Water
// Wish Upon a Star
// Word Game
// Wordle
// Worms
// Yanga
// Yeah Bunny
// Zhed
// zNumbers