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.