Talking about Game development, HTML5, Javascript, Phaser and TypeScript.
Last week I published a “Block it” prototype with walls activable when the player touches the canvas, and an energy system which made energy drain as long as the player touches the canvas.
This post covers in detail the process of handling a consuming energy bar, or measuring the time a player is pressing a button.
This process is divided in 3 steps:
1 – The player start touching the canvas, and if the amount of energy is greater than zero, we have to save the timestamp of this event.
2 – The player keeps touching the canvas. At this point, each frame we determine the difference between current timestamp and the timestamp measured at step 1. This amount of time should be smaller than the time required to consume all energy, which as this time shouldn’t be touched. It’s enough for us to check if the amount of time is smaller than the time required to consume all energy.
2a – If the amount of time passed is smaller than the time to consume all energy, everything is ok and we still have energy.
2b – If the amount of time passed is greater than – or equal to – the time required to consume all energy, then we ran out of energy.
3 – The player stops touching the canvas. Now we determine the difference between current timestamp and the timestamp measured at step 1. At this time we update the amount of energy, and we wait for the player to start touching again the canvas, at step 1.
Look at a working prototype:
Press and hold the canvas to drain energy, stop pressing to stop draining. Once you do not have energy, press anywhere to restart the example.
In your console you will see the log of what’s happening.
Now look at the completely commented source code, which consists in one html file, one css file and 4 TypeScript files:
index.html
The web page which hosts the game, to be run inside thegame element.
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="initial-scale=1, maximum-scale=1">
<link rel="stylesheet" href="style.css">
</style>
<script src="main.js"></script>
</head>
<body>
<div id="thegame"></div>
</body>
</html>
style.css
The cascading style sheets of the main web page.
* {
padding : 0;
margin : 0;
}
canvas {
touch-action : none;
-ms-touch-action : none;
}
gameOptions.ts
Configurable game options. It’s a good practice to place all configurable game options, if possible, in a single and separate file, for a quick tuning of the game.
// CONFIGURABLE GAME OPTIONS
export const GameOptions = {
// starting energy
startingEnergy : 5000,
// energy draining per second
energyPerSecond : 1000,
// energy bar size
energyBarSize: {
width : 500,
height : 50
}
}
main.ts
This is where the game is created, with all Phaser related options.
// MAIN GAME FILE
// modules to import
import Phaser from 'phaser';
import { PreloadAssets } from './preloadAssets';
import { PlayGame } from './playGame';
// object to initialize the Scale Manager
const scaleObject : Phaser.Types.Core.ScaleConfig = {
autoCenter : Phaser.Scale.CENTER_BOTH,
parent : 'thegame',
width : 600,
height : 300
}
// game configuration object
const configObject : Phaser.Types.Core.GameConfig = {
type : Phaser.AUTO,
backgroundColor : 0x444444,
scale : scaleObject,
scene : [PreloadAssets, PlayGame]
}
// the game itself
new Phaser.Game(configObject);
preloadAssets.ts
Here we preload all assets to be used in the game, in this case the energy bar.
// CLASS TO PRELOAD ASSETS
// this class extends Scene class
export class PreloadAssets extends Phaser.Scene {
// constructor
constructor() {
super({
key : 'PreloadAssets'
});
}
// method to be execute during class preloading
preload(): void {
// this is how we preload an image
this.load.image('bar', 'assets/bar.png');
}
// method to be called once the instance has been created
create(): void {
// call PlayGame class
this.scene.start('PlayGame');
}
}
playGame.ts
Main game file, all game logic is stored here.
// THE GAME ITSELF
import { GameOptions } from "./gameOptions";
// this class extends Scene class
export class PlayGame extends Phaser.Scene {
// variable to save the time the player touched the input to activate the wall
downTime : number;
// walls energy
energy : number;
// text object to display the energy
energyText : Phaser.GameObjects.Text;
// is the energy draining?
energyDraining : boolean;
// tile sprite to represent the energy bar
energyBar : Phaser.GameObjects.TileSprite;
// constructor
constructor() {
super({
key: 'PlayGame'
});
}
// method to be executed when the scene has been created
create() : void {
// we aren't draining energy
this.energyDraining = false;
// fill the energy according to game options
this.energy = GameOptions.startingEnergy;
// just a couple of variables to store game width and height
let gameWidth : number = this.game.config.width as number;
let gameHeight : number = this.game.config.height as number;
// another couple of variables to have the energy bar centered
let barPositionX : number = (gameWidth - GameOptions.energyBarSize.width) / 2;
let barPositionY : number = (gameHeight - GameOptions.energyBarSize.height) / 2;
// energy backgroud is a fixed, semi trasparent energy bar just to make the prototype look better
let energyBackground : Phaser.GameObjects.TileSprite = this.add.tileSprite(barPositionX, barPositionY, GameOptions.energyBarSize.width, GameOptions.energyBarSize.height, 'bar');
// set energy background alpha to almost transparent
energyBackground.setAlpha(0.2);
// set energy background origin to left, top
energyBackground.setOrigin(0);
// actual energy bar
this.energyBar = this.add.tileSprite(barPositionX, barPositionY, GameOptions.energyBarSize.width, GameOptions.energyBarSize.height, 'bar');
// set energy background origin to left, top
this.energyBar.setOrigin(0);
// get energy bar bounds
let energyBarBounds : Phaser.Geom.Rectangle = this.energyBar.getBounds();
// add the text object to show the amount of energy
this.energyText = this.add.text(energyBarBounds.right, energyBarBounds.bottom + 10, '', {
font : '32px Arial'
});
// set energy text origin to right, top
this.energyText.setOrigin(1, 0);
// update energy text;
this.updateEnergyText(this.energy);
// wait for input start to call startDraining method
this.input.on('pointerdown', this.startDraining, this);
// wait for input end to call stopDraining method
this.input.on('pointerup', this.stopDraining, this);
// log variables
this.log('Scene started');
}
// method to update energy text
updateEnergyText(energy : number) : void {
// set energy text to energy value itself or zero
this.energyText.setText(Math.max(0, energy).toString() + '/' + GameOptions.startingEnergy.toString());
}
// method to update energy bar
updateEnergyBar(energy : number) : void {
// set energy bar display width according to energy
this.energyBar.displayWidth = GameOptions.energyBarSize.width / GameOptions.startingEnergy * energy;
}
// method to start draining
startDraining(e : Phaser.Input.Pointer) : void {
// do we have energy?
if (this.energy > 0) {
// save the current timestamp
this.downTime = e.downTime;
// now we are draining energy
this.energyDraining = true;
this.log('Start draining');
}
// we do not have energy
else {
// output some log
this.log('About to restart scene');
// just restart the scene
this.scene.start('PlayGame');
}
}
// method to stop draining
stopDraining(e : Phaser.Input.Pointer) : void {
// are we draining?
if (this.energyDraining) {
// we aren't draining anymore
this.energyDraining = false;
// determine how long the input has been pressed
let ellapsedTime : number = e.upTime - e.downTime;
// subtract the ellapsed time from energy
this.energy -= Math.min(this.energy, Math.round(ellapsedTime / 1000 * GameOptions.energyPerSecond));
// update energy text
this.updateEnergyText(this.energy);
// update energy bar
this.updateEnergyBar(this.energy);
// output some log
this.log('Stop draining');
}
}
// method to be called at each frame
update(time : number) : void {
// are we draining energy?
if (this.energyDraining) {
// determine remaining energy subtracting from current energy the amount of time we are pressing the input
let remainingEnergy : number = this.energy - Math.round((time - this.downTime) / 1000 * GameOptions.energyPerSecond);
// if remaining energy is less than zero...
if (remainingEnergy < 0) {
// set energy to zero
remainingEnergy = 0;
}
// update energy text
this.updateEnergyText(remainingEnergy);
// update energy bar
this.updateEnergyBar(remainingEnergy);
// output some log
this.log('Draining, remaining energy: ' + remainingEnergy.toString());
}
}
// prompt some output to the console
log(text : string) : void {
console.log(text);
console.log('Energy :', this.energy);
console.log('Energy draining :', this.energyDraining);
console.log('--------------------------');
}
}
Now the concept about handling an energy bar should be simpler. Download the source code.
Never miss an update! Subscribe, and I will bother you by email only when a new game or full source code comes out.