Build a HTML5 game like GAMEE’s “Color Hit” only using tweens and trigonometry, powered by Phaser

Talking about Color Hit game, Game development, HTML5, Javascript and Phaser.

Do you play games on GAMEE? It’s a free gaming network which offers real money prizes.

I do not play for money at GAMEE, but I like its hyper causal games, and Color Hit is one of them.

It’s a game similar to Knife Hit, which has already been explained in a tutorial series, but rather than simply throwing knives at a target, Color Hit introduces colored sectors and knives.

Obviously, you can only hit target sectors which match with the color of the knife you are throwing, or it’s game over.

Have a try:

Click or tap to throw a knife. Match knife color with target color or it’s game over. Also, do not throw knives over other knives.

Easy and fun, and here is the completely commented source code:


// the game itself
let game;

// global game options
let gameOptions = {

    // target radius, in pixels
    targetRadius: 200,

    // array with all target colors
    targetColors: [0xff0000, 0x00ff00, 0x0000ff],

    // target Y position, in screen height ratio
    targetY: 1 / 4,

    // target rotation speed, in degrees per frame
    rotationSpeed: 3,

    // knife throwing duration, in milliseconds
    throwSpeed: 150,

    // minimum angle between two knives
    minAngle: 15,

    // max rotation speed variation, in degrees per frame
    rotationVariation: 2,

    // interval before next rotation speed variation, in milliseconds
    changeTime: 2000,

    // maximum rotation speed, in degrees per frame
    maxRotationSpeed: 6
}

