Flappy Bird & HTML5

Cocos2d & Flappy Bird

Example

Index.html

						
<!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 Resolution

							
	// set default view resolution
	cc.view.setDesignResolutionSize(320, 480, cc.ResolutionPolicy.SHOW_ALL);
	// resize of the canvas by browser size
	cc.view.resizeWithBrowserSize(true);
							
						

Nacteni Obrazku

							
				// 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);
							
						

Create screen


// 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();
	},
});
						

Rectangle - logic

Example

Static constants

							
    // 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();
    },
							
						

Preparing


    // 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();
    }
						

Create Rectangle

							
    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
    },

							
						

Add update method

							

    // 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 ctverce


    // 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

							
    // 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
    }
							
						

Obstacles

Example

Create Pipe Obstacle

							
    // 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);
    },
							
						

Obstacles update method

							    // 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
        }
    },
							
						

Infinite background & ground

Example

Create infinite image


    // 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 Background


    // 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 Ground


    // 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);
    },
						

Infinite Background update method


    // 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
        }
    },
						

Score count

Example

Create Counter


    // 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 counting


    // 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
    },
						

Change in obstacles update method


    // 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
        }
    },
						

Collision, Start & End

Example

Collision update method


    // 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();
        }
    },
						

Game Over


    // 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 Game


    // 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 Game


    // 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));
    },
						

Change in MakeFlappy method


    // 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
    },
						

Questions?

Created by Lukáš 'chleba' Franěk.