HTML5 prototype of “Run Around” game builth with Phaser with no physics engines

Read all posts about "" game

Did you play Run Around?

In the original game you can find both for iOS and Android, you play as a stickman as you run around the circle and jump over obstacles. Complete the circle to pass levels.

This particular kind of game can be described as a circular endless runner, and I already developed a circular endless runner prototype, the only difference is this time the player is running inside the circle, painting it, and we also need to keep track of the amount of circumference the player already painted.

Look at this prototype. I kept the feature to make the player jump and double jump:

Tap to jump and double jump and see how the player paints the big circle.

The game features no physics, every circle movement has been done using trigonometry.

The trickiest part was to draw the painted path, but I just acted like it was a straight line, keeping track of the painted arcs/segments and merging them into bigger arcs/segments with an external script, because I did not want to reinvent the wheel.

Have a look at the commented source code:

// the game itself
let game;

// global object with configuration options
let gameOptions = {

    // radius of the big circle, in pixels
    bigCircleRadius: 300,

    // thickness of the big circle, in pixels
    bigCircleThickness: 20,

    // radius of the player, in pixels
    playerRadius: 25,

    // player speed, in degrees per frame
    playerSpeed: 0.6,

    // world gravity
    worldGravity: 0.8,

    // jump force of the single and double jump
    jumpForce: [12, 8]
}

window.onload = function() {
    let gameConfig = {
        type: Phaser.CANVAS,
        backgroundColor: 0x444444,
        scale: {
            mode: Phaser.Scale.FIT,
            autoCenter: Phaser.Scale.CENTER_BOTH,
            parent: "thegame",
            width: 800,
            height: 800
        },
        scene: playGame
    }
    game = new Phaser.Game(gameConfig);
    window.focus();
}
class playGame extends Phaser.Scene{
    constructor(){
        super("PlayGame");
    }
    preload(){
        this.load.image("player", "player.png");
    }
    create(){

        // array to store all painted arcs
        this.paintedArcs = [];

        // calculate the distance from the center of the canvas and the big circle
        this.distanceFromCenter = gameOptions.bigCircleRadius - gameOptions.playerRadius - gameOptions.bigCircleThickness / 2;

        // draw the big circle
        this.bigCircle = this.add.graphics();
        this.bigCircle.lineStyle(gameOptions.bigCircleThickness, 0xffffff);
        this.bigCircle.strokeCircle(game.config.width / 2, game.config.height / 2, gameOptions.bigCircleRadius);

        // graphics object where to draw the highlight circle
        this.highlightCircle = this.add.graphics();

        // add player sprite
        this.player = this.add.sprite(game.config.width / 2, game.config.height / 2 - this.distanceFromCenter, "player");
        this.player.displayWidth = gameOptions.playerRadius * 2;
        this.player.displayHeight = gameOptions.playerRadius * 2;

        // player current angle, on top of the big circle
        this.player.currentAngle = -90;

        // player previous angle, at the moment same value of current angle
        this.player.previousAngle = this.player.currentAngle;

        // jump offset, the distance from the ground and player position during jumps
        this.player.jumpOffset = 0;

        // counter to keep track of player jumps
        this.player.jumps = 0;

        // current jump force
        this.player.jumpForce = 0;

        // input listener
        this.input.on("pointerdown", function(){

            // if the player jumped less than 2 times...
            if(this.player.jumps < 2){

                // one more jump
                this.player.jumps ++;

                // add to player jump force the proper force according to the number of jumps performed
                this.player.jumpForce = gameOptions.jumpForce[this.player.jumps - 1];
            }
        }, this);

        // text to display player progress
        this.levelText = this.add.text(game.config.width / 2, game.config.height / 2, "", {
            fontFamily: "Arial",
            fontSize: 96,
            color: "#00ff00"
        });
        this.levelText.setOrigin(0.5);
    }

