天天看點

JavaScript操作canvas制作前端H5小遊戲——Flappy Bird

遊戲檢視

源碼和素材下載下傳

部落客學習前端一年多一點,還是個新手,不過勇于嘗試,才能不斷進步,如果代碼品質不好,歡迎提意見,下面開始講解,首先貼張遊戲界面圖:

JavaScript操作canvas制作前端H5小遊戲——Flappy Bird

遊戲使用canvas畫圖制作,分析遊戲确定有這幾個元素:

  1. 天空背景不動
  2. 小鳥上下移動,左右不動
  3. 地闆和水管向左移動(造成小鳥向前移動的錯覺)

canvas畫圖是覆寫畫圖,是以畫圖順序很重要,它的api我就不多說了,隻有用到以下内容:

<!-- html代碼 -->
<canvas id="canvas">您的浏覽器不支援canvas</canvas>
           
/* js相關 */
var canvas = document.getElementById('canvas'),   //擷取canvas節點
    ctx = canvas.getContext('2d');                //擷取畫布上下文畫圖環境
//畫圖(隻舉了一種,還有另一種傳參方式)
ctx.drawImage(img, imgx, imgy, imgw, imgh, canx, cany, canw, canh);
//參數的含義依次為:圖檔資源、圖檔的x坐标、y坐标、寬度、高度、畫布中x坐标、y坐标、寬度、高度
//因為我把所有的圖檔都合成一張,是以需要用截取圖像的傳參方式
           

下面簡單說說整個遊戲的運作代碼結構:

var img = new Image();           //加載圖像
img.src = './img.png';             
img.onload = start;              //圖像加載完成就運作start函數,是以start是入口

function start(){
    //檢查是否碰撞到地闆,水管
    check();
    if(是否遊戲結束){
        //遊戲結束的操作然後退出
        return;
    }
    //畫背景
    ...
    if(isStarted){            //isStarted為是否開始遊戲的變量,是全局的,預設為false
        //開始遊戲就畫小鳥,水管
    }else{
        //否則就畫準備開始的圖像
    }
    //畫分數(預設為,準備階段畫的是)
    ...
    //畫地闆
    ...
    //設定定時器,保證動畫在遊戲中不斷進行
    timer = requestAnimationFrame(start); //和setTimeout(start, )效果差不多
}

document.ontouchstart = document.onmousedown = function(e){
    //點選螢幕時小鳥進行跳躍等處理
}
           

整體結構就是這樣,然後我們一部分一部分完成就可以了。

第一步:擷取裝置的螢幕大小,相容各種螢幕裝置

var viewSize = (function(){

    var pageWidth = window.innerWidth,
        pageHeight = window.innerHeight;

    if (typeof pageWidth != 'number') {
        pageHeight = document.documentElement.clientHeight;
        pageWidth = document.documentElement.clientWidth;
    };

    if(pageWidth >= pageHeight){
        pageWidth = pageHeight *  / ;
    }
    pageWidth = pageWidth >   ?  : pageWidth;
    pageHeight = pageHeight >  ?  : pageHeight;

    return {
        width: pageWidth,
        height: pageHeight
    };

})();
//然後就設定畫布寬高
canvas.width = viewSize.width;
canvas.height = viewSize.height;
//定義原圖像與遊戲界面的像素比
var k = viewSize.height /     //我找的背景圖高度為600px,是以比例就是螢幕高除以600
           

第二步:完成遊戲進行中的部分(沒有gameover檢查,isStarted為true時)

1)畫背景(沒有難點,主要是圖像大小的計算要想清楚)

//清除
ctx.clearRect(,,viewSize.width,viewSize.height);
//畫背景
ctx.drawImage(img, , , , , , , Math.ceil(k * ), viewSize.height);
           

2)畫小鳥:我在全局定義了一個小鳥類,如下:

