@[TOC](Flappy Bird(Canvas 实现))
资源源码下载https://download.csdn.net/download/qq_37644845/11267687
Flappy Bird
一、Canvas基础
- 创建 canvas 标签注意事项
[html]
<canvas width="360" height="520" id="myCanvas"></canvas>
[javascript]
// 获取canvas对象
/**@type { HTMLCanvasElement }*/
var canvas=document.getElementById('myCanvas');
// 获取画布对象
var ctx=canvas.getContext('2d');
[注]
canvas标签:宽高最好写在标签内而不是css样式表,否则画布可能会变形
/**@type { HTMLCanvasElement }*/:为了标记变量canvas的类型,方便下文调用其属和方法;
- canvas 绘图操作
// 1、画方块(坐标(100,100);宽高(100,100))
ctx.fillRect(100,100,100,100);
// 2、清除画布
ctx.clearRect(0,0,canvas.weight,canvas.height);
// 3、加载图片
var image=new Image();
image.src='images/xx.png';
image.onload=function(){
//ctx.drawImage();的参数的个数可变化为3、5、9
ctx.drawImage(image,100,100);
}
// 4、其余 略
二、组成实现
- 实现背景移动
// 在实现功能之前先搭一个简易的游戏框架(非常简易)
[js]
// 使用了SimpleJavaScriptInheritance.js(网上可下载、方便进行类的继承)
[Game.js]
(function(){
var Game=window.Game=Class.extend({
init:function(){
// 构造函数
},
loadResources:function(){
// 相当于原生js使用prototype.loadResources
// 加载资源、所有图片加载完成后才能进行渲染
},
start:function(){
// 游戏的主循环位置,游戏画面每秒 50 帧左右
this.timer=setInterval(function(){
},20);
}
, bindEvent: function () {
// 添加方法、主要是交互方法
}
});
})();
// 使用中介者模式来创建游戏实例子
var game=new Game();
[Background.js]
// 突然就不想总结的这么详细了,那么就把我认为重要的写下
// 略
- 实现管子添加
// 略
- 小鸟飞行、以及画布事件检测
// 略
终. 效果图
三、场景管理器的引用(升级版)
- 游戏实现多场景切换:
将游戏中元素加载、刷新放入场景管理器中并设置一个SceneNumber来确定当前场景
结构如下
(function () {
var Scene = window.Scene = Class.extend({
init: function () {
// 场景0 准备
// 场景1 游戏教程
// 场景2 游戏开始
// 场景3 小鸟死亡
// 场景4 分数界面
this.sceneNumber = 0;
},
update: function () {
// 游戏主循环中刷新的函数
switch (self.sceneNumber) {
case 0:break;
case 1:break;
case 2:break;
case 3:break;
case 4:break;
}
},
render: function () {
// 游戏主循环中加载的函数
switch (self.sceneNumber) {
case 0:break;
case 1:break;
case 2:break;
case 3:break;
case 4:break;
}
},
enter: function (sceneNumber) {
// 游戏切换场景的函数
switch (self.sceneNumber) {
case 0:break;
case 1:break;
case 2:break;
case 3:break;
case 4:break;
}
},
bindEvent: function (event) {
var mX = event.clientX;
var mY = event.clientY;
switch (self.sceneNumber) {
case 0:break;
case 1:break;
case 2:break;
case 3:break;
case 4:break;
}
}
});
})();
终.
- 场景1 开始
2. 场景2
3.场景3 本人降低了游戏的难度、便于测试、开口极大
4、场景4 死亡爆炸场景
5. 场景5 分数场景
四、部分源码(升级版)
- SceneManage.js 场景管理器
// 场景管理器
(function () {
var Scene = window.Scene = Class.extend({
init: function () {
// 场景0 准备
// 场景1 游戏教程
// 场景2 游戏开始
// 场景3 小鸟死亡
this.sceneNumber = 0;
// 场景0 logo y值
//this.s_0_logoY = 0;
//this.s_0_btnY = game.canvas.height;
//this.s_0_globalAlpha = 1;
//this.s_0_globalAlpha_isRise = false;
场景3 中 的小帧号\ 爆炸编号
//this.s_3_fno = 0;
//this.s_3_boom = 0;
//this.s_3_bird_isShow = true;
场景4、升起成绩版 \小帧号
//this.s_4_fno = 0;
//this.s_4_textY = 0;
//this.s_4_panelY = game.canvas.height;
当前成绩的奖牌
//this.s_4_medals = null;
// 进入场景0;
this.enter(0);
this.bindEvent();
},
update: function () {
switch (this.sceneNumber) {
case 0:
game.bg.update();
game.land.update();
this.s_0_logoY += 5;
if (this.s_0_logoY > 100) {
this.s_0_logoY = 100;
}
this.s_0_btnY -= 12;
if (this.s_0_btnY < 250) {
this.s_0_btnY = 250;
}
break;
case 1:
game.bg.update();
game.land.update();
break;
case 2:
game.bg.update();
game.land.update();
break;
case 3:
break;
case 4:
this.s_4_textY += (20 - this.s_4_fno * 10);
this.s_4_panelY -= ((parseInt(game.canvas.height - 165) / 5) - this.s_4_fno * 10);
if (this.s_4_textY > 100) {
this.s_4_textY = 100;
}
if (this.s_4_panelY < 165) {
this.s_4_panelY = 165;
}
break;
default:
}
},
render: function () {
// 加载背景
game.bg.render();
// 加载大地
game.land.render();
switch (this.sceneNumber) {
case 0:
game.ctx.drawImage(game.R["logo"], game.canvas.width / 2 - game.R["logo"].width / 2, this.s_0_logoY);
game.ctx.drawImage(game.R["button_play"], game.canvas.width / 2 - game.R["button_play"].width / 2, this.s_0_btnY);
// 加载鸟、保持鸟在屏幕中间飞
game.bird.update();
game.bird.y = 165;
game.bird.x = game.canvas.width / 2 - game.bird.images[0].width / 2;
game.bird.d = 0;
game.bird.fno = 0;
game.bird.render();
break;
case 1:
// 加载鸟、保持鸟在屏幕中间飞
game.bird.update();
game.bird.y = 165;
game.bird.x = game.canvas.width / 2 - game.bird.images[0].width / 2;
game.bird.d = 0;
game.bird.fno = 0;
game.bird.render();
game.ctx.save();
this.s_0_globalAlpha +=(this.s_0_globalAlpha_isRise ? 0.1 : -0.1);
if (this.s_0_globalAlpha > 0.9 || this.s_0_globalAlpha<0.1) {
this.s_0_globalAlpha_isRise = !this.s_0_globalAlpha_isRise;
}
game.ctx.globalAlpha = this.s_0_globalAlpha;
//this.s_0_globalAlpha
game.ctx.drawImage(game.R['tutorial'], game.canvas.width / 2 - game.R['tutorial'].width / 2, game.bird.y + 100);
game.ctx.restore();
game.ctx.drawImage(game.R['text_ready'], game.canvas.width / 2 - game.R['text_ready'].width / 2,100);
break;
case 2:
if (game.f % 80 === 0) {
new Pipe();
}
for (var i = 0; i < game.actors.length; i++) {
// 加载所有管子类
if (game.actors[i] instanceof Pipe) {
game.actors[i].update();
game.actors[i].render();
}
}
game.bird.update();
game.bird.render();
// 计算得分
for (var i = 0; i < game.score.toString().length; i++) {
game.ctx.drawImage(game.R['shu' + game.score.toString().charAt(i)], game.canvas.width / 2 - game.score.toString().length *14 + 28 * i, 82);
}
break;
case 3:
this.s_3_bird_isShow ? game.bird.render() : '';
for (var i = 0; i < game.actors.length; i++) {
// 加载所有管子类
if (game.actors[i] instanceof Pipe) {
game.actors[i].render();
}
}
// 计算得分
for (var i = 0; i < game.score.toString().length; i++) {
game.ctx.drawImage(game.R['shu' + game.score.toString().charAt(i)], game.canvas.width / 2 - game.score.toString().length * 14 + 28 * i, 82);
}
// 如果鸟不在地上,让其落在地上
if (game.bird.y < 368) {
game.bird.y += 0.25 * this.s_3_fno;
this.s_3_fno++;
} else {
this.s_3_bird_isShow = false;
game.ctx.drawImage(game.R['bird_boom'], (this.s_3_boom % 4) * 128, parseInt(this.s_3_boom / 4) * 128, 128, 128, game.bird.x - 38, game.bird.y - 38, 128, 128);
this.s_3_boom++;
if (this.s_3_boom > 16) {
// 进入场景4
game.scene.enter(4);
}
}
break;
case 4:
game.ctx.drawImage(game.R["text_game_over"], game.canvas.width / 2 - game.R["text_game_over"].width / 2, this.s_4_textY);
game.ctx.drawImage(game.R["score_panel"], game.canvas.width / 2 - game.R["score_panel"].width / 2, this.s_4_panelY);
game.ctx.drawImage(this.s_4_medals, game.canvas.width / 2 - game.R["score_panel"].width / 2 + 30, this.s_4_panelY + 44);
game.ctx.drawImage(game.R["button_menu"], game.canvas.width / 2 - game.R["button_menu"].width / 2, this.s_4_panelY + 144);
game.ctx.font = '20px 微软雅黑';
game.ctx.fillText(game.score, game.canvas.width / 2 - game.R["score_panel"].width / 2 + 208 - game.score.toString().length*11, this.s_4_panelY + 53);
game.ctx.fillText(game.bestScore, game.canvas.width / 2 - game.R["score_panel"].width / 2 + 208 - game.bestScore.toString().length * 11, this.s_4_panelY + 95);
break;
default:
}
},
enter: function (sceneNumber) {
this.sceneNumber = sceneNumber;
switch (this.sceneNumber) {
case 0:
game.score = 0;
// 场景0 logo y值
this.s_0_logoY = 0;
this.s_0_btnY = game.canvas.height;
this.s_0_globalAlpha = 1;
this.s_0_globalAlpha_isRise = false;
// 固定位置
game.bird.y = 165;
game.bird.x = game.canvas.width / 2 - game.bird.images[0].width / 2;
// 清除水管
for (var i = (game.actors.length-1); i >=0; i--) {
if (game.actors[i] instanceof Pipe) {
game.actors.splice(i, 1);
}
}
break;
case 1:
break;
case 2: break;
case 3:
场景3 中 的小帧号\ 爆炸编号
this.s_3_fno = 0;
this.s_3_boom = 0;
this.s_3_bird_isShow = true;
break;
case 4:
// 场景4、升起成绩版 \小帧号
this.s_4_fno = 0;
this.s_4_textY = 0;
this.s_4_panelY = game.canvas.height;
this.s_4_medals = null;
// 进入第4场景、存储分数
game.historyScore.push(game.score);
if (game.score > game.bestScore) {
game.bestScore = game.score;
}
// 获取奖牌
if (game.score < 5) {
this.s_4_medals = game.R['medals_0'];
} else if (game.score < 10 && game.score>=5) {
this.s_4_medals = game.R['medals_1'];
} else if (game.score < 20 && game.score >= 10) {
this.s_4_medals = game.R['medals_2'];
} else if (game.score >= 20) {
this.s_4_medals = game.R['medals_3'];
}
break;
default:
}
},
bindEvent: function () {
var self = this;
game.canvas.onclick = function (event) {
var mX = event.clientX;
var mY = event.clientY;
switch (self.sceneNumber) {
case 0:
if (mY > (self.s_0_btnY + 4) && mY < (self.s_0_btnY + 60) && mX > (game.canvas.width / 2 - 52) && mX < (game.canvas.width / 2 + 52)) {
self.enter(1);
}
break;
case 1:
self.enter(2);
break;
case 2:
game.bird.fly();
break;
case 3: break;
case 4:
// game.canvas.width / 2 - game.R["button_menu"].width / 2, this.s_4_panelY + 144
if (mY > (self.s_4_panelY + 144) && mY < (self.s_4_panelY + 172) && mX > (game.canvas.width / 2 - game.R["button_menu"].width / 2) && mX < (game.canvas.width / 2 - game.R["button_menu"].width / 2 + 82)) {
// 换背景、换小鸟
for (var i = (game.actors.length-1); i >=0; i--) {
if ((game.actors[i] instanceof Background) || (game.actors[i] instanceof Bird)) {
game.actors.splice(i, 1);
}
}
game.bg = new Background();
game.bird = new Bird();
self.enter(0);
}
break;
default:
}
};
}
});
})();
- Actors.js 管理演员(对象)的类
(function () {
var Actor = window.Actor = Class.extend({
init: function () {
game.actors.push(this);
}
, update: function () {
}
, render: function () {
throw new Error('必须重写render方法');
}
});
})();
- Game.js 游戏类、也是中介者模式中的中介者
(function () {
var Game = window.Game = Class.extend({
// 构造函数
init: function (id) {
/**@type { HTMLCanvasElement }*/
this.canvas = document.getElementById(id);
// 得到上下文
this.ctx = this.canvas.getContext('2d');
// R文件的路径
this.RTextUrl = 'R.txt';
// 自己的图片资源对象,v是图片路径
this.RObj = null;
this.R = {};
// 游戏历史数据
this.historyScore = [];
this.bestScore = 0;
// 所有演员类
this.actors = [];
// 帧编号
this.f = 0;
// 得分
this.score = 0;
var self = this;
// 加载所有资源,这个函数的终点就是循环的开始
this.loadResources(function () {
this.start();
// self.bindEvent();
});
}
// 加载所有资源
,loadResources: function (callback) {
var self = this;
// 已经加载好的图片编号
var count = 0;
// 提示正在加载图片
self.ctx.font = "30px 微软雅黑";
self.ctx.textAlign = 'center';
self.ctx.fillText("正在加载图片...", self.canvas.width / 2, self.canvas.height * (1 - 0.618));
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && (xhr.status == 200 || xhr.status == 304)) {
self.RObj = JSON.parse(xhr.responseText);
// 图片总数
var imageAmount = 0;
for (var i in self.RObj) {
imageAmount++;
}
// 遍历对象
for (var k in self.RObj) {
// 创建image对象
self.R[k] = new Image();
// 发出SRC请求
self.R[k].src = self.RObj[k];
self.R[k].onload = function () {
// 计数器加 1
count++;
// 清屏
self.ctx.clearRect(0, 0, self.canvas.width, self.canvas.height);
self.ctx.fillText("正在加载图片" + count + '/' + imageAmount, self.canvas.width / 2, self.canvas.height * (1 - 0.618));
if (count == imageAmount) {
// 开启游戏主循环
callback.call(self);
}
};
}
}
};
xhr.open('get', this.RTextUrl, true);
xhr.send(null);
}
, start: function () {
var self = this;
// 背景
this.bg = new Background();
// 大地
this.land = new Land();
// 加载鸟
this.bird = new Bird();
this.scene = new Scene();
this.timer = setInterval(function () {
self.ctx.clearRect(0, 0, self.canvas.width, self.canvas.height);
//for (var i = 0; i < self.actors.length; i++) {
// self.actors[i].update();
// self.actors[i].render();
//}
//if (self.f % 80 === 0) {
// new Pipe();
//}
self.scene.update();
self.scene.render();
self.f++;
self.ctx.font = '14px 宋体';
self.ctx.textAlign = 'left';
self.ctx.fillText('FNO ' + self.f, 20, 20);
}, 20);
}
//, bindEvent: function () {
// var self = this;
// this.canvas.onclick = function () {
// self.bird.fly();
// };
//}
});
})();
四、添加互动发音乐(待优化)
音乐素材均为互联网下载
[Music.js]
(function () {
var Music = window.Music = Class.extend({
init: function (src) {
this.dom = document.createElement('audio');
this.dom.src = src;
// this.dom.autoplay = "autoplay";
this.dom.style.display = 'none';
this.dom.controls = "controls";
this.dom.loop = "false";
this.dom.hidden = "true";
this.IsPlay = false;
document.body.appendChild(this.dom);
},
play: function () {
var self = this;
this.IsPlay = true;
this.dom.play();
setTimeout(function () {
self.IsPlay = false;
}, self.dom.duration * 1000);
},
stop: function () {
this.IsPlay = false;
this.dom.pause();
this.dom.load();
}
});
})();
// 配合动画使用,比如小鸟扇动翅膀
// 加载资源的时候创建
var a =new Music(src);
// 动画方法
game.bird.fly();
// 声音
a.play();
setTimeout(function () {
// 定时关闭 ,否则自动单曲循环播放
a.stop();
}, a.dom.duration * 1000);