“Serious Scramblers” HTML5 prototype built with Phaser and TypeScript – Adding platform types

Read all posts about "" game

Let’s add some new features to Serious Scramblers prototype, trying to turn it into a more complete game.

I added two new platform types:

Green platform: a bouncy platform, hero will bounce when landing on it. Bounce force can be configured in game options.

Red Platform: a disappearing platform. When the hero lands on it, the platform disappears after a certain amount of time which can also be configured in game options.

Have a look at the game:

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.

Let’s have a look at the 6 TypeScript files and the HTML file used in this prototype. I also highlighted the new lines of code added to the source code of the previous step.

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.

Now game gravity has been rolled back to a more enjoyable value. First platform position has been moved down a bit, to give the player some more time to move when the game starts, and I added an array with platform type tint colors, a value to set bounce velocity and another value to set the amount of time before a platform disappears.

// CONFIGURABLE GAME OPTIONS

export const GameOptions = {

    // first platform vertical position. 0 = top of the screen, 1 = bottom of the screen
    firstPlatformPosition: 4 / 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],

    // platform tint colors
    platformColors: [0xffffff, 0xff0000, 0x00ff00],

    // bounce velocity when landing on bouncing platform
    bounceVelocity: 800,

    // disappearing platform time before disappearing, in milliseconds
    disappearTime: 1000
}

playGame.ts

The game itself, the biggest class, game logic is stored here, including platform collision management, mostly in handleCollision method:

// 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, platform.getBounds().y - 100, "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);

        // set platform bounding box size to make it way taller than platform sprite
        platform.setBodySize(platform.displayWidth / platform.scaleX, 400, false);

        // the platform is higher
        platform.isHigher = true;

        // random platform type
        platform.platformType = Phaser.Math.Between(0, 2);

        // set platform tint according to platform type
        platform.setTint(GameOptions.platformColors[platform.platformType]);
    }

    // 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 handle collisions
    handleCollision(body1: Phaser.GameObjects.GameObject, body2: Phaser.GameObjects.GameObject): void {
 
        // first body is the hero
        let hero: PlayerSprite = body1 as PlayerSprite;
 
        // second body is the platform
        let platform: PlatformSprite = body2 as PlatformSprite;

        // the following code will be executed only if the hero touches the platform on its upper side
        if (hero.body.touching.down && platform.body.touching.up) {

            // different actions according to platform type
            switch (platform.platformType) {

                // breakable platform
                case 1:

                    // if the platform is not already fading out...
                    if (!platform.isFadingOut) {

                        // flag the platform as a fading out platform
                        platform.isFadingOut = true;

                        // add a tween to fade the platform out
                        this.tweens.add({
                            targets: platform,
                            alpha: 0,
                            ease: 'bounce',
                            duration: GameOptions.disappearTime,
                            callbackScope: this,
                            onComplete: function() {

                                // when the tween is over, platform is not fading out anymore
                                platform.isFadingOut = false;

                                // reset platform opacity to 1
                                platform.alpha = 1;

                                // recycle the platform
                                this.positionPlatform(platform);
                            }
                        });
                    }
                    break;
                
                // bouncy platform
                case 2:

                    // make the hero jump changing vertical velocity
                    hero.setVelocityY(GameOptions.bounceVelocity * -1);
                    break;
            }
        }
    }

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

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

        // get hero lower bounds
        let heroLowerBounds: number = this.hero.getBounds().bottom;

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

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

            // get platform bounds
            let platformBounds: Phaser.Geom.Rectangle = platform.getBounds();

            // if hero bottom side is below platform top side and platform size is higher...
            if (platformBounds.top < heroLowerBounds && platform.isHigher) {

                // reset platform body size
                platform.setBodySize(platform.displayWidth / platform.scaleX, platform.displayHeight, false); 
                
                // platform is no longer higher
                platform.isHigher = false;
            }

            // if a platform leaves the stage to the upper side...
            if (platformBounds.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, we added two properties to see if the platform is fading out and to store platform type.

// 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;

    // is the platform body higher?
    isHigher: Boolean = false;

    // platform type
    platformType: number = 0;

    // is the platform fading out?
    isFadingOut: Boolean = false;

    // 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 we have bouncing and disappearing platforms. Do you have some other platform types to add to the game? Let me know. Meanwhile, 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

215 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
// Stairs
// 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