Do you like my tutorials?

Then consider supporting me on Ko-fi

Talking about Game development, HTML5, Javascript and Phaser.

Normally, when you build a game, there is a routine which is called at each frame which updates the content to display. Think about a platformer, with characters moving at each frame. Those characters have a speed, and they move towards a direction by a certain amount of pixels every frame. Let’s see a simplified version of the circular endless runner I showed you during the last weeks:
var game;
var gameOptions = {
    bigCircleRadius: 250,
    playerRadius: 25,
    playerSpeed: 1
}
window.onload = function() {
    var gameConfig = {
        thpe: Phaser.CANVAS,
        width: 800,
        height: 800,
        scene: [playGame]
    }
    game = new Phaser.Game(gameConfig);
    window.focus()
    resize();
    window.addEventListener("resize", resize, false);
}
class playGame extends Phaser.Scene{
    constructor(){
        super("PlayGame");
    }
    preload(){
        this.load.image("bigcircle", "bigcircle.png");
        this.load.image("player", "player.png");
    }
    create(){
        this.bigCircle = this.add.sprite(game.config.width / 2, game.config.height / 2, "bigcircle");
        this.bigCircle.displayWidth = gameOptions.bigCircleRadius * 2;
        this.bigCircle.displayHeight = gameOptions.bigCircleRadius * 2;
        this.player = this.add.sprite(game.config.width / 2, game.config.height / 2 - gameOptions.bigCircleRadius - gameOptions.playerRadius, "player");
        this.player.displayWidth = gameOptions.playerRadius * 2;
        this.player.displayHeight = gameOptions.playerRadius * 2;
        this.player.currentAngle = -90;
    }
    update(){
        this.player.currentAngle = Phaser.Math.Angle.WrapDegrees(this.player.currentAngle + gameOptions.playerSpeed);
        var radians = Phaser.Math.DegToRad(this.player.currentAngle);
        var distanceFromCenter = gameOptions.bigCircleRadius + gameOptions.playerRadius;
        this.player.x = this.bigCircle.x + distanceFromCenter * Math.cos(radians);
        this.player.y = this.bigCircle.y + distanceFromCenter * Math.sin(radians);
        var revolutions = gameOptions.bigCircleRadius / gameOptions.playerRadius + 1;
        this.player.angle = this.player.currentAngle * revolutions;
    }
}
// pure javascript to scale the game
function resize() {
    var canvas = document.querySelector("canvas");
    var windowWidth = window.innerWidth;
    var windowHeight = window.innerHeight;
    var windowRatio = windowWidth / windowHeight;
    var gameRatio = game.config.width / game.config.height;
    if(windowRatio < gameRatio){
        canvas.style.width = windowWidth + "px";
        canvas.style.height = (windowWidth / gameRatio) + "px";
    }
    else{
        canvas.style.width = (windowHeight * gameRatio) + "px";
        canvas.style.height = windowHeight + "px";
    }
}

