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.
Never miss an update! Subscribe, and I will bother you by email only when a new game or full source code comes out.