    // method to be executed at each frame
    update(){

        // if the player is jumping...
        if(this.player.jumps > 0){

            // increase player jump offset according to jump force
            this.player.jumpOffset += this.player.jumpForce;

            // decrease jump force ti simulate gravity
            this.player.jumpForce -= gameOptions.worldGravity;

            // if jump offset is zero or less than zero, that is the player is on the ground...
            if(this.player.jumpOffset < 0){

                // set jump offset to zero
                this.player.jumpOffset = 0;

                // player is not jumping
                this.player.jumps = 0;

                // player has no jump force
                this.player.jumpForce = 0;
            }
        }

        // update previous angle to current angle
        this.player.previousAngle = this.player.currentAngle;

        // update current angle adding player speed
        this.player.currentAngle = Phaser.Math.Angle.WrapDegrees(this.player.currentAngle + gameOptions.playerSpeed);

        // if player is not jumping...
        if(this.player.jumpOffset == 0){

            // set painted ratio to zero
            this.paintedRatio = 0;

            // convert Phaser angles to a more readable angles where zero is on top, 90 is right, 180 down, 270 left
            let currentAngle = this.getGameAngle(this.player.currentAngle);
            let previousAngle = this.getGameAngle(this.player.previousAngle);

            // if current angle is greater than previous angle...
            if(currentAngle >= previousAngle){

                // put in paintedArcs array a new arc
                this.paintedArcs.push([previousAngle, currentAngle]);
            }
            else{

                // this is the case player passed from a value less than 360 to a value greater than 360, which is zero
                // we manage this case as a couple of arcs
                this.paintedArcs.push([previousAngle, 360]);
                this.paintedArcs.push([0, currentAngle]);
            }

            // prepare highlightCircle graphic object to draw
            this.highlightCircle.clear();
            this.highlightCircle.lineStyle(gameOptions.bigCircleThickness, 0xff00ff);

            // merge small arcs into bigger arcs, if possible
            this.paintedArcs = this.mergeIntervals(this.paintedArcs);

            // loop through all arcs
            this.paintedArcs.forEach(function(i){

                // increase painted ratio value with arc length
                this.paintedRatio += (i[1] - i[0]);

                // draw the arc
                this.highlightCircle.beginPath();
                this.highlightCircle.arc(game.config.width / 2, game.config.height / 2, gameOptions.bigCircleRadius, Phaser.Math.DegToRad(i[0] - 90), Phaser.Math.DegToRad(i[1] - 90), false);
                this.highlightCircle.strokePath();
            }.bind(this));

            // convert the sum of all arcs lenght into a 0 -> 100 value
            this.paintedRatio = Math.round(this.paintedRatio * 100 / 360);

            // update player progress text
            this.levelText.setText(this.paintedRatio + "%");

            // if the player painted the whole circle...
            if(this.paintedRatio == 100){

                // ... restart the game in two seconds
                this.time.addEvent({
                    delay: 2000,
                    callbackScope: this,
                    callback: function(){
                        this.scene.start("PlayGame");
                    }
                });
            }
        }

        // transform degrees to radians
        let radians = Phaser.Math.DegToRad(this.player.currentAngle);

        // determine distance from center according to jump offset
        let distanceFromCenter = this.distanceFromCenter - this.player.jumpOffset;

        // position player using trigonometry
        this.player.x = game.config.width / 2 + distanceFromCenter * Math.cos(radians);
        this.player.y = game.config.height / 2 + distanceFromCenter * Math.sin(radians);

        // rotate player using trigonometry
        let revolutions = gameOptions.bigCircleRadius / gameOptions.playerRadius + 1;
        this.player.angle = -this.player.currentAngle * revolutions;
    }

    // method to convert Phaser angles to a more readable angles
    getGameAngle(angle){
        let gameAngle = angle + 90;
        if(gameAngle < 0){
            gameAngle = 360 + gameAngle
        }
        return gameAngle;
    }

    // method to merge intervals, found at
    // https://gist.github.com/vrachieru/5649bce26004d8a4682b
    mergeIntervals(intervals){
        if(intervals.length <= 1){
            return intervals;
        }
        let stack = [];
        let top = null;
        intervals = intervals.sort(function(a, b){
            return a[0] - b[0]
        });
        stack.push(intervals[0]);
        for(let i = 1; i < intervals.length; i++){
            top = stack[stack.length - 1];
            if(top[1] < intervals[i][0]){
                stack.push(intervals[i]);
            }
            else{
                if (top[1] < intervals[i][1]){
                    top[1] = intervals[i][1];
                    stack.pop();
                    stack.push(top);
                }
            }
        }
        return stack;
    }
}

Next time we’ll add enemies, meanwhile download the source code.

Get the most popular Phaser 3 book

Through 202 pages, 32 source code examples and an Android Studio project you will learn how to build cross platform HTML5 games and create a complete game along the way.

