Talking about Circular endless runner game, Game development, HTML5, Javascript, Phaser and TypeScript.
With Phaser 4 already in Beta and hopefully close to the final release, I wanted to test it and see if my old Phaser 3 scripts would run in the new version.
So I took my circular endless runner prototype, whose final version is available for free on this Gumroad page, and installed Phaser 4 beta.
All I had to do was a simple
npm install –save phaser@beta
And my package.json file was updated with latest beta version:
{
"name": "ts_multicontrol",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"development": "webpack serve --open --config webpack.development.js",
"distribution": "webpack --config webpack.distribution.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"phaser": "^4.0.0-beta.2"
},
"devDependencies": {
"clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^9.0.1",
"ts-loader": "^9.2.6",
"typescript": "^4.9.5",
"webpack": "^5.88.2",
"webpack-cli": "^4.10.0",
"webpack-dev-server": "^4.15.1"
}
}
The source code remained the same used in HTML5 prototype of a circular endless runner built with Phaser – final version in TypeScript with more optimization post, I just added some lines to playGame.ts to add the blur filter as seen on this sandbox example, just to test it in a real world example.
playGame.ts
I had to add a //@ts-ignore to prevent errors as internal was not recognized, but everything worked fine.
// THE GAME ITSELF
// modules to import
import { GameOptions } from '../gameOptions'; // game options
import { PhysicsCircle } from '../physicsCircle'; // physics circle
import { PhysicsSpike } from '../physicsSpike'; // physics spike
// PlayGame class extends Phaser.Scene class
export class PlayGame extends Phaser.Scene {
constructor() {
super({
key : 'PlayGame'
});
}
player : PhysicsCircle; // the player
spikeArray : PhysicsSpike[]; // group with all spikes
trailEmitter : Phaser.GameObjects.Particles.ParticleEmitter // trail particle emitter
// method to be called once the instance has been created
create() : void {
//@ts-ignore
const parallelFilters = this.cameras.main.filters.internal.addParallelFilters();
parallelFilters.top.addThreshold(0.5, 1);
parallelFilters.top.addBlur();
parallelFilters.blend.blendMode = Phaser.BlendModes.ADD;
parallelFilters.blend.amount = 0;
this.tweens.add({
targets: parallelFilters.blend,
amount: 5,
duration: 1000,
yoyo: true,
repeat: -1,
ease: 'Sine.easeInOut'
});
// set some custom data
this.data.set({
gameOver : false, // is the game over?
centerX : this.game.config.width as number / 2, // horizontal center of the game
centerY : this.game.config.height as number / 2 // vertical center of the game
})
// place and resize big circle
const bigCircle : Phaser.GameObjects.Sprite = this.add.sprite(this.data.get('centerX'), this.data.get('centerY'), 'bigcircle');
bigCircle.displayWidth = GameOptions.bigCircleRadius * 2;
bigCircle.displayHeight = GameOptions.bigCircleRadius * 2;
// initialize spikeArray vector
this.spikeArray = [];
// place spikes
for (let i : number = 0; i < 3 * GameOptions.spikesPerQuadrant; i ++) {
// create a spike
const spike : PhysicsSpike = new PhysicsSpike(this);
// addi the spike to spike array
this.spikeArray.push(spike);
// determine the quadrant where to place the spike
// 0 : bottom-right
// 1 : bottom-left
// 2 : top-left
// 3 : top-right
const quadrant : number = Math.floor(i % 3);
// place the spike in the given quadrant.
// I don't want spikes in the quadrant 2 at the beginning, because it's the quadrant where the player spawns
spike.place(quadrant != 2 ? quadrant : 3);
}
// create the player
this.player = new PhysicsCircle(this);
// create the particle trail emitter and make it follow the player
this.trailEmitter = this.add.particles(this.player.x, this.player.y, 'particle', {
lifespan : 900, // particle life span, in mmilliseconds
alpha : {
start : 1, // alpha start value, 0 = transparent; 1 = opaque
end : 0 // alpha end value
},
scale : {
start : 1, // scale start value
end : 0.8 // scale end value
},
quantity : 1, // amount of particle to be fired
frequency : 150 // particle frequency, in milliseconds
}).startFollow(this.player);
// handle player input on touch or clicl
this.input.on('pointerdown', () => {
// make player jump
this.player.jump();
});
}
// method to be called when the game is over
gameOver() : void {
// set gameOver data to true
this.data.set('gameOver', true);
// shake the camera
this.cameras.main.shake(800, 0.01);
// hide the player
this.player.setVisible(false);
// stop trail emitter
this.trailEmitter.stop();
// add a particle explosion effect
this.add.particles(this.player.x, this.player.y, 'particle', {
lifespan : 900, // particle life span, in milliseconds
alpha : {
start : 1, // alpha start value, 0 = transparent; 1 = opaque
end : 0 // alpha end value
},
scale : {
start : 0.6, // scale start value
end : 0.02 // scale end value
},
speed: {
min : -150, // minimum speed, in pixels per second
max : 150 // maximum speed, in pixels per second
},
}).explode(100);
// add a timer event
this.time.addEvent({
delay : 2000, // delay, in milliseconds
callback : () => { // callback function
// start PlayGame scene
this.scene.start('PlayGame');
}
});
}
// metod to be called at each frame
// time : time passed since the beginning, in milliseconds
// deltaTime : time passed since last frame, in milliseconds
update(time : number, deltaTime : number) {
// if the game is over, do nothing
if (this.data.get('gameOver')) {
return;
}
// move the player, according to deltaTime
this.player.move(deltaTime / 1000);
// loop through all spikes
this.spikeArray.forEach((spike : PhysicsSpike) => {
// get angle difference between spike and player
const angleDifference : number = Math.abs(Phaser.Math.Angle.ShortestBetween(spike.angle, this.player.currentAngle));
// if the player is not approaching the spike and it's close enough...
if (!spike.approaching && angleDifference < GameOptions.closeToSpike) {
// player is now approaching the spike
spike.approaching = true;
}
// if the player is approaching the spike...
if (spike.approaching) {
// if spike triangle shape and player circle shape intersect...
if (Phaser.Geom.Intersects.TriangleToCircle(spike.triangle, this.player.circle)) {
// the game is over!
this.gameOver();
}
// if we are getting too far from the spike...
if (angleDifference > GameOptions.farFromSpike) {
// recycle the spike making it disappear
spike.disappear();
}
}
});
}
}
And this is the result:
Tap or click to jump and avoid the spikes. You can perform double and even triple jump.
Also have a look at the glowing blur filter, which is useless but I added it just to test the game.
It is a bit too early to tell, but at the moment the transition from Phaser 3 to Phaser 4 seems to be painless, allowing us to use the new features without rewriting large portions of code.
I look forward to getting my hands on the final release, meanwhile get the full source code along with the entire wepack project.
Don’t know where to start developing with Phaser and TypeScript? I’ll explain it to you step by step in this free minibook.
Never miss an update! Subscribe, and I will bother you by email only when a new game or full source code comes out.