Player speed is set in angles per frame at line 5. Being player speed 1, we will need 360 frames to travel all the way around the big circle.
What do you see here? If your browser runs at 60 frames per second, you should see the small circle moving all the way around the big circle in 360 frames / 60 frames per second = 6 seconds. So let’s say this is the speed you want, and you will code the entire gameplay having in mind the small circle will move all the way around the big circle in 6 seconds, assuming your browser will always render the game at 60 frames per second. What happens if the game for some reason lags, or your game renders faster than 60fps? Look at this script:
var game;
var gameOptions = {
    bigCircleRadius: 250,
    playerRadius: 25,
    playerSpeed: 1
}
window.onload = function() {
    var gameConfig = {
        thpe: Phaser.CANVAS,
        width: 800,
        height: 800,
        scene: [playGame]
    }
    game = new Phaser.Game(gameConfig);
    window.focus()
    resize();
    window.addEventListener("resize", resize, false);
}
class playGame extends Phaser.Scene{
    constructor(){
        super("PlayGame");
    }
    preload(){
        this.load.image("bigcircle", "bigcircle.png");
        this.load.image("player", "player.png");
    }
    create(){
        this.bigCircle = this.add.sprite(game.config.width / 2, game.config.height / 2, "bigcircle");
        this.bigCircle.displayWidth = gameOptions.bigCircleRadius * 2;
        this.bigCircle.displayHeight = gameOptions.bigCircleRadius * 2;
        this.player = this.add.sprite(game.config.width / 2, game.config.height / 2 - gameOptions.bigCircleRadius - gameOptions.playerRadius, "player");
        this.player.displayWidth = gameOptions.playerRadius * 2;
        this.player.displayHeight = gameOptions.playerRadius * 2;
        this.player.currentAngle = -90;
    }
    update(){
        if(Phaser.Math.Between(0, 3) == 0){
            this.player.currentAngle = Phaser.Math.Angle.WrapDegrees(this.player.currentAngle + gameOptions.playerSpeed);
            var radians = Phaser.Math.DegToRad(this.player.currentAngle);
            var distanceFromCenter = gameOptions.bigCircleRadius + gameOptions.playerRadius;
            this.player.x = this.bigCircle.x + distanceFromCenter * Math.cos(radians);
            this.player.y = this.bigCircle.y + distanceFromCenter * Math.sin(radians);
            var revolutions = gameOptions.bigCircleRadius / gameOptions.playerRadius + 1;
            this.player.angle = this.player.currentAngle * revolutions;
        }
    }
}
// pure javascript to scale the game
function resize() {
    var canvas = document.querySelector("canvas");
    var windowWidth = window.innerWidth;
    var windowHeight = window.innerHeight;
    var windowRatio = windowWidth / windowHeight;
    var gameRatio = game.config.width / game.config.height;
    if(windowRatio < gameRatio){
        canvas.style.width = windowWidth + "px";
        canvas.style.height = (windowWidth / gameRatio) + "px";
    }
    else{
        canvas.style.width = (windowHeight * gameRatio) + "px";
        canvas.style.height = windowHeight + "px";
    }
}
If you look at the highlighted code, you’ll see we are simulating a lag by skipping frames on a random basis. Look at the game now:
How long does the small circle take to move all the way around the big circle? Still six seconds? No, way more. This happens because we are updating the game at each frame, and if the frame rate reduces, the game gets slower. Well, then just don’t reduce the frame rate, it’s simple! Yes, in a perfect world each device renders the game with the same amount of frames per second, which never changes no matter what happens. But we are living in the actual world. That’s why we are going to handle game movement using time. Look at this script:
var game;
var gameOptions = {
    bigCircleRadius: 250,
    playerRadius: 25,
    playerSpeed: 6000
}
window.onload = function() {
    var gameConfig = {
        thpe: Phaser.CANVAS,
        width: 800,
        height: 800,
        scene: [playGame]
    }
    game = new Phaser.Game(gameConfig);
    window.focus()
    resize();
    window.addEventListener("resize", resize, false);
}
class playGame extends Phaser.Scene{
    constructor(){
        super("PlayGame");
    }
    preload(){
        this.load.image("bigcircle", "bigcircle.png");
        this.load.image("player", "player.png");
    }
    create(){
        this.bigCircle = this.add.sprite(game.config.width / 2, game.config.height / 2, "bigcircle");
        this.bigCircle.displayWidth = gameOptions.bigCircleRadius * 2;
        this.bigCircle.displayHeight = gameOptions.bigCircleRadius * 2;
        this.player = this.add.sprite(game.config.width / 2, game.config.height / 2 - gameOptions.bigCircleRadius - gameOptions.playerRadius, "player");
        this.player.displayWidth = gameOptions.playerRadius * 2;
        this.player.displayHeight = gameOptions.playerRadius * 2;
        this.player.currentAngle = -90;
    }
    update(t, dt){
        var deltaAngle = 360 * (dt / gameOptions.playerSpeed);
        this.player.currentAngle = Phaser.Math.Angle.WrapDegrees(this.player.currentAngle + deltaAngle);
        var radians = Phaser.Math.DegToRad(this.player.currentAngle);
        var distanceFromCenter = gameOptions.bigCircleRadius + gameOptions.playerRadius;
        this.player.x = this.bigCircle.x + distanceFromCenter * Math.cos(radians);
        this.player.y = this.bigCircle.y + distanceFromCenter * Math.sin(radians);
        var revolutions = gameOptions.bigCircleRadius / gameOptions.playerRadius + 1;
        this.player.angle = this.player.currentAngle * revolutions;
    }
}
// pure javascript to scale the game
function resize() {
    var canvas = document.querySelector("canvas");
    var windowWidth = window.innerWidth;
    var windowHeight = window.innerHeight;
    var windowRatio = windowWidth / windowHeight;
    var gameRatio = game.config.width / game.config.height;
    if(windowRatio < gameRatio){
        canvas.style.width = windowWidth + "px";
        canvas.style.height = (windowWidth / gameRatio) + "px";
    }
    else{
        canvas.style.width = (windowHeight * gameRatio) + "px";
        canvas.style.height = windowHeight + "px";
    }
}
At line 5 now the speed is no longer defined in degrees per frame, but in milliseconds needed to move all the way around the big circle. Still 6 seconds. Then at line 36 we have two arguments, representing respectively the amount in milliseconds since the game started, and the amount in milliseconds since last frame was rendered. Line 37 determines the amount of degrees the small circle should move according to elapsed time and circle speed. Look at the result:
The small circle moves again all the way around the big circle in 6 seconds. Let’s add some fake lag:
var game;
var gameOptions = {
    bigCircleRadius: 250,
    playerRadius: 25,
    playerSpeed: 6000
}
window.onload = function() {
    var gameConfig = {
        thpe: Phaser.CANVAS,
        width: 800,
        height: 800,
        scene: [playGame]
    }
    game = new Phaser.Game(gameConfig);
    window.focus()
    resize();
    window.addEventListener("resize", resize, false);
}
class playGame extends Phaser.Scene{
    constructor(){
        super("PlayGame");
    }
    preload(){
        this.load.image("bigcircle", "bigcircle.png");
        this.load.image("player", "player.png");
    }
    create(){
        this.bigCircle = this.add.sprite(game.config.width / 2, game.config.height / 2, "bigcircle");
        this.bigCircle.displayWidth = gameOptions.bigCircleRadius * 2;
        this.bigCircle.displayHeight = gameOptions.bigCircleRadius * 2;
        this.player = this.add.sprite(game.config.width / 2, game.config.height / 2 - gameOptions.bigCircleRadius - gameOptions.playerRadius, "player");
        this.player.displayWidth = gameOptions.playerRadius * 2;
        this.player.displayHeight = gameOptions.playerRadius * 2;
        this.player.currentAngle = -90;
        this.lastUpdate = 0;
    }
    update(t){
        if(Phaser.Math.Between(0, 3) == 0){
            var dt = t - this.lastUpdate;
            this.lastUpdate = t;
            var deltaAngle = 360 * (dt / gameOptions.playerSpeed);
            this.player.currentAngle = Phaser.Math.Angle.WrapDegrees(this.player.currentAngle + deltaAngle);
            var radians = Phaser.Math.DegToRad(this.player.currentAngle);
            var distanceFromCenter = gameOptions.bigCircleRadius + gameOptions.playerRadius;
            this.player.x = this.bigCircle.x + distanceFromCenter * Math.cos(radians);
            this.player.y = this.bigCircle.y + distanceFromCenter * Math.sin(radians);
            var revolutions = gameOptions.bigCircleRadius / gameOptions.playerRadius + 1;
            this.player.angle = this.player.currentAngle * revolutions;
        }
    }
}
// pure javascript to scale the game
function resize() {
    var canvas = document.querySelector("canvas");
    var windowWidth = window.innerWidth;
    var windowHeight = window.innerHeight;
    var windowRatio = windowWidth / windowHeight;
    var gameRatio = game.config.width / game.config.height;
    if(windowRatio < gameRatio){
        canvas.style.width = windowWidth + "px";
        canvas.style.height = (windowWidth / gameRatio) + "px";
    }
    else{
        canvas.style.width = (windowHeight * gameRatio) + "px";
        canvas.style.height = windowHeight + "px";
    }
}
At line 35 we need a property to save the last update since the beginning of the game, in milliseconds. Line 37 now features only the amount of milliseconds passed since the game started. We are faking a lag, so there’s no point in getting the amount of milliseconds passed since last frame update. Lines 39 and 40 calculate the amount of milliseconds passed since last active frame and update the property to save the current time. Look at the result:
Yes, the game still lags, but the small circle still moves all the way around the big circle in 6 seconds. The game is not slower. It just runs in at a slower amount of frames per seconds. And this is the way you should move your characters. Download all the examples featured in this post and play with them.

Never miss an update! Subscribe, and I will bother you by email only when a new game or full source code comes out.