<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FlappBird</title>
<style type="text/css">
html,body{margin:0;padding:0;}
#canvas{
display: block;
margin:0 auto;
background-color: gainsboro;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script src="script/Background.js"></script>
<script src="script/Bird.js"></script>
<script src="script/Land.js"></script>
<script src="script/Pipe.js"></script>
<script src="script/SceneManager.js"></script>
<script src="script/Game.js"></script>
<script type="text/javascript">
let game = new Game();
</script>
</body>
</html>
class Background{
constructor(){
/** @type {HTMLCanvasElement} */
this.w = game.allImg["bg_day"].width;
this.h = game.allImg["bg_day"].height;
this.x = 0;
this.speed = 1;
}
//更新
update(){
this.x -= this.speed;
this.x <= -this.w ? this.x=0 : null;
}
//渲染
render(){
game.draw.fillStyle="#4ec0ca";
game.draw.fillRect(0,0,game.canvas.width,game.canvas.height);
game.draw.drawImage(game.allImg["bg_day"],this.x,game.canvas.height-this.h);
game.draw.drawImage(game.allImg["bg_day"],this.x+this.w,game.canvas.height-this.h);
game.draw.drawImage(game.allImg["bg_day"],this.x+this.w*2,game.canvas.height-this.h);
}
}
class Land{
constructor(){
/** @type {CanvasRenderingContext2D} */
this.w = game.allImg["land"].width;
this.h = game.allImg["land"].height;
this.x = 0;
this.y = game.canvas.height - this.h;
this.speed = 1;
}
update(){
this.x -= this.speed;
this.x < -this.w ? this.x=0 : null
}
render(){
game.draw.drawImage(game.allImg["land"],this.x,this.y)
game.draw.drawImage(game.allImg["land"],this.x+this.w,this.y)
game.draw.drawImage(game.allImg["land"],this.x+this.w*2,this.y)
}
}
class Bird{
constructor(){
this.w = game.allImg["bird0_0"].width;
this.h = game.allImg["bird0_0"].height;
this.x = game.canvas.width/2 - this.w/2;
this.y = game.canvas.height * (1-0.618) - 50;
this.wing = 0;
this.status = "drop";//up
this.changY = 0;
this.rotate = 0;
}
update(){
if(this.status == "drop"){
this.changY += 0.5;
this.y += this.changY;
this.rotate += 0.08;
}else if(this.status == "up"){
this.changY -= 0.6;
this.changY <= 0 ? this.status = "drop" : null;
this.y -= this.changY;
this.y <= 0 ? this.y = 0 : null;
this.wing >= 2 ? this.wing = 0 : (game.frame%4==0 ? this.wing++ : null);
}
//34*24,小鸟上下左右四个值
this.x1 = this.x - 17;
this.x2 = this.x + 17;
this.y1 = this.y - 12;
this.y2 = this.y + 12;
if(this.y >= game.canvas.height - game.allImg["land"].height){
// this.y = game.canvas.height - game.allImg["land"].height;
game.SM.enter(3);
}
}
render(){
game.draw.save();//包裹一下,只对里面的图起作用
//下落旋转,更改中心点,坐标改为图片宽高负数的一半
game.draw.translate(this.x,this.y);
game.draw.rotate(this.rotate);
game.draw.drawImage(game.allImg["bird0_" + this.wing],-this.w/2,-this.h/2);
game.draw.restore();
}
fly(){
this.changY = 6;
this.rotate = -1.2;
this.status = "up";
}
}
class Pipe{
constructor(){
this.w = game.allImg['pipe_down'].width;
this.h = game.allImg['pipe_down'].height;
this.h1 = Math.round(Math.random()*200+100);//上面管子截取高度
this.space = 140;//空隙
this.h2 = game.canvas.height - game.allImg["land"].height - this.h1 - this.space;//下面管子截取高度
this.x = game.canvas.width;
this.speed = 1;
this.done = true;
//每new一个管子放进数组中
game.pipeArr.push(this);
}
update(){
this.x -= this.speed;
//销毁没用的管子
for(let i=0;i<game.pipeArr.length;i++){
if(game.pipeArr[i].x <= -this.w){
game.pipeArr.splice(i,1);
i--;
}
}
//管子的上下左右
this.x1 = this.x;
this.x2 = this.x + this.w;
this.y1 = this.h1;
this.y2 = this.h1 + this.space;
//碰撞检测
if(game.bird.x2 >= this.x1 && game.bird.x1 <= this.x2 && (game.bird.y1 <= this.y1 || game.bird.y2 >= this.y2)){
// clearInterval(game.timer)
game.SM.enter(3);
}
if(this.done && game.bird.x1 > this.x2){
game.score++;
this.done = false;
}
}
render(){
game.draw.drawImage(game.allImg["pipe_down"],0,this.h-this.h1,this.w,this.h1,this.x,0,this.w,this.h1);
game.draw.drawImage(game.allImg["pipe_up"],0,0,this.w,this.h2,this.x,this.h1+this.space,this.w,this.h2);
}
}
class SceneManager{
constructor(){
game.bg = new Background();//实例化
game.land = new Land();
this.bindEvent();
}
enter(number){
switch (number){
case 0://第1场景
this.titleW = game.allImg["title"].width;
this.titleX = game.canvas.width/2 - this.titleW/2;
this.titleY = -50;
this.btnX = game.canvas.width/2 - game.allImg["button_play"].width/2;
this.btnY = game.canvas.height;
this.birdX = game.canvas.width/2 - game.allImg["bird0_0"].width/2;
this.birdY = 220;
this.valve = true;
this.birdChangeY = 2;
// game.scene = 0;
break;
case 1:
game.scene = 1;
this.tutorialW = game.allImg["tutorial"].width;
this.tutorialX = game.canvas.width/2 - this.tutorialW/2;
this.tutorialY = 250;
this.alpha = 1
this.birdChangeA = 0.03;
break;
case 2:
game.scene = 2;
game.bird = new Bird();
game.pipeArr = [];
break;
case 3:
game.scene = 3;
this.isBoom = false;
this.index = 0;
break;
case 4:
game.scene = 4;
let arr = JSON.parse(localStorage.getItem("FB"));
if(arr.length!=0){
if(game.score == 0){
this.model = "medals_0";
}else{
for(let i=0;i<=arr.length;i++){
if(!arr[0]&&game.score >= arr[0]){
this.best = game.score;
arr[0] = game.score;
this.model = "medals_1";
}else if(!arr[1]&&game.score >= arr[1]){
arr[1] = game.score;
this.model = "medals_2";
}else if(!arr[2]&&game.score >= arr[2]){
arr[2] = game.score;
this.model = "medals_3";
}else{
this.model = "medals_0";
}
}
}
}else{
arr[0] = game.score;
this.model = "medals_1";
}
this.best = arr[0];
localStorage.setItem("FB",JSON.stringify(arr));
this.overX = game.canvas.width/2 - game.allImg["game_over"].width/2;
this.overY = -80;
this.panelX = game.canvas.width/2 - game.allImg["score_panel"].width/2;
this.panelY = game.canvas.height;
break;
}
}
updateAndRender(){
switch (game.scene){
case 0://第1场景
game.bg.render();//渲染
game.land.render();
this.titleY >= 160 ? this.titleY = 160 : this.titleY += 5;
this.btnY <= 400 ? this.btnY = 400 : this.btnY -= 10;
// if(this.valve){
// this.birdY > 350 ? this.valve = false : this.birdY += 3;
// }else{
// this.birdY < 220 ? this.valve = true : this.birdY -= 3;
// }
if(this.birdY > 300 || this.birdY < 220){
this.birdChangeY *= -1
}
this.birdY += this.birdChangeY;
game.draw.drawImage(game.allImg["title"],this.titleX,this.titleY);
game.draw.drawImage(game.allImg["button_play"],this.btnX,this.btnY);
game.draw.drawImage(game.allImg["bird0_0"],this.birdX,this.birdY);
break;
case 1:
game.bg.render();
game.land.render();
game.draw.drawImage(game.allImg["bird0_0"],game.canvas.width/2-24,150);
game.draw.save();
if(this.alpha > 1 || this.alpha < 0.3){
this.birdChangeA *= -1
}
this.alpha += this.birdChangeA;
game.draw.globalAlpha = this.alpha;
game.draw.drawImage(game.allImg["tutorial"],this.tutorialX,this.tutorialY);
game.draw.restore();
break;
case 2:
game.bg.update();
game.bg.render();
game.land.update();
game.land.render();
if(game.frame%180 == 0){
new Pipe();
}
game.pipeArr.forEach((item)=>{
item.update();
item.render();
})
game.bird.update();
game.bird.render();
this.scoreRender();
break;
case 3:
game.bg.render();
game.land.render();
game.pipeArr.forEach((item)=>{
item.render();
})
if(this.isBoom){
this.index++;
if(this.index > 8){
this.enter(4);
return;
}
game.draw.drawImage(game.allImg["baozha" + this.index],game.bird.x-50,game.bird.y-50,100,100)
}else{
game.bird.y += 5;
if(game.bird.y >= game.canvas.height - game.allImg["land"].height) this.isBoom = true;
game.bird.render();
}
break;
case 4:
game.bg.render();
game.land.render();
this.overY >= 160 ? this.overY = 160 : this.overY+=5;
this.panelY <= 250 ? this.panelY = 250 : this.panelY-=10;
game.draw.drawImage(game.allImg["game_over"],this.overX,this.overY);
game.draw.drawImage(game.allImg["score_panel"],this.panelX,this.panelY);
game.draw.drawImage(game.allImg[this.model],this.panelX+32,this.panelY+44);
game.draw.fillStyle = "#666";
game.draw.font = "20px consolas";
game.draw.textAlign = "right";
game.draw.fillText(game.score,(game.canvas.width/2)+90,this.panelY+50);
game.draw.fillText(this.best,(game.canvas.width/2)+90,this.panelY+96);
break;
}
}
bindEvent(){
game.canvas.onclick = (e)=>{
switch (game.scene){
case 0://第1场景
if(e.offsetX >= this.btnX && (e.offsetX <= this.btnX + 116) && e.offsetY >= this.btnY && (e.offsetY <= this.btnY + 70)){
this.enter(1);
}
break;
case 1:
this.enter(2);
break;
case 2:
game.bird.fly();
break;
case 3:
break;
case 4:
this.enter(1);
break;
}
}
}
scoreRender(){
let str = game.score.toString();
let line = game.canvas.width/2 - str.length*30/2;
for(let i=0;i<str.length;i++){
game.draw.drawImage(game.allImg["shuzi" + str[i]],i*30+line,100)
}
}
}
class Game{
constructor(){
/** @type {HTMLCanvasElement} */
this.canvas = document.getElementById("canvas");
//创建一个绘制环境
this.draw = this.canvas.getContext("2d");
let W = document.documentElement.clientWidth > 420 ? 420 : document.documentElement.clientWidth;
let H = document.documentElement.clientHeight > 750 ? 750 : document.documentElement.clientHeight;
this.canvas.width = W;
this.canvas.height = H;
this.timer = null;
this.frame = 0;
this.score = 0;//分数
this.scene = 0;//记录场景编号
if(!localStorage.getItem("FB")){
localStorage.setItem("FB","[]")
}
this.imgLoad();
// this.bindEvent();
};
//清屏
clear(){
this.draw.clearRect(0,0,this.canvas.width,this.canvas.height);
};
//开始
start(){
this.SM = new SceneManager();
this.SM.enter(this.scene)
/*
this.bg = new Background();//实例化
this.land = new Land();
this.bird = new Bird();
this.pipeArr = [];
*/
this.timer = setInterval(()=>{
this.frame++;
this.clear();
this.SM.updateAndRender();
/*
this.clear();
this.bg.update();//更新
this.bg.render();//渲染
this.land.update();
this.land.render();
if(this.frame%180 == 0){
new Pipe();
}
//将数组中存放的管子更新渲染出来
this.pipeArr.forEach((item)=>{
item.update();
item.render();
})
this.bird.update();
this.bird.render();
*/
},20)
};
imgLoad(){
this.allImg={
"bg_day":"images/bg_day.png",
"bg_night":"images/bg_night.png",
"land":"images/land.png",
"pipe_down":"images/pipe_down.png",
"pipe_up":"images/pipe_up.png",
"bird0_0":"images/bird0_0.png",
"bird0_1":"images/bird0_1.png",
"bird0_2":"images/bird0_2.png",
"title":"images/title.png",
"button_play":"images/button_play.png",
"tutorial":"images/tutorial.png",
"number_score_00":"images/number_score_00.png",
"number_score_01":"images/number_score_01.png",
"number_score_02":"images/number_score_02.png",
"number_score_03":"images/number_score_03.png",
"number_score_04":"images/number_score_04.png",
"number_score_05":"images/number_score_05.png",
"number_score_06":"images/number_score_06.png",
"number_score_07":"images/number_score_07.png",
"number_score_08":"images/number_score_08.png",
"number_score_09":"images/number_score_09.png",
"blink_00":"images/blink_00.png",
"blink_01":"images/blink_01.png",
"blink_02":"images/blink_02.png",
"medals_0":"images/medals_0.png",
"medals_1":"images/medals_1.png",
"medals_2":"images/medals_2.png",
"medals_3":"images/medals_3.png",
"game_over":"images/text_game_over.png",
"score_panel":"images/score_panel.png",
"shuzi0":"images/font_048.png",
"shuzi1":"images/font_049.png",
"shuzi2":"images/font_050.png",
"shuzi3":"images/font_051.png",
"shuzi4":"images/font_052.png",
"shuzi5":"images/font_053.png",
"shuzi6":"images/font_054.png",
"shuzi7":"images/font_055.png",
"shuzi8":"images/font_056.png",
"shuzi9":"images/font_057.png",
"baozha1":"images/Bomb1.png",
"baozha2":"images/Bomb2.png",
"baozha3":"images/Bomb3.png",
"baozha4":"images/Bomb4.png",
"baozha5":"images/Bomb5.png",
"baozha6":"images/Bomb6.png",
"baozha7":"images/Bomb7.png",
"baozha8":"images/Bomb8.png",
};
let count = 0,total = Object.keys(this.allImg).length;
for(let key in this.allImg){
let img = new Image();
img.src = this.allImg[key];
img.onload = ()=>{
this.allImg[key] = img;
count++;
if(count>=total) this.start();
}
}
};
/*
bindEvent(){
this.canvas.onclick = ()=>{
this.bird.fly();
}
}
*/
}