【練習】canvas——flappyBird

YIYIYI1205發表於2018-05-22

<body>

canvas的預設大小,以及如何改變畫布的大小:https://blog.csdn.net/csm0912/article/details/52963240

<!-- <canvas id="cvs" width="800px" height="600px"></canvas> -->
<canvas id="cvs"></canvas>
</body>
<script>
//載入所有圖片的函式:imgObj是存放圖片的物件,fn是回撥函式
function LoadImage(imgObj,fn){
//用來存放載入完的圖片
var imgobj={};
//用來動態生成圖片
var tempImg;
//記錄要載入的數量
// console.log(imgObj.length);  輸出結果是undefined
var imgLength=0;
//記錄已經載入完畢的數量
var loaded=0;
for(var key in imgObj){
imgLength++;
//動態建立一個image
tempImg=new Image();
//所有圖片要監聽的onload事件
tempImg.onload=function(){
loaded++;
if(loaded>=imgLength){
//把載入好的資料傳入回撥函式
fn(imgobj);
}
};
tempImg.src=imgObj[key];
imgobj[key]=tempImg;
}
}
</script>
<script>
//繪製背景
function Sky(ctx,img,speed){
this.ctx=ctx;
this.img=img;
this.width=img.width;
this.height=img.height;
this.speed=speed||10;
Sky.len++;
this.x=this.img.width*(Sky.len-1);
this.y=0;

}

        物件的靜態屬性:http://www.jb51.net/article/64278.htm

