Simulate a keyboard input delay with Phaser (why on earth? You’ll see)
Talking about Input Delay game, HTML5, Javascript, Phaser and TypeScript.
I was messing around with Phaser as usual when an idea popped into my head: why not find a system to add delay to user input?
I know it may seem strange in a world where we struggle to minimize lag, but I have an idea for my next game that will take advantage of it.
I started with this official example and simulated a buffer to add a 2.5s delay to all user input, and this is the result:
Focus on the canvas and try to press P, H, A, S, E or R keys. Nothing happens, apparently, but all your input will be processed with 2.5 seconds delay, highlighting the proper letters.
Look at the commented source code which consists in one HTML file, one CSS file and threeTypeScript files.
index.html
The web page which hosts the game, to be run inside thegame element.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title></title>
<script type="module" src="/src/main.ts"></script>
</head>
<body>
<div id="thegame"></div>
</body>
</html>
style.css
The cascading style sheets of the main web page.
/* remove margin and padding from all elements */
* {
padding : 0;
margin : 0;
}
/* set body background color */
body {
background-color : #000000;
}
/* Disable browser handling of all panning and zooming gestures. */
canvas {
touch-action : none;
}
main.ts
This is where the game is created, with all Phaser related options.
import 'phaser'; // Phaser
import { PreloadAssets } from './scenes/preloadAssets'; // PreloadAssets scene
import { PlayGame } from './scenes/playGame'; // PlayGame scene
import './style.css'; // main page stylesheet
// game configuration object
let configObject : Phaser.Types.Core.GameConfig = {
scale : {
mode : Phaser.Scale.FIT, // set game size to fit the entire screen
autoCenter : Phaser.Scale.CENTER_BOTH, // center the canvas both horizontally and vertically in the parent div
parent : 'thegame', // parent div element
width : 1024, // game width, in pixels
height : 400, // game height, in pixels
},
backgroundColor : 0xefefef, // game background color
scene : [
PreloadAssets, // scene to preload all game assets
PlayGame // the game itself
]
};
new Phaser.Game(configObject);
scenes > preloadAssets.ts
Here we preload all assets to be used in the game.
// CLASS TO PRELOAD ASSETS
// PreloadAssets class extends Phaser.Scene class
export class PreloadAssets extends Phaser.Scene {
// constructor
constructor() {
super({
key : 'PreloadAssets'
});
}
// method to be called during class preloading
preload() : void {
this.load.image('P', 'assets/sprites/p.png');
this.load.image('H', 'assets/sprites/h.png');
this.load.image('A', 'assets/sprites/a.png');
this.load.image('S', 'assets/sprites/s.png');
this.load.image('E', 'assets/sprites/e.png');
this.load.image('R', 'assets/sprites/r.png');
}
// method to be executed when the scene is created
create() : void {
// start PlayGame scene
this.scene.start('PlayGame');
}
}
scenes > playGame.ts
Main game file, all game logic is stored here.
// THE GAME ITSELF
// PlayGame class extends Phaser.Scene class
export class PlayGame extends Phaser.Scene {
constructor() {
super('PlayGame');
}
keysPressed : string[]; // array to store keys currently being pressed
buffer : any[]; // array to buffer user input
delay : number; // input delay, in milliseconds
// PHASER letters
imageP : Phaser.GameObjects.Sprite;
imageH : Phaser.GameObjects.Sprite;
imageA : Phaser.GameObjects.Sprite;
imageS : Phaser.GameObjects.Sprite;
imageE : Phaser.GameObjects.Sprite;
imageR : Phaser.GameObjects.Sprite;
// method to be executed when the scene is created
create() : void {
// set input delay to 2.5 seconds
this.delay = 2500;
// initialize arrays
this.keysPressed = [];
this.buffer = [];
// place images on the stage
this.imageP = this.add.sprite(50, 100, 'P').setOrigin(0);
this.imageH = this.add.sprite(200, 100, 'H').setOrigin(0);
this.imageA = this.add.sprite(350, 100, 'A').setOrigin(0);
this.imageS = this.add.sprite(500, 100, 'S').setOrigin(0);
this.imageE = this.add.sprite(650, 100, 'E').setOrigin(0);
this.imageR = this.add.sprite(800, 100, 'R').setOrigin(0);
// keys allowed: only the letters to form 'PHASER'
const allowedKeys : string[] = ['KeyP', 'KeyH', 'KeyA', 'KeyS', 'KeyE', 'KeyR']
// initialize keyboard plugin
const keyboard : Phaser.Input.Keyboard.KeyboardPlugin = this.input.keyboard as Phaser.Input.Keyboard.KeyboardPlugin;
// event to be triggered when a key is pressed
keyboard.on('keydown', (event : any) => {
// is this an allowed key and still missing in keyPressed array?
if (allowedKeys.indexOf(event.code) != -1 && this.keysPressed.indexOf(event.code) == -1) {
// insert key code in keysPressed array
this.keysPressed.push(event.code);
// insert the event in the bugger
this.buffer.push({
code : event.code, // which key?
start : event.timeStamp, // when was the key pressed?
end : Infinity // when was the key released (not yet, at the moment)
});
}
});
// event to be triggered when a key is released
keyboard.on('keyup', (event : any) => {
// is this an allowed key?
if (allowedKeys.indexOf(event.code) != -1) {
// remove the key from keysPressed array
this.keysPressed = this.keysPressed.filter(element => element !== event.code);
// find the index of the element with the same key code and Inifinity as end time
const index : number = this.buffer.findIndex(element => (element.code == event.code && element.end == Infinity));
// did we find it (we should!)
if (index > -1) {
// update end time with current timestamp
this.buffer[index].end = event.timeStamp;
}
}
})
}
// method to be executed at each frame
// time :time passed since the start of the scene, in milliseconds
update(time: number): void {
// array with delayed keys pressed
const delayedKeysPressed : string[] = [];
// looping through all buffer
this.buffer.forEach(element => {
// if start time, once added the delay, is less than curren time...
if (element.start + this.delay < time) {
// place the key in delayedKeysPressed array
delayedKeysPressed.push((element.code));
}
})
// remove from buffer all elements whose end timestamp, once added the delay, is greater tan current time
this.buffer = this.buffer.filter(element => element.end + this.delay > time);
// highlight PHASER letters according to delayed keys pressed
this.imageP.setAlpha((delayedKeysPressed.indexOf('KeyP') > -1) ? 1 : 0.2);
this.imageH.setAlpha((delayedKeysPressed.indexOf('KeyH') > -1) ? 1 : 0.2);
this.imageA.setAlpha((delayedKeysPressed.indexOf('KeyA') > -1) ? 1 : 0.2);
this.imageS.setAlpha((delayedKeysPressed.indexOf('KeyS') > -1) ? 1 : 0.2);
this.imageE.setAlpha((delayedKeysPressed.indexOf('KeyE') > -1) ? 1 : 0.2);
this.imageR.setAlpha((delayedKeysPressed.indexOf('KeyR') > -1) ? 1 : 0.2);
}
}
How would you use an input delay to your games? Download the full source code along with the Vite project. Don’t know what I am talking about? There’s a free minibook to get you started.
Never miss an update! Subscribe, and I will bother you by email only when a new game or full source code comes out.