function Bird(){
    //小鳥拍翅膀有三種狀态,是以畫圖相關大多用一個數組來表示
    this.imgX = [, , ];                           //在原圖中x的坐标
    this.imgY = [, , ];                           //在原圖中y的坐标
    this.imgW = [, , ];                              //在原圖中寬度
    this.imgH = [, , ];                              //在原圖中高度
    var canX = Math.ceil( /  * viewSize.width);      //在畫布中x的坐标
    this.canX = [canX, canX, canX];
    var canY = Math.ceil( /  * viewSize.height);     //在畫布中y的初始坐标 
    this.canY = [canY, canY, canY];                        
    var canW = Math.ceil( * k);                          //在畫布中的寬度  
    this.canW = [canW, canW, canW];                 
    var canH = Math.ceil( * k);                          //在畫布中的高度
    this.canH = [canH, canH, canH];
    //下面三個變量是用來協助記住是在三個狀态中的哪個狀态,後面一看就知道了
    this.index = ;                                        
    this.count = ;
    this.step = ;
    //表示小鳥飛行的時間,後面知道用途
    this.t = ;
    //記住初始y坐标,也是後面一看就知道了
    this.y = [canY, canY, canY];
}
           

定義類的好處就是可以不用設定那麼多的全局變量,你可以直接定義小鳥為一個對象,接着定義小鳥畫圖方法:

Bird.prototype.draw = function(){
    var index = this.index;
    //翅膀拍動, this.count就是用來控制拍動的頻率,記住定時器1秒運作16幀,頻率很快的
    this.count++;
    if(this.count == ){
        this.index += this.step;   
        this.count = ;
    }
    //this.index的變化過程為0、1、2、1、0、1、2、1...是以需要this.index +1和-1變化
    if((this.index ==  && this.step == ) || (this.index ==  && this.step) == -){
        this.step = - this.step;
    } 
    //計算垂直位移,使用公式 y = a * t * (t - c),這裡就知道了this.t是代表着小鳥起跳後到現在的時間
    //我使用了抛物線的函數方程,你也可以自己選擇,代碼下面我會給出函數坐标圖就很清除了
    var c =  * ;
    var minY = -  * viewSize.height / ;
    var a = -minY *  / (c * c);
    var dy = a * this.t * (this.t - c);  //dy是小鳥的位移
    //下面是小鳥飛到頂部的情況,我的處理是,使再點選失效,要小鳥飛下來才能繼續點選
    if(this.y[] + dy < ){
        canClick = false;
    }else{
        canClick = true;
    }
    //然後小鳥在畫布的y坐标就等于原先的y坐标加上位移
    for(var i = ; i < ; i++){
        this.canY[i] = this.y[i] + Math.ceil(dy);
    }
    this.t++;
    ctx.drawImage(img, this.imgX[index], this.imgY[index], this.imgW[index], 
                       this.imgH[index], this.canX[index], this.canY[index], 
                       this.canW[index], this.canH[index]);

};
           

給出小鳥計算方程的坐标圖

JavaScript操作canvas制作前端H5小遊戲——Flappy Bird

因為canvas的y正方向是向下的,是以跳躍應該位移是先負後正,自由落體又是抛物線,接下來就是數學知識了,圖中可以看出:

  • 如果this.t > c,dy > 0,是以可以得出,當this.t = c小鳥到最高點,選c的大小就可以控制上升和下落的速度
  • 在this.t = c/2時,dy達到了最小值,是以,控制Ymin可以确定小鳥的垂直移動最大距離。

要畫小鳥就可以:

var bird = new Bird();
bird.draw();
           

3)畫水管:

遊戲畫面中最多出現兩組水管,當第一組水管到中間時,第二組開始出現,當第一組水管從遊戲界面的左邊出去了,第二組水管剛剛到達中間,而最右邊又開始有水管進來,以此類推,不斷重複。

JavaScript操作canvas制作前端H5小遊戲——Flappy Bird

