Talking about Serious Scramblers game, Game development, HTML5, Javascript, Phaser and TypeScript.
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.
Never miss an update! Subscribe, and I will bother you by email only when a new game or full source code comes out.