Get the book

214 GAME PROTOTYPES EXPLAINED WITH SOURCE CODE
// 1+2=3
// 100 rounds
// 10000000
// 2 Cars
// 2048
// A Blocky Christmas
// A Jumping Block
// A Life of Logic
// Angry Birds
// Angry Birds Space
// Artillery
// Astro-PANIC!
// Avoider
// Back to Square One
// Ball Game
// Ball vs Ball
// Ball: Revamped
// Balloon Invasion
// BallPusher
// Ballz
// Bar Balance
// Bejeweled
// Biggification
// Block it
// Blockage
// Bloons
// Boids
// Bombuzal
// Boom Dots
// Bouncing Ball
// Bouncing Ball 2
// Bouncy Light
// BoxHead
// Breakout
// Bricks
// Bubble Chaos
// Bubbles 2
// Card Game
// Castle Ramble
// Chronotron
// Circle Chain
// Circle Path
// Circle Race
// Circular endless runner
// Cirplosion
// CLOCKS - The Game
// Color Hit
// Color Jump
// ColorFill
// Columns
// Concentration
// Crossy Road
// Crush the Castle
// Cube Jump
// CubesOut
// Dash N Blast
// Dashy Panda
// Deflection
// Diamond Digger Saga
// Don't touch the spikes
// Dots
// Down The Mountain
// Drag and Match
// Draw Game
// Drop Wizard
// DROP'd
// Dudeski
// Dungeon Raid
// Educational Game
// Elasticity
// Endless Runner
// Erase Box
// Eskiv
// Farm Heroes Saga
// Filler
// Flappy Bird
// Fling
// Flipping Legend
// Floaty Light
// Fuse Ballz
// GearTaker
// Gem Sweeper
// Globe
// Goat Rider
// Gold Miner
// Grindstone
// GuessNext
// Helicopter
// Hero Emblems
// Hero Slide
// Hexagonal Tiles
// HookPod
// Hop Hop Hop Underwater
// Horizontal Endless Runner
// Hundreds
// Hungry Hero
// Hurry it's Christmas
// InkTd
// Iromeku
// Jet Set Willy
// Jigsaw Game
// Knife Hit
// Knightfall
// Legends of Runeterra
// Lep's World
// Line Rider
// Lumines
// Magick
// MagOrMin
// Mass Attack
// Math Game
// Maze
// Meeblings
// Memdot
// Metro Siberia Underground
// Mike Dangers
// Mikey Hooks
// Nano War
// Nodes
// o:anquan
// One Button Game
// One Tap RPG
// Ononmin
// Pacco
// Perfect Square!
// Perfectionism
// Phyballs
// Pixel Purge
// PixelField
// Planet Revenge
// Plants Vs Zombies
// Platform
// Platform game
// Plus+Plus
// Pocket Snap
// Poker
// Pool
// Pop the Lock
// Pop to Save
// Poux
// Pudi
// Pumpkin Story
// Puppet Bird
// Pyramids of Ra
// qomp
// Quick Switch
// Racing
// Radical
// Rebuild Chile
// Renju
// Rise Above
// Risky Road
// Roguelike
// Roly Poly
// Run Around
// Rush Hour
// SameGame
// SamePhysics
// Save the Totem
// Security
// Serious Scramblers
// Shrink it
// Sling
// Slingy
// Snowflakes
// Sokoban
// Space Checkers
// Space is Key
// Spellfall
// Spinny Gun
// Splitter
// Spring Ninja
// Sproing
// Stabilize!
// Stack
// Stick Hero
// String Avoider
// Stringy
// Sudoku
// Super Mario Bros
// Surfingers
// Survival Horror
// Talesworth Adventure
// Tetris
// The Impossible Line
// The Moops - Combos of Joy
// The Next Arrow
// Threes
// Tic Tac Toe
// Timberman
// Tiny Wings
// Tipsy Tower
// Toony
// Totem Destroyer
// Tower Defense
// Trick Shot
// Tunnelball
// Turn
// Turnellio
// TwinSpin
// vvvvvv
// Warp Shift
// Way of an Idea
// Whack a Creep
// Wheel of Fortune
// Where's my Water
// Wish Upon a Star
// Word Game
// Wordle
// Worms
// Yanga
// Yeah Bunny
// Zhed
// zNumbers