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 1, 2, 3 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.