先解決一組水管的畫法,仍然先定義水管類,分為上水管和下水管:

//基類,屬性的含義同小鳥類
function Pie(){
    this.imgY = ;              
    this.imgW = ;              
    this.imgH = ;
    this.canX = viewSize.width;               //預設在畫布的最右邊
    this.canW = Math.ceil( /  * viewSize.width);
    this.canH = Math.ceil(this.canW *  / );
}
//其中top我們随機生成,代表的是同一組水管中,上水管的左下角在畫布中的y坐标
//上水管類
function UpPie(top){  
    Pie.call(this);                       //繼承相同的屬性
    this.imgX = ;                       //上水管在原圖中的x坐标  
    this.canY = top - this.canH;          //上水管在畫布中的y坐标計算
    this.draw = drawPie;                   
};
//下水管類
function DownPie(top){
    Pie.call(this);
    this.imgX = ;
    this.canY = top + Math.ceil( /  * viewSize.height);  //上水管和下水管的距離固定,大小可調
    this.draw = drawPie;
}
function drawPie(){
    var speed =  * k;
    this.canX -= speed;  //每畫一次就向左邊走
    ctx.drawImage(img, this.imgX, this.imgY, this.imgW, this.imgH, 
                       this.canX, this.canY, this.canW, this.canH);
}
           

然後開始畫水管:

//用一個數組存在畫面中的水管
var Pies = [];
//建立水管函數,首先随機生成top,然後分别執行個體化上、下水管然後存進Pies裡
function createPie(){
    var minTop = Math.ceil( / * viewSize.height),
        maxTop = Math.ceil( / * viewSize.height),
        top = minTop + Math.ceil(Math.random() * (maxTop - minTop));
    Pies.push(new UpPie(top));
    Pies.push(new DownPie(top));
};
//畫水管時,首先判斷
//第一組水管出左邊螢幕,移除水管
if(Pies[].canX <= -Pies[].canW && Pies.length == ){
    Pies[] = null;
    Pies[] = null;
    Pies.shift();
    Pies.shift();
    canCount = true;
}
//第一組水管到達中間時建立水管
if(Pies[].canX <=  * (viewSize.width - Pies[].canW) && Pies.length == ){
    createPie();
}
//然後就可以畫水管
for(var i = , len = Pies.length; i < len; i++){
    Pies[i].draw();
}
           

4)畫分數,比較簡單,主要是需要計算居中:

/**
 * 分數類
 */
function Score(){
    this.imgX = ;
    this.imgY = ;
    this.imgW = ;
    this.imgH = ;
    this.canW = Math.ceil( * k);
    this.canH = Math.ceil( * k);
    this.canY = Math.ceil( /  * viewSize.height);
    this.canX = Math.ceil(viewSize.width /  - this.canW / );
    this.score = ;
}
Score.prototype.draw = function(){
    var aScore = ('' + this.score).split('');
    var len = aScore.length;
    //計算一下居中
    this.canX =  * (viewSize.width - (this.canW + ) * len + );
    for(var i = ; i < len; i++){
        var num = parseInt(aScore[i]);
        if(num < ){
            var imgX = this.imgX + num * ;
            var imgY = ;
        }else{
            var imgX = this.imgX + (num - ) * ;
            var imgY = ;
        }
        var canX = this.canX + i * (this.canW + );
        ctx.drawImage(img, imgX, imgY, this.imgW, this.imgH, canX, this.canY, this.canW, this.canH);
    }
};
           

然後畫就簡單了

var score = new Score();

score.draw();
           

5)畫地闆,主要是需要讓它向左移動

//地闆類
function Ground(){
    this.imgX = ;
    this.imgY = ;
    this.imgH = ;
    this.imgW = ;
    this.canH = Math.ceil( * k);
    this.canW = Math.ceil(k * );
    this.canX = ;
    this.canY = viewSize.height - this.canH;
}
Ground.prototype.draw = function(){
    if(this.imgX > ) this.imgX = ;   //因為無限滾動,是以需要無痕接上
    ctx.drawImage(img, this.imgX, this.imgY, this.imgW, this.imgH, 
                       this.canX, this.canY, this.canW, this.canH);
    this.imgX += ;
};
           