// once the window loads...
window.onload = function() {
    let gameConfig = {
        type: Phaser.AUTO,
        backgroundColor: 0xeeeeee,
        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();
}

// PlayGame scene
class playGame extends Phaser.Scene {

    // constructor
    constructor() {
        super("PlayGame");
    }

    // method to be executed when the scene preloads
    preload() {

        // load the knife
        this.load.image("knife", "knife.png");
    }

    // method to be executed once the scene has been created
    create() {

        // make a graphic object but don'y add it to the game
        let graphics = this.make.graphics({
            x: 0,
            y: 0,
            add: false
        });

        // angle covered by a slice
        this.sliceAngle = 2 * Math.PI / gameOptions.targetColors.length;

        // loop through all colors
        for(let i = 0; i < gameOptions.targetColors.length; i ++) {

            // set fill stile
            graphics.fillStyle(gameOptions.targetColors[i], 1);

            // draw a target slice
            graphics.slice(gameOptions.targetRadius, gameOptions.targetRadius, gameOptions.targetRadius, 2 * Math.PI - this.sliceAngle * i, 2 * Math.PI - this.sliceAngle * (i + 1), true);

            // close and fill the path
            graphics.fillPath();
        }

        // generate wheel texture from the graphics
        graphics.generateTexture("wheel", gameOptions.targetRadius * 2, gameOptions.targetRadius * 2);

        // at the beginning of the game, both current rotation speed and new rotation speed are set to default rotation speed
        this.currentRotationSpeed = gameOptions.rotationSpeed;
        this.newRotationSpeed = gameOptions.rotationSpeed;

        // can the player throw a knife? Yes, at the beginning of the game
        this.canThrow = true;

        // group to store all rotating knives
        this.knifeGroup = this.add.group();

        // add the knife
        this.knife = this.add.sprite(game.config.width / 2, game.config.height / 5 * 4, "knife");

        // random color of the target to hit
        this.knife.target = Phaser.Math.Between(0, gameOptions.targetColors.length - 1);

        // tint the knife accordingly
        this.knife.tint = gameOptions.targetColors[this.knife.target];

        // add the target
        this.target = this.add.sprite(game.config.width / 2, game.config.height * gameOptions.targetY, "wheel");

        // move the target to front
        this.target.depth = 1;

        // wait for player input to throw a knife
        this.input.on("pointerdown", this.throwKnife, this);

        // this is how we create a looped timer event
        let timedEvent = this.time.addEvent({

            // delay, in milliseconds
            delay: gameOptions.changeTime,

            // callback function
            callback: this.changeSpeed,

            // callback scope
            callbackScope: this,

            // the event will repeat endlessly
            loop: true
        });
    }

    // method to change the rotation speed of the target
    changeSpeed() {

        // random number between -gameOptions.rotationVariation and gameOptions.rotationVariation
        let variation = Phaser.Math.FloatBetween(-gameOptions.rotationVariation, gameOptions.rotationVariation);

        // new rotation speed
        this.newRotationSpeed = (this.currentRotationSpeed + variation) * Phaser.Math.RND.sign();

        // set new rotation speed limits
        this.newRotationSpeed = Phaser.Math.Clamp(this.newRotationSpeed, -gameOptions.maxRotationSpeed, gameOptions.maxRotationSpeed);
    }

    // method to throw a knife
    throwKnife() {

        // can the player throw?
        if(this.canThrow) {

            // player can't throw anymore
            this.canThrow = false;

            // tween to throw the knife
            this.tweens.add({

                // add the knife to tween targets
                targets: [this.knife],

                // y destination
                y: this.target.y + this.target.width / 2,

                // tween duration
                duration: gameOptions.throwSpeed,

                // callback scope
                callbackScope: this,

                // function to be executed once the tween has been completed
                onComplete: function(tween) {

                    // at the moment, this is a legal hit
                    let legalHit = true;

                    // if the knife hit the wrong color...
                    if(Math.floor(Phaser.Math.Angle.Normalize(this.target.rotation - Math.PI / 2) / this.sliceAngle) != this.knife.target) {

                        // ... not a legal hit
                        legalHit = false;
                    }

                    // if still a legal hit...
                    else {
                        // get an array with all rotating knives
                        let children = this.knifeGroup.getChildren();

                        // loop through rotating knives
                        for (let i = 0; i < children.length; i ++) {

                            // is the knife too close to the i-th knife?
                            if(Math.abs(Phaser.Math.Angle.ShortestBetween(this.target.angle, children[i].impactAngle)) < gameOptions.minAngle) {

                                // this is not a legal hit
                                legalHit = false;

                                // no need to continue with the loop
                                break;
                            }
                        }
                    }

                    // is this a legal hit?
                    if(legalHit) {

                        // player can now throw again
                        this.canThrow = true;

                        // addi the rotating knife in the same place of the knife just landed on target
                        let knife = this.add.sprite(this.knife.x, this.knife.y, "knife");

                        // impactAngle property saves the target angle when the knife hits the target
                        knife.impactAngle = this.target.angle;

                        knife.tint = gameOptions.targetColors[this.knife.target];

                        // add the rotating knife to knifeGroup group
                        this.knifeGroup.add(knife);

                        // bring back the knife to its starting position
                        this.knife.y = game.config.height / 5 * 4;

                        // set a new random color target
                        this.knife.target = Phaser.Math.Between(0, gameOptions.targetColors.length - 1);

                        // tint the knife accordingly
                        this.knife.tint = gameOptions.targetColors[this.knife.target];
                    }

                    // in case this is not a legal hit
                    else{

                        // tween to make the knife fall down
                        this.tweens.add({

                            // add the knife to tween targets
                            targets: [this.knife],

                            // y destination
                            y: game.config.height + this.knife.height,

                            // rotation destination, in radians
                            rotation: 5,

                            // tween duration
                            duration: gameOptions.throwSpeed * 4,

                            // callback scope
                            callbackScope: this,

                            // function to be executed once the tween has been completed
                            onComplete: function(tween) {

                                // restart the game
                                this.scene.start("PlayGame")
                            }
                        });
                    }
                }
            });
        }
    }

    // method to be executed at each frame. Please notice the arguments.
    update(time, delta) {

        // rotate the target
        this.target.angle += this.currentRotationSpeed;

        // get an array with all rotating knives
        let children = this.knifeGroup.getChildren();

        // loop through rotating knives
        for (let i = 0; i < children.length; i ++) {

            // rotate the knife
            children[i].angle += this.currentRotationSpeed;

            // turn knife angle in radians
            let radians = Phaser.Math.DegToRad(children[i].angle + 90);

            // trigonometry to make the knife rotate around target center
            children[i].x = this.target.x + (this.target.width / 2) * Math.cos(radians);
            children[i].y = this.target.y + (this.target.width / 2) * Math.sin(radians);
        }

        // adjust current rotation speed using linear interpolation
        this.currentRotationSpeed = Phaser.Math.Linear(this.currentRotationSpeed, this.newRotationSpeed, delta / 1000);
    }
}

I found it more fun than Knife Hit because of the different colors, maybe we could randomize a bit more sector colors, in a further step, meanwhile download the source code.