//靜態變數,每建立一個sky例項都會加1,用來確定x的位置
Sky.len = 0;
Sky.prototype={
construct:Sky,
draw:function(){
this.ctx.drawImage(this.img,this.x,this.y);
},
update:function(){
//讓背景移動起來
this.x-=this.speed;
//如果x移出畫布(整個移出,x一定小於負的寬度,令這個圖片拼到最後面的圖片的後面,給現在的x加上一共多少個圖片的寬度即可)
this.x=this.x<=-this.width?this.x+this.width*Sky.len:this.x
}
}
//繪製小鳥widthFrame:圖片一行有幾個小鳥
function Bird(ctx,img,widthFrame,heightFrame,x,y){
this.ctx=ctx;
this.img=img;
this.widthFrame=widthFrame;
this.heightFrame=heightFrame;
this.x=x;
this.y=y;
this.width=this.img.width / this.widthFrame;
this.height=this.img.height / this.heightFrame;
//當前小鳥的幀數
this.currentFrame=0;
//小鳥下落的速度是speed,下落的加速度是speedPlus
this.speed=2;
this.speedPlus=0.2;
}
Bird.prototype={
construct:Sky,
//讓小鳥根據speed調整旋轉角度
draw:function(){
var baseRadian=Math.PI/180*10;
var rotateRadian=baseRadian*this.speed;
//儲存小鳥還沒開始旋轉時的狀態
this.ctx.save();
//1.先把座標軸平移到小鳥的中心點
//2.根據speed旋轉小鳥的度數,speed=1時,旋轉90度,超過45度,就是45度
//3.繪製小鳥,將小鳥的x,y設定為寬度和高度的一半
this.ctx.translate(this.x+this.width/2,this.y+this.height/2);
 
if(rotateRadian>=Math.PI/180*45){
rotateRadian=Math.PI/180*45;
}
this.ctx.rotate(rotateRadian);
// this.ctx.rotate(Math.PI/180*45);


this.ctx.drawImage(this.img,this.width*this.currentFrame,0,this.width,this.height,-this.width/2,-this.height/2,this.width,this.height);
// this.ctx.drawImage(this.img,this.width*this.currentFrame,0,this.width,this.height,this.x,this.y,this.width,this.height);
this.ctx.restore();
},
// 小鳥不需要往前跑,背景,land,pipe在動,小鳥往下落就可以
update:function(){
this.currentFrame++;
this.currentFrame=this.currentFrame>=this.widthFrame?0:this.currentFrame;
// this.currentFrame=++this.currentFrame>=this.widthFrame?0:this.currentFrame;
this.y+=this.speed;
this.speed+=this.speedPlus;
},
//監聽點選事件,點選畫布speed變成反方向,因此y再加speed就會往上跑,且越往上speed越小,就變成向上的減速運動
_bind:function(){
var that =this;
this.ctx.canvas.addEventListener("click",function(){
that.speed=-3;
});
}
}
//大地 和背景是類似的
function Land(ctx,img,speed){
this.ctx=ctx;
this.img=img;
this.speed=speed||2;
this.height=this.img.height;
this.y=this.ctx.canvas.height-this.img.height;
Land.len++;
this.x=this.img.width*(Land.len-1);
}
Land.len=0;
Land.prototype={
construct:Land,
draw:function(){
this.ctx.drawImage(this.img,this.x,this.y);
},
update:function(){
this.x-=this.speed;
this.x=this.x<=-this.img.width?this.x+this.img.width*Land.len:this.x;
}
}
//管道 兩張圖片,上下管道,space表示中間的間隙,land的高度
function Pipe(ctx,img1,img2,space,speed,landHeight){
this.ctx=ctx;
this.img1=img1;
this.img2=img2;
this.space=space;
this.speed=speed||2;
this.landHeight=landHeight;
this.width=this.img2.width;
this.height=this.img2.height;
this.y1=0;
this.y2=0;
this.height2=this.img2.height;
this.minHeight=100;

Pipe.len++;

                // 中間隔3個管子,再加上本身寬度一共4個

this.x=500+this.width*4*(Pipe.len-1);
//init在建立例項的時候呼叫一次,不是在setinterval中呼叫,否則y值會一直變化
this.init();
}
Pipe.len=0;
Pipe.prototype={
construct:Pipe,
init:function(){
var maxHeight=this.ctx.canvas.height-this.minHeight-this.landHeight-this.space;
var randomHeight=Math.random()*maxHeight;
if(randomHeight<this.minHeight){
randomHeight=this.minHeight;
}
this.y1=randomHeight-this.img1.height;
this.y2=randomHeight+this.space;
//注意不能直接drawImage(this.img2,this.x,this.y2);
//因為下面管子的下半部分應該被擷取了,但是它現在是全部顯示的
//得做計算求出現在需要剩餘的下管子的高度
this.height2=this.ctx.canvas.height-this.y2-this.landHeight;
},
draw:function(){
this.ctx.drawImage(this.img1,this.x,this.y1);
this.ctx.drawImage(this.img2,this.x,this.y2,this.width,this.height2);
this.drawPath();
},
update:function(){
this.x-=this.speed;
if(this.x<=-this.width){
//從畫布中出去的管子再重新初始化
this.init();
this.x+=this.width*4*Pipe.len;
}
},
//畫管道的路徑
drawPath:function(){
this.ctx.rect(this.x,this.y1,this.width,this.height);
this.ctx.rect(this.x,this.y2,this.width,this.height2);
// this.ctx.stroke();
}
}
</script>
<script>
var cvs =document.getElementById('cvs');
var ctx =cvs.getContext("2d");
LoadImage({
bird:"image/birds.png",
sky:"image/sky.png",
pipe1:"image/pipe2.png",
pipe2:"image/pipe1.png",
land:"image/land.png"
},function(imgObj){
//這兩行根據背景的高寬設定畫布的高寬應該寫在最上面,如果寫在下面就會先建立land例項,此時畫布的高寬還沒有設定,是預設,計算的land的y就不對
cvs.width=imgObj.sky.width;
cvs.height=imgObj.sky.height;
var sky=new Sky(ctx,imgObj.sky,10);
var sky1=new Sky(ctx,imgObj.sky,10);
var bird=new Bird(ctx,imgObj.bird,3,1,10,10);
var land=new Land(ctx,imgObj.land,10);
var land1=new Land(ctx,imgObj.land,10);
var land2=new Land(ctx,imgObj.land,10);
//3個land已經可以填滿畫布的寬,但是動起來時,第一張還沒有完全出去,最右邊已經出來了,就會導致右邊空出來一塊一會兒才被補上。需要4張圖片
var land3=new Land(ctx,imgObj.land,10);
var pipe=new Pipe(ctx,imgObj.pipe1,imgObj.pipe2,100,5,land.height);
var pipe1=new Pipe(ctx,imgObj.pipe1,imgObj.pipe2,100,5,land.height);
var pipe2=new Pipe(ctx,imgObj.pipe1,imgObj.pipe2,100,5,land.height);
var pipe3=new Pipe(ctx,imgObj.pipe1,imgObj.pipe2,100,5,land.height);
var timer=setInterval(function(){
//定時器一開始就進行判斷,小鳥是否碰壁,如果碰壁就清除定時器
//小鳥的中心點
var bordCoreX=bird.x+bird.width/2;
var bordCoreY=bird.y+bird.height/2;
//如果小鳥的中心點在路徑內,或者超過畫布最上面,或者落地,都清除定時器
//isPointInPath(x,y)方法,判斷點(x,y)在不在路徑中
// if(ctx.isPointInPath(bordCoreX,bordCoreY)||bordCoreY<0||bordCoreY>(ctx.canvas.height-land.height)){
if(ctx.isPointInPath(bordCoreX,bordCoreY)||bordCoreY<0||bordCoreY>(land.y)){
clearInterval(timer);
ctx.fillStyle="rgba(100,100,100,0.8)";
ctx.fillRect(0,0,ctx.canvas.width,ctx.canvas.height);
//不顯示字!因為出現字以後跳出判斷,接著sky.draw(),把字給覆蓋了
ctx.textAlign="center";
ctx.textBaseline="middle";
ctx.fillStyle="red";
ctx.font="900 40px 微軟雅黑";
ctx.fillText("GAME OVER!!",ctx.canvas.width/2,ctx.canvas.height/2);
// return的作用是跳出這個判斷,不執行下面的程式碼,不會覆蓋掉文字
return;
}
sky.draw();
sky.update();
sky1.draw();
sky1.update();
bird.draw();
bird.update();
bird._bind();
land.draw();
land1.draw();
land2.draw();
land3.draw();
land.update();
land1.update();
land2.update();
land3.update();
//要在這幾個管道一起的前面清楚路徑,如果每次畫draw就會只有一個路徑,所以要一起清,不清的話每次update就會產生新的路徑。
ctx.beginPath();
pipe.draw();
pipe.update();
pipe1.draw();
pipe1.update();
pipe2.draw();
pipe2.update();
pipe3.draw();
pipe3.update();
},50);

});
</script>