畫的時候執行個體就可以了:

var ground = new Ground();

ground.draw();
           

到這裡你就可以看到水管和地闆可以向後走了,小鳥也能飛起來了,隻是會不斷下落,是以我們要設定點選彈跳。

第三步:點選處理

//touchstart是手機端,mousedown是PC端
document.ontouchstart = document.onmousedown = function(e){
    //遊戲如果結束點選無效
    if(gameover) return;
    if(isStarted){
        //遊戲如果開始了,那麼久開始
        //剛才在小鳥飛出頂部我做了點選屏蔽,
        if(canClick){
            //當我們點選的時候,我們應該恢複初始狀态,初始狀态就是this.t=0, bird.y[i]儲存了初始高度
            for(var i = ; i < ; i++){
                bird.y[i] = bird.canY[i];
            }
            bird.t = ;
        }else{
            return;
        }
    }else{
        //遊戲沒有開始說明在準備,是以開始
        isStarted = true;
    }

    //在ios用戶端,touch事件之後還會觸發click事件,阻止預設事件就可以屏蔽了
    var e = e || window.event;
    if(e.preventDefault){
        e.preventDefault();
    }else{
        e.returnValue = false;
    }
};
           

現在你已經可以使小鳥跳躍了,勝利就在前方。

第四步:check函數

檢測小鳥和地闆是否碰撞最為簡單:

//地闆碰撞,小鳥的y坐标 + 小鳥的高度 >= 地闆的y坐标,表示撞了地闆 
if(bird.canY[] + bird.canH[] >= ground.canY){
    gameover = true;
    return;
}
           

檢測小鳥和水管是否碰撞,可以化成兩個矩形是否重合,重合的情況比較複雜,我們可以看不重合的情況:隻有4種,如圖:

(1)

JavaScript操作canvas制作前端H5小遊戲——Flappy Bird

(2)

JavaScript操作canvas制作前端H5小遊戲——Flappy Bird

(3)

JavaScript操作canvas制作前端H5小遊戲——Flappy Bird

(4)

JavaScript操作canvas制作前端H5小遊戲——Flappy Bird

隻要符合上面一種情況就不重合,其餘情況就是重合,是以:

//檢測兩個矩形是否重合,可以反着看,先找出矩形不重合的情況,
function isOverLay(r1, r2){
    var flag = false;
    if(r1.top > r2.bottom || r1.bottom < r2.top || r1.right < re2.left || r1.left > r2.right){
        flag = true;
    }
    //反之就是重合
    return !flag;
}
//水管碰撞
var birdRect = {
    top: bird.canY[],
    bottom: bird.canY[] + bird.canH[],
    left: bird.canX[],
    right: bird.canX[] + bird.canW[]
};
for(var i = , len = Pies.length; i < len; i++){
    var t = Pies[i];
    var pieRect = {
        top: t.canY,
        bottom: t.canY + t.canH,
        left: t.canX,
        right: t.canX + t.canW
    };
    if(isOverLay(birdRect,pieRect)){
        gameover = true;
        return;
    }
}
           

還需要檢查是否得分

if(Math.floor(bird.canX[]) > Math.floor(Pies[].canX + Pies[].canW) && canCount){
    //小鳥的左邊出了第一組水管的右邊就得分,得分以後,第一組水管還沒出螢幕左邊時不能計算得分
    canCount = false;
    score.score++;
};
           

是以check函數為:

function check(){
    function isOverLay(r1, r2){
        var flag = false;
        if(r1.top > r2.bottom || r1.bottom < r2.top || r1.right < re2.left || r1.left > r2.right){
            flag = true;
        }
        //反之就是重合
        return !flag;
    }
    //地闆碰撞
    if(bird.canY[] + bird.canH[] >= ground.canY){
        console.log(viewSize)
        console.log(bird.canY[],bird.canH[],ground.canY)
        gameover = true;
        return;
    }
    //水管碰撞
    var birdRect = {
        top: bird.canY[],
        bottom: bird.canY[] + bird.canH[],
        left: bird.canX[],
        right: bird.canX[] + bird.canW[]
    };
    for(var i = , len = Pies.length; i < len; i++){
        var t = Pies[i];
        var pieRect = {
            top: t.canY,
            bottom: t.canY + t.canH,
            left: t.canX,
            right: t.canX + t.canW
        };
        if(isOverLay(birdRect,pieRect)){
            gameover = true;
            return;
        }
    }
    //是否得分
    if(Math.floor(bird.canX[]) > Math.floor(Pies[].canX + Pies[].canW) && canCount){
        canCount = false;
        score.score++;
    };
}
           

現在遊戲已經可以玩了,就是還差gameover處理,和重新開始處理了

第五步:gameover處理:

//畫gameover字樣
ctx.drawImage(img, , , , , Math.ceil(viewSize.width *  - k *  * ), 
              Math.ceil( /  * viewSize.height),  * k,  * k);
//畫重新開始點選按鈕           
ctx.drawImage(img, , , , , Math.ceil(viewSize.width *  - k *  * ), 
              Math.ceil( /  * viewSize.height),  * k,  * k)
//因為有重新點選開始,是以在html中有個隐藏的div用來點選重新開始,現在讓它出現
startBtn.style.display = 'block';
startBtn.style.width =  * k + 'px';
startBtn.style.height =  * k + 'px';
startBtn.style.left = Math.ceil(viewSize.width *  - k *  * ) + 'px';
startBtn.style.top = Math.ceil( /  * viewSize.height) + 'px';
//消除定時器       
cancelAnimationFrame(timer);  //如果用setTimeout就是:cleatTimeout(timer)
//回收資源
ground = null;
bird = null;
score = null;
for(var i = , len = Pies.length; i < len; i++){
    Pies[i] = null;
}
Pies = [];
           

第六步:重新開始遊戲處理

startBtn.ontouchstart = startBtn.onmousedown = function(e){
    //初始化參數
    canClick = true;
    gameover = false;
    canCount = true;
    isStarted = false;
    startBtn.style.display = 'none';
    ground = new Ground();
    bird = new Bird();
    score = new Score();
    Pies = [];
    createPie();
    //開定時器
    timer = requestAnimationFrame(start); //或者timer = setTimeout(start, 16);
    //阻止冒泡到document
    var e = e || window.event;
    if(e.stopPropagation){
        e.stopPropagation();
    }else{
        e.cancelBubble = false;
    }

}
           

到此結束,貼上全部代碼,有耐心看完的估計沒有幾個,哈哈哈

html代碼

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Flappy Bird</title>
    <meta name="viewport" content="width=device-width"/>
    <style>
       body,html{
           padding:;
           margin:;
           height:%;
           width:%;
           backgroung:#f1f1f1;
           cursor:pointer;
           overflow: hidden;
        }
        canvas{
            position:relative;
            z-index:;
        }
        #restart{
            position:absolute;
            top:;left:;
            z-index:;
            display:none;
        }
    </style>
</head>
<body>
    <canvas id="canvas"></canvas>
    <div id="restart"></div>
    <script src="index.js"></script>
</body>
</html>
           

js代碼

