<!DOCTYPE html>
<html>
<head>
<title>Flappy Bird Clone HTML5</title>
<script type="text/javascript" src="cocos2d-js-v3.2.js" charset="UTF-8"></script>
<script type="text/javascript" src="resource.js" charset="UTF-8"></script>
</head>
<body>
<canvas id="gameCanvas" width="800" height="450"></canvas>
<script type="text/javascript">
window.onload = function(){
cc.game.onStart = function(){
// set default view resolution
cc.view.setDesignResolutionSize(320, 480, cc.ResolutionPolicy.SHOW_ALL);
// resize of the canvas by browser size
cc.view.resizeWithBrowserSize(true);
// load resources and then start the game scene
cc.LoaderScene.preload(g_resources, function () {
cc.director.runScene(new FlappyScene());
}, this);
};
// run game and add ID of the canvas element
cc.game.run("gameCanvas");
};
</script>
</body>
</html>
// set default view resolution
cc.view.setDesignResolutionSize(320, 480, cc.ResolutionPolicy.SHOW_ALL);
// resize of the canvas by browser size
cc.view.resizeWithBrowserSize(true);
// file with resources paths for preloading
<script type="text/javascript" src="resource.js" charset="UTF-8"></script>
// load resources and then start the game scene
cc.LoaderScene.preload(g_resources, function () {
cc.director.runScene(new FlappyScene());
}, this);
// random number in range
// @return int
Math.randRange = function(min, max){
return Math.floor(Math.random() * (max - min + 1)) + min;
};
var FlappyScene = cc.Scene.extend({
// called after init
onEnter:function () {
this._super();
},
});
// basic config constants
gravity : 0.4, // gravity of falling
rot : 1.6, // rectangle rotation
vel : 7, // jump velocity
velY : 0, // actual velocity
distance : 0, // distance of fly
obstacleRange : { f : 150, t : 200 }, // constant of space between obstacles
nextObstacle : Math.randRange(200, 250), // distance of next obstacle
speed : 2, // speed of fly
score : 0, // score
gap : 125, // height of gap
// called after init
onEnter:function () {
this._super();
this.makeFlappy();
},
// create of everything
makeFlappy : function(){
this.wSize = cc.director.getWinSize(); // saved window size
this.dom = {}; // object for nodes
this.obstacles = []; // array of obstacles
this.makeRectangle(); // create bird / rectangle
// add touch event listener for jump
cc.eventManager.addListener({
event : cc.EventListener.TOUCH_ONE_BY_ONE,
onTouchBegan : this.onTouchesBegan.bind(this)
}, this);
// start scheduler for ticking
this.scheduleUpdate();
}
makeRectangle : function(){
this.dom.rectangle = cc.Sprite.create('res/bird.png'); // image
this.dom.rectangle.setPosition(cc.p(this.wSize.width/3, this.wSize.height/2)); // position
this.addChild(this.dom.rectangle, 4); // add to scene
},
// update method (scheduleUpdate)
// @param CGTime dt tick time
update : function(dt){
// apply gravity
this.velY -= this.gravity;
// add next step of speed to distance
this.distance += this.speed;
// call of every update method
this.rectangleUpdate(dt);
},
// update method for rectangle / bird
rectangleUpdate : function(){
var p = this.dom.rectangle.getPosition(); // position
var newP = cc.p(p.x, p.y+this.velY); // new position
// check rectangle min/max Y position
newP.y = newP.y <= 0 ? 0 : newP.y;
newP.y = newP.y >= this.wSize.height ? this.wSize.height : newP.y;
// rotation of the rectangle / bird
var rot = this.dom.rectangle.getRotation() + this.rot;
rot = rot >= 90 ? 90 : rot;
// reset rotation & position of the rectangle
this.dom.rectangle.setRotation(rot);
this.dom.rectangle.setPosition(newP);
},
// touch event for rectangle jump
onTouchesBegan:function (touches, event) {
this.velY = this.vel; // jump
this.dom.rectangle.setRotation(0); // set rotation as zero after jump
}
// create obstacles
// @param CGPoint pos
makeObstacle : function(pos){
// random height of the obstacle
var height = Math.randRange(180, this.wSize.height-200);
// obstacle
var obs = cc.Sprite.create('res/pipe.png');
obs.tag = 0; // tag for not counting score for that obstacle
obs.setAnchorPoint(cc.p(0, 1));
obs.setPosition(cc.p(pos.x, height));
// upper part of the obstacle above gap
var h = this.wSize.height-height-this.gap;
var obs1 = cc.Sprite.create('res/pipe.png');
obs1.setFlippedY(true); // flip image
obs1.tag = 0; // tag for not counting score for that obstacle
obs1.setAnchorPoint(cc.p(0, 0));
obs1.setPosition(cc.p(pos.x, this.wSize.height - h));
// add to array
this.obstacles.push(obs);
this.obstacles.push(obs1);
// add to scene
this.addChild(obs, 2);
this.addChild(obs1, 2);
},
// update method for obstacles
obstaclesUpdate : function(){
var r = null; // variable for obstacle which will be deleted
// cycle of the obstacles
for(var i=0;i<this.obstacles.length;i++){
var p = this.obstacles[i].getPosition(); // position of the obstacle
var newP = cc.p(p.x - this.speed, p.y); // new position for obstacle when update tick
this.obstacles[i].setPosition(newP); // set position
if(newP.x < this.obstacles[i].getBoundingBox().width * -1){ // if it's not visible
this.obstacles[i].removeFromParent(); // remove obstacle
r = this.obstacles[i]; // select obstacle
}
}
if(!!r){ // if selected obstacle for remove is not null
this.obstacles.splice(this.obstacles.indexOf(r), 1);
}
// if distance if further then next obstacle variable position
if(this.distance > this.nextObstacle){
this.nextObstacle = Math.randRange(this.obstacleRange.f, this.obstacleRange.t); // reset next obstacle distance
this.distance = 0; // set distance as zero
this.makeObstacle(cc.p(this.wSize.width, 0)); // create new obstacle
}
},
// create image of infinite layer
// @return cc.Sprite
makeInfiniteSprite : function(res){
var s = cc.Sprite.create(res);
s.setAnchorPoint(cc.p(0, 0)); // set anchor to left down corner
return s;
},
// create infinite background
makeBg : function(){
var sprite = cc.Sprite.create('res/bg.png'); // create image for getting size of it
this.dom.bg = []; // array of backgrounds
// cyclus for make background images for complete fill width of a window
for(var i=0;i<Math.ceil(this.wSize.width / sprite.getBoundingBox().width); i++){
var bg = this.makeInfiniteSprite('res/bg.png'); // image
bg.setPosition(cc.p((bg.getBoundingBox().width*i) - (i > 0 ? 5 : 0), 0)); // position
this.addChild(bg, 1); // add to scene
this.dom.bg.push(bg); // add to array
}
// last image to the end
var bg = this.makeInfiniteSprite('res/bg.png');
bg.setPosition(cc.p((bg.getBoundingBox().width*i) - 5, 0));
this.addChild(bg, 1);
this.dom.bg.push(bg);
},
// create infinite ground ( same as background )
makeGround : function(){
var sprite = cc.Sprite.create('res/ground.png');
this.dom.ground = [];
for(var i=0;i<Math.ceil(this.wSize.width / sprite.getBoundingBox().width); i++){
var bg = this.makeInfiniteSprite('res/ground.png');
bg.setPosition(cc.p((bg.getBoundingBox().width*i), 0));
this.addChild(bg, 3);
this.dom.ground.push(bg);
}
var bg = this.makeInfiniteSprite('res/ground.png');
bg.setPosition(cc.p((bg.getBoundingBox().width*i), 0));
this.addChild(bg, 3);
this.dom.ground.push(bg);
},
// update method for infinite background / ground
// @param CGTime dt time of tick
// @param Array array ground / background
// @param float speed speed of the ground / background
infiniteUpdate : function(dt, array, speed){
// set new position
for(var i=0;i<array.length;i++){
var bg = array[i];
var bp = bg.getPosition();
bg.setPosition(cc.p(bp.x - speed, bp.y));
}
// check not visible node for move it to the end
var bf = array[0];
var bfp = bf.getPosition();
if(bfp.x <= bf.getBoundingBox().width*-1){ // if it's off screen
var sb = array.shift(); // remove from array
var lb = array[array.length-1]; // get last node
var p = cc.p((lb.getPosition().x + lb.getBoundingBox().width) - 5, lb.getPosition().y); // new position for move not visible node to the end
sb.setPosition(p);
array.push(sb); // add node to the end of array
}
},
// create score text label
makeScore : function(){
this.dom.score = cc.LabelTTF.create(String(this.score), 'Tahoma', 45); // label
this.dom.score.setColor(cc.color(0, 0, 0)); // color of text
this.dom.score.setPosition(cc.p(this.wSize.width/2, this.wSize.height - this.dom.score.getBoundingBox().height)); // position
this.addChild(this.dom.score, 9); // add to scene
},
// add score
addScore : function(){
// tag closest obstacles for score counting
this.obstacles[0].tag = 1;
this.obstacles[1].tag = 1;
this.score += 1; // add +1 to score
this.dom.score.setString(String(this.score)); // set score label
},
// update method for obstacles
obstaclesUpdate : function(){
var r = null; // variable for obstacle which will be deleted
// cycle of the obstacles
for(var i=0;i<this.obstacles.length;i++){
var p = this.obstacles[i].getPosition(); // position of the obstacle
var newP = cc.p(p.x - this.speed, p.y); // new position for obstacle when update tick
this.obstacles[i].setPosition(newP); // set position
if(newP.x < this.obstacles[i].getBoundingBox().width * -1){ // if it's not visible
this.obstacles[i].removeFromParent(); // remove obstacle
r = this.obstacles[i]; // select obstacle
}
}
if(!!r){ // if selected obstacle for remove is not null
this.obstacles.splice(this.obstacles.indexOf(r), 1);
}
// add score
var op = this.obstacles[0].getPosition(); // position of the obstacle
if(op.x <= this.dom.rectangle.getPosition().x){ // if rectangle position is after obstacle position
if(this.obstacles[0].tag == 0 && this.obstacles[1].tag == 0){ // if obstacle is tagged as not counted for score
this.addScore(); // add score
}
}
// if distance if further then next obstacle variable position
if(this.distance > this.nextObstacle){
this.nextObstacle = Math.randRange(this.obstacleRange.f, this.obstacleRange.t); // reset next obstacle distance
this.distance = 0; // set distance as zero
this.makeObstacle(cc.p(this.wSize.width, 0)); // create new obstacle
}
},
// update method collision with obstacle
collisionUpdate : function(){
var status = false; // semafor of the collision
var oRect, rRect = this.dom.rectangle.getBoundingBox(); // position & size of the rectangle
// cycle for get all obstacles for collision detect
for(var i=0;i<this.obstacles.length;i++){
oRect = this.obstacles[i].getBoundingBox(); // position & size of the obstacle
if(cc.rectIntersectsRect(oRect, rRect)){ // is rectangle in obstacle
status = true; // reset collision semafor
break;
}
}
// collision with ground and top of the window
var rp = this.dom.rectangle.getPosition(); // rectangle position
if(
rp.y <= this.dom.ground[0].getBoundingBox().height || // top of the window
rp.y + this.dom.rectangle.getBoundingBox().height >= this.wSize.height // ground
){
status = true; // reset collision semafor
}
// if collision semafor is true - call game over
if(!!status){
this.gameOver();
}
},
// end of the game after collision
gameOver : function(){
// stop tick scheduler
this.unscheduleUpdate();
// remove event listener
cc.eventManager.removeListener(this.onTouchesBegan.bind(this));
// show menu layer
this.dom.menuLayer.setVisible(true);
},
// start of the game - callback on start button
startGame : function(target, type){
if(type == ccui.Widget.TOUCH_ENDED){ // just touch end
this.resetGame();
// touch event listener
cc.eventManager.addListener({
event : cc.EventListener.TOUCH_ONE_BY_ONE,
onTouchBegan : this.onTouchesBegan.bind(this)
}, this);
// set tick scheduler
this.scheduleUpdate();
this.dom.menuLayer.setVisible(false); // hide menu layer
}
},
// reset all
resetGame : function(){
// clear distance, score, obstacles and actual jump velocity
this.distance = 0;
this.velY = 0;
this.score = 0;
this.nextObstacle = Math.randRange(this.obstacleRange.f, this.obstacleRange.t);
// reset score label
this.dom.score.setString('0');
// set default bird position
this.dom.rectangle.setPosition(cc.p(this.wSize.width/3, this.wSize.height/2));
// remove all obstacles
for(var i=0;i<this.obstacles.length;i++){
this.obstacles[i].removeFromParent();
}
this.obstacles = [];
// make first obtacle
this.makeObstacle(cc.p(this.wSize.width, 0));
},
// create of everything
makeFlappy : function(){
this.wSize = cc.director.getWinSize(); // saved window size
this.dom = {}; // object for nodes
this.obstacles = []; // array of obstacles
this.makeScore(); // create text label for score
this.makeBg(); // create infinite background
this.makeGround(); // create infinite ground
this.makeRectangle(); // create bird / rectangle
this.makeObstacle(cc.p(this.wSize.width, 0)); // create next obstacle
this.makeMenu(); // create menu with button start
},
Created by Lukáš 'chleba' Franěk.