“Dash N Blast” HTML5 prototype updated: added obstacles and collision detection without physics engines

Read all posts about "" game

Did you enjoy Dash N Blast tutorial? In first step we covered the basic movement of this vertical endless runner.

Obviously an endless runner with no obstacles or risky situations is quite boring, so we added deadly arcs rotating around the targets.

This makes things WAY harder, have a look:

Tap or click to move from the bottom circle to top circle. Avoid the walls or it’s game over.

The core of the script is the ball Vs rotating arcs collision. Detecting the collision between a circle and a circumference arc is not the easiest thing to do, and you won’t find a lot of guides around the web to help you.

I did not want to dig too dep into geometry and trigonometry, so this is my solution:

First, I check for collision between the ball and the whole circle representing the target. This is simple, it’s just a matter to compare the distance between the two circles and see if it’s less than the sum of circle radiuses.

If there’s a collision between the two circles, there MAY be a collision between the ball and an arc. So I am keeping track of arc start and end angle, as well as its position around the circumference of the target, and compare it with the angle of collision between the ball and the target.

If such angle of collision is included in the current position of an arc, BOOM and it’s game over.

Have a look at the completely commented source code:

var game;

// global object containing all configurable options
var gameOptions = {

    // number of circles used in the game
    numCircles: 4,

    // circle min/max radius, in pixels
    circleRadiusRange: [120, 200],

    // min/max distance between circles, in pixels
    circleDistanceRange: [600, 750],

    // distance from the bottom of the canvas, in pixels
    bottomDistance: 150,

    // min/max circle distance from the center of the canvas, in pixels
    distanceFromCenter: [0, 150],

    // ball speed, in pixels per second
    speed: 1000,

    // possible arc colors
    circleColors: [0x4deeea, 0x74ee15, 0xffe700, 0xf000ff, 0x001eff],

    // arc length, in degrees
    arcLength: [10, 90],

    // amount of arcs on each circle
    arcsOnCircle: 3
}
window.onload = function() {
    let gameConfig = {
        type: Phaser.AUTO,
        backgroundColor: 0x111111,
        scale: {
            mode: Phaser.Scale.FIT,
            autoCenter: Phaser.Scale.CENTER_BOTH,
            parent: "thegame",
            width: 750,
            height: 1334
        },
        scene: playGame
    }
    game = new Phaser.Game(gameConfig);
    window.focus();
}
class playGame extends Phaser.Scene{
    constructor(){
        super("PlayGame");
    }
    preload(){
        this.load.image("ball", "ball.png");
    }
    create(){

        // group which will contain the ball, all circles and landing spots
        this.stuffGroup = this.add.group();

        // flag to determine if the player can shoot the ball
        this.canShoot = true;

        // array which will contain all circles
        this.circles = [];

        // array which will contain all landing spots
        this.landingSpots = [];

        // array which will contain all arcs
        this.arcs = [];

        // array which will contain all arc tweens
        this.arcTweens = [];

        // index of the circle currently at the bottom of the canvas
        this.bottomCircle = 0;

        // time to create circles
        for(let i = 0; i < gameOptions.numCircles; i++){

            // add a graphics object at i-th position of the array
            this.circles[i] = this.add.graphics();

            // add the graphic object to stuffGroup group
            this.stuffGroup.add(this.circles[i]);

            // add a graphics object at i-th position of the array
            this.arcs[i] = this.add.graphics();

            // add the graphic object to stuffGroup group
            this.stuffGroup.add(this.arcs[i]);

            // infinite tween to rotate the arc by 360 degrees in one second
            this.arcTweens[i] = this.tweens.add({
                targets: this.arcs[i],
                angle: 360,
                duration: 1000,
                repeat: -1,
            })

            // add a sprite representing the landing spot at i-th position of the array
            this.landingSpots[i] = this.add.sprite(0, 0, "ball");

            // set the landing spot semi-transparent
            this.landingSpots[i].alpha = 0.5;

            // add landing spot to stuffGroup group
            this.stuffGroup.add(this.landingSpots[i]);

            // this method will draw a random circle
            this.drawCircle(i);
        }

        // the ball! The hero of our game
        this.ball = this.add.sprite(this.circles[0].x, this.circles[0].y, "ball");

        // the ball too is added to stuffGroup group
        this.stuffGroup.add(this.ball);

        // wait for player input then call shootBall method
        this.input.on("pointerdown", this.shootBall, this);
    }

    // method to draw a circle along with its landing area
    drawCircle(i){

        // clear the graphic object
        this.circles[i].clear();

        // set graphic line style choosing a random color
        this.circles[i].lineStyle(8, 0x888888, 1);

        // define a random radius
        let radius = this.randomOption(gameOptions.circleRadiusRange);

        // save the radius as a custom property
        this.circles[i].radius = radius;

        // place the circle at a random horizontal position
        this.circles[i].x = game.config.width / 2 + this.randomOption(gameOptions.distanceFromCenter) * Phaser.Math.RND.sign();

        // if both i and bottomCircle are equal to zero, this means it's the first grapic object we are placing
        if(i == 0 &amp;&amp; this.bottomCircle == 0){

            // so we place it at the bottom of the screen
            this.circles[i].y = game.config.height - radius - gameOptions.bottomDistance;
        }
        else{

            // otherwise we are placing it above the grapic object in the highest position
            this.circles[i].y = this.circles[Phaser.Math.Wrap(i - 1, 0, gameOptions.numCircles)].y - this.randomOption(gameOptions.circleDistanceRange);
        }

        this.arcs[i].x = this.circles[i].x
        this.arcs[i].y = this.circles[i].y

        // time to draw the circle
        this.circles[i].strokeCircle(0, 0, radius);

        // place the landing spot at circle origin
        this.landingSpots[i].x = this.circles[i].x;
        this.landingSpots[i].y = this.circles[i].y;

        // place the arc at circle origin
        this.arcs[i].x = this.circles[i].x
        this.arcs[i].y = this.circles[i].y

        // clear the graphic object
        this.arcs[i].clear();
        this.arcs[i].arcCoords = [];

        // set graphic line style choosing a random color
        this.arcs[i].lineStyle(18, Phaser.Utils.Array.GetRandom(gameOptions.circleColors), 1);

        // drawing arcs
        for(let j = 0; j < gameOptions.arcsOnCircle; j++){
            this.arcs[i].beginPath();
            let arcStart = Phaser.Math.Between(0, 360)
            let arcEnd = arcStart + this.randomOption(gameOptions.arcLength);
            this.arcs[i].arc(0, 0, radius, Phaser.Math.DegToRad(arcStart), Phaser.Math.DegToRad(arcEnd), false);
            this.arcs[i].strokePath();

            // although arc width has already been set, we save a slighty bigger ark, for collision detection purpose
            this.arcs[i].arcCoords[j] = [arcStart - 8, arcEnd + 8]
        }

        // this is a speed divider to apply to tween duration
        // this way it will range from 1/0.3 to 1/0.5 seconds
        this.arcTweens[i].timeScale = Phaser.Math.RND.realInRange(0.3, 0.5);
    }

    // choose a random integer between an option declared in gameOptions object
    randomOption(option){
        return Phaser.Math.Between(option[0], option[1]);
    }

    // method to shoot the ball
    shootBall(){

        // if the player can shoot...
        if(this.canShoot){

            // can't shoot anymore at the moment
            this.canShoot = false;

            // define target index, that is the circle at the top of the canvas
            let targetIndex = Phaser.Math.Wrap(this.bottomCircle + 1, 0, gameOptions.numCircles);

            // calculate distance between the two targets
            let distance = Phaser.Math.Distance.Between(this.landingSpots[this.bottomCircle].x, this.landingSpots[this.bottomCircle].y, this.landingSpots[targetIndex].x, this.landingSpots[targetIndex].y);

            // add a tween to the ball to move to the target
            this.ballTween = this.tweens.add({
                targets: this.ball,
                x: this.landingSpots[targetIndex].x,
                y: this.landingSpots[targetIndex].y,

                // duration, in milliseconds, is determined according to distance and speed
                duration: distance * 1000 / gameOptions.speed,
                callbackScope: this,

                // at each update call checkCollision method looking for collision with the circle at the
                // bottom of the screen and the circle immediately above it, the one at the top of the screen
                onUpdate: function(){
                    this.checkCollision(this.bottomCircle);
                    this.checkCollision(Phaser.Math.Wrap(this.bottomCircle + 1, 0, gameOptions.numCircles));
                },

                // once the tween is completed
                onComplete: function(){

                    // determine the amount of pixels to scroll to make top circle move down to the bottom of the canvas
                    let yScroll = game.config.height - this.circles[targetIndex].radius - gameOptions.bottomDistance - this.circles[targetIndex].y

                    // add a tween to all stuffGroup children to move them down by yScroll pixels
                    this.tweens.add({
                        targets: this.stuffGroup.getChildren(),
                        props: {
                            y: {
                                value: "+=" + yScroll
                            }
                        },
                        duration: 250,
                        callbackScope: this,
                        onComplete: function(){

                            // at the end of the tween, save bottomCircle value
                            let currentCircle = this.bottomCircle;

                            // update bottomCircle value
                            this.bottomCircle = Phaser.Math.Wrap(this.bottomCircle + 1, 0, gameOptions.numCircles);

                            // redraw the bottom target to be placed at the top
                            this.drawCircle(Phaser.Math.Wrap(currentCircle, 0, gameOptions.numCircles))

                            // player can shoot again
                            this.canShoot = true;
                        }
                    })
                }
            })
        }
    }

    // method to check collision between the ball and the arcs on the i-th circle
    checkCollision(i){

        // calculate the distance between the circle and the ball
        let distance = Phaser.Math.Distance.Between(this.circles[i].x, this.circles[i].y, this.ball.x, this.ball.y);

        // if the difference between the distance and the radius is less than ball radius,
        // this means the ball could collide with an arc and we have to investigate
        if(Math.abs(distance - this.circles[i].radius) < this.ball.width / 2){

            // determine the angle between the ball and the circle
            let angle = Phaser.Math.RadToDeg(Phaser.Math.Angle.Between(this.circles[i].x, this.circles[i].y, this.ball.x, this.ball.y));

            // looping through all arcs
            this.arcs[i].arcCoords.forEach(function(p){

                // get arc start and end angle, according to tween rotation
                let arcStart = Phaser.Math.Angle.WrapDegrees(p[0] + this.arcs[i].angle);
                let arcEnd = Phaser.Math.Angle.WrapDegrees(p[1] + this.arcs[i].angle);

                // if the angle between the ball and the circle is between arc start and end angle,
                // we have a collision.
                if(angle >= arcStart &amp;&amp; angle <= arcEnd){

                    // stop tweens
                    this.ballTween.stop();
                    this.arcTweens[i].stop();

                    // shake the camera
                    this.cameras.main.shake(500, 0.01);

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

And now we have a working prototype of a physics hyper casual game without using any physics engine, thanks to the power of Phaser tweens and a bit of trigonometry. 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