var viewSize = (function(){

    var pageWidth = window.innerWidth,
        pageHeight = window.innerHeight;

    if (typeof pageWidth != 'number') {
        if (document.compatMode == 'CSS1Compat') {
            pageHeight = document.documentElement.clientHeight;
            pageWidth = document.documentElement.clientWidth;
        } else {
            pageHeight = document.body.clientHeight;
            pageWidth = document.body.clientWidth;
        }
    };
    if(pageWidth >= pageHeight){
        pageWidth = pageHeight *  / ;
    }
    pageWidth = pageWidth >   ?  : pageWidth;
    pageHeight = pageHeight >  ?  : pageHeight;

    return {
        width: pageWidth,
        height: pageHeight
    };

})();

(function(){
    var lastTime = ;
    var prefixes = 'webkit moz ms o'.split(' '); //各浏覽器字首

    var requestAnimationFrame = window.requestAnimationFrame;
    var cancelAnimationFrame = window.cancelAnimationFrame;

    var prefix;
//通過周遊各浏覽器字首,來得到requestAnimationFrame和cancelAnimationFrame在目前浏覽器的實作形式
    for( var i = ; i < prefixes.length; i++ ) {
        if ( requestAnimationFrame && cancelAnimationFrame ) {
            break;
        }
        prefix = prefixes[i];
        requestAnimationFrame = requestAnimationFrame || window[ prefix + 'RequestAnimationFrame' ];
        cancelAnimationFrame  = cancelAnimationFrame  || window[ prefix + 'CancelAnimationFrame' ] || window[ prefix + 'CancelRequestAnimationFrame' ];
    }

//如果目前浏覽器不支援requestAnimationFrame和cancelAnimationFrame,則會退到setTimeout
    if ( !requestAnimationFrame || !cancelAnimationFrame ) {
        requestAnimationFrame = function( callback, element ) {
            var currTime = new Date().getTime();
            //為了使setTimteout的盡可能的接近每秒60幀的效果
            var timeToCall = Math.max( ,  - ( currTime - lastTime ) );
            var id = window.setTimeout( function() {
                callback( currTime + timeToCall );
            }, timeToCall );
            lastTime = currTime + timeToCall;
            return id;
        };

        cancelAnimationFrame = function( id ) {
            window.clearTimeout( id );
        };
    }

//得到相容各浏覽器的API
    window.requestAnimationFrame = requestAnimationFrame;
    window.cancelAnimationFrame = cancelAnimationFrame;
})()

var canvas = document.getElementById('canvas'),
    ctx = canvas.getContext('2d'),
    img = new Image(),
    k= viewSize.height / ,
    canClick,
    gameover,
    canCount,
    isStarted,
    timer,
    ground,
    bird,
    score,
    Pies,
    startBtn = document.getElementById('restart');
//導入圖像
img.onload = start;
img.src = './img.png';
//設定畫布寬高
canvas.width = viewSize.width;
canvas.height = viewSize.height;
init();
function init(){
    canClick = true;
    gameover = false;
    canCount = true;
    isStarted = false;
    startBtn.style.display = 'none';
    ground = new Ground();
    bird = new Bird();
    score = new Score();
    Pies = [];
    createPie();
}
function destroy(){
    ground = null;
    bird = null;
    score = null;
    for(var i = , len = Pies.length; i < len; i++){
        Pies[i] = null;
    }
    Pies = [];
}
/**
 * 開始遊戲
 */
function start(){
    check();
    if(gameover){
        console.log()
        ctx.drawImage(img, , , , , Math.ceil(viewSize.width *  - k *  * ), Math.ceil( /  * viewSize.height),  * k,  * k)
        ctx.drawImage(img, , , , , Math.ceil(viewSize.width *  - k *  * ), Math.ceil( /  * viewSize.height),  * k,  * k)
        startBtn.style.width =  * k + 'px';
        startBtn.style.height =  * k + 'px';
        startBtn.style.left = Math.ceil(viewSize.width *  - k *  * ) + 'px';
        startBtn.style.top = Math.ceil( /  * viewSize.height) + 'px';
        startBtn.style.display = 'block';
        cancelAnimationFrame(timer);
        destroy();
    }else{
        //清除
        ctx.clearRect(,,viewSize.width,viewSize.height);
        //畫背景
        ctx.drawImage(img, , , , , , , Math.ceil(k * ), viewSize.height);
        if(isStarted){
            //第一組水管出左邊螢幕,移除水管
            if(Pies[].canX <= -Pies[].canW && Pies.length == ){
                Pies[] = null;
                Pies[] = null;
                Pies.shift();
                Pies.shift();
                canCount = true;
            }
            //畫小鳥
            bird.draw();
            //建立水管
            if(Pies[].canX <=  * (viewSize.width - Pies[].canW) && Pies.length == ){
                createPie();
            }
            //畫水管
            for(var i = , len = Pies.length; i < len; i++){
                Pies[i].draw();
            }

        }else{
            //畫ready
            ctx.drawImage(img, , , , , Math.ceil(viewSize.width *  - k *  * ), Math.ceil( /  * viewSize.height),  * k,  * k)
            ctx.drawImage(img, , , , , Math.ceil(viewSize.width *  - k *  * ), Math.ceil( /  * viewSize.height),  * k,  * k)
        }
        //畫分數
        score.draw();
        //畫地闆
        ground.draw();
        //設定定時器
        timer = requestAnimationFrame(start);

    }

};
/**
 * 檢查是否碰撞、得分
 */
function check(){
    function isOverLay(rect1, rect2){
        var flag = false;
        if(rect1.top > rect2.bottom || rect1.bottom < rect2.top || rect1.right < rect2.left || rect1.left > rect2.right) flag = true;
        return !flag;
    }
    //地闆碰撞
    if(bird.canY[] + bird.canH[] >= ground.canY){
        console.log(viewSize)
        console.log(bird.canY[],bird.canH[],ground.canY)
        gameover = true;
        return;
    }
    //水管碰撞
    var birdRect = {
        top: bird.canY[],
        bottom: bird.canY[] + bird.canH[],
        left: bird.canX[],
        right: bird.canX[] + bird.canW[]
    };
    for(var i = , len = Pies.length; i < len; i++){
        var t = Pies[i];
        var pieRect = {
            top: t.canY,
            bottom: t.canY + t.canH,
            left: t.canX,
            right: t.canX + t.canW
        };
        if(isOverLay(birdRect,pieRect)){
            gameover = true;
            return;
        }
    }
    //是否得分
    if(Math.floor(bird.canX[]) > Math.floor(Pies[].canX + Pies[].canW) && canCount){
        canCount = false;
        score.score++;
    };
}
/**
 * 點選
 */
document.ontouchstart = document.onmousedown = function(e){
    if(gameover) return;
    if(isStarted){
        if(canClick){
            for(var i = ; i < ; i++){
                bird.y[i] = bird.canY[i];
            }
            bird.t = ;
        }else{
            return;
        }
    }else{
        isStarted = true;
    }
    var e = e || window.event;
    if(e.preventDefault){
        e.preventDefault();
    }else{
        e.returnValue = false;
    }
};

startBtn.ontouchstart = startBtn.onmousedown = function(e){
    var e = e || window.event;
    if(e.stopPropagation){
        e.stopPropagation();
    }else{
        e.cancelBubble = false;
    }
    init();
    timer = requestAnimationFrame(start);
}
/**
 * 分數類
 */
function Score(){
    this.imgX = ;
    this.imgY = ;
    this.imgW = ;
    this.imgH = ;
    this.canW = Math.ceil( * k);
    this.canH = Math.ceil( * k);
    this.canY = Math.ceil( /  * viewSize.height);
    this.canX = Math.ceil(viewSize.width /  - this.canW / );
    this.score = ;
}
Score.prototype.draw = function(){
    var aScore = ('' + this.score).split('');
    var len = aScore.length;
    this.canX =  * (viewSize.width - (this.canW + ) * len + );
    for(var i = ; i < len; i++){
        var num = parseInt(aScore[i]);
        if(num < ){
            var imgX = this.imgX + num * ;
            var imgY = ;
        }else{
            var imgX = this.imgX + (num - ) * ;
            var imgY = ;
        }
        var canX = this.canX + i * (this.canW + );
        ctx.drawImage(img, imgX, imgY, this.imgW, this.imgH, canX, this.canY, this.canW, this.canH);
    }
};
/**
 * 小鳥類
 */
function Bird(){
    this.imgX = [, , ];
    this.imgY = [, , ];
    this.imgW = [, , ];
    this.imgH = [, , ];
    this.index = ;
    this.count = ;
    this.step = ;
    var canX = Math.ceil( /  * viewSize.width);
    this.canX = [canX, canX, canX];
    var canY = Math.ceil( /  * viewSize.height);
    this.canY = [canY, canY, canY];
    var canW = Math.ceil( * k);
    this.canW = [canW, canW, canW];
    var canH = Math.ceil( * k);
    this.canH = [canH, canH, canH];
    this.t = ;
    this.y = [canY, canY, canY];
}
Bird.prototype.draw = function(){
    var index = this.index;
    //翅膀拍動
    this.count++;
    if(this.count == ){
        this.index += this.step;
        this.count = ;
    }
    if((this.index ==  && this.step == ) || this.index ==  && this.step == -) this.step = - this.step;
    //計算垂直位移,使用公式 y = a * t * (t - c)
    var c =  * ;
    var minY = -  * viewSize.height / ;
    var a = -minY *  / (c * c);
    var dy = a * this.t * (this.t - c);

    if(this.y[] + dy < ){
        canClick = false;
    }else{
        canClick = true;
    }
    for(var i = ; i < ; i++){
        this.canY[i] = this.y[i] + Math.ceil(dy);
    }
    this.t++;
    ctx.drawImage(img, this.imgX[index], this.imgY[index], this.imgW[index], this.imgH[index], this.canX[index], this.canY[index], this.canW[index], this.canH[index])

};
/**
 * 水管基類
 */
function Pie(){
    this.imgY = ;
    this.imgW = ;
    this.imgH = ;
    this.canX = viewSize.width;
    this.canW = Math.ceil( /  * viewSize.width);
    this.canH = Math.ceil(this.canW *  / );
}
/**
 * 上水管類
 */
function UpPie(top){
    Pie.call(this);
    this.imgX = ;
    this.canY = top - this.canH;
    this.draw = drawPie;
};
UpPie.prototype = new Pie();
/**
 * 下水管類
 */
function DownPie(top){
    Pie.call(this);
    this.imgX = ;
    this.canY = top + Math.ceil( /  * viewSize.height);
    this.draw = drawPie;
}
DownPie.prototype = new Pie();

function drawPie(){
    var speed =  * k;
    this.canX -= speed;
    ctx.drawImage(img, this.imgX, this.imgY, this.imgW, this.imgH, this.canX, this.canY, this.canW, this.canH);
}

/**
 * 建立水管
 */
function createPie(){
    var minTop = Math.ceil( / * viewSize.height),
        maxTop = Math.ceil( / * viewSize.height),
        top = minTop + Math.ceil(Math.random() * (maxTop - minTop));
    Pies.push(new UpPie(top));
    Pies.push(new DownPie(top));
};
/**
 * 地闆類
 */
function Ground(){
    this.imgX = ;
    this.imgY = ;
    this.imgH = ;
    this.imgW = ;
    this.canH = Math.ceil( * k);
    this.canW = Math.ceil(k * );
    this.canX = ;
    this.canY = viewSize.height - this.canH;
}
Ground.prototype.draw = function(){
    if(this.imgX > ) this.imgX = ;
    ctx.drawImage(img, this.imgX, this.imgY, this.imgW, this.imgH, this.canX, this.canY, this.canW, this.canH);
    this.imgX += ;
};
           

繼續閱讀