一、利用中介者模式进行业务搭建
- Game是游戏中介者类,它的作用就是中介者的作用
- Bird小鸟类,作用是对小鸟的业务进行封装
- Land大地类,封装地面的样式
- Background背景类,作用是封装背景图
- PiPe管子类,作用是随机出现管子
辅助插件 underscore.js
图片传个资源包吧
二、Game类
(function () {
window.Game = function () {
//获取画布设置上下文
this.canvas = document.querySelector("canvas");
this.ctx = this.canvas.getContext("2d");
//维护资源
this.R = {
"bg_day" : "images/bg_day.png",
"land" : "images/land.png",
"pipeDown" : "images/pipe_up.png",
"pipeUp" : "images/pipe_down.png",
"bird0_0" : "images/bird0_0.png",
"bird0_1" : "images/bird0_1.png",
"bird0_2" : "images/bird0_2.png",
"bg_night" : "images/bg_night.png",
"title" : "images/title.png",
"button" : "images/button.png",
"tutorial" : "images/tutorial.png",
"0" : "images/font_048.png",
"1" : "images/font_049.png",
"2" : "images/font_050.png",
"3" : "images/font_051.png",
"4" : "images/font_052.png",
"5" : "images/font_053.png",
"6" : "images/font_054.png",
"7" : "images/font_055.png",
"8" : "images/font_056.png",
"9" : "images/font_057.png"
}
//维护类的数组
this.types = [];
//伟华一个管子类
this.pipes = [];
//图片总数
this.allAmout = _.keys(this.R).length;
//维护一个对象,值是img对象,和this.R 一样有同样的k
this.Robj = {}
//加载资源
this.loadResource();
//帧编号
this.frame = 0;
//绑定点击事件
//this.bindEvent();
//初始化场景设置
this.sceneNum = 0;
//加分
this.score = 0;
}
//加载图片
Game.prototype.loadResource = function () {
//已加载资源数量
var already = 0;
for(var k in this.R){
//创建img
this.Robj[k] = new Image();
//添加src属性
this.Robj[k].src = this.R[k];
//备份上下文
var self = this;
this.Robj[k].onload = function () {
//擦除画布
self.ctx.clearRect(0,0,self.canvas.width,self.canvas.height);
already ++ ;
//渲染图片的加载状态
self.ctx.font = "20px 微软雅黑";
self.ctx.fillText("已加载"+already+"/"+self.allAmout+"张",20,40);
//判断是否加载完
if(already == self.allAmout){
//alert("加载完了")
self.start();
}
}
// console.log(this.Robj[k])
}
}
Game.prototype.start = function () {
//创建场景管理器
this.scene = new SceneManager();
//初始的场景状态
this.scene.enter(this.sceneNum);
var self = this;
game.timer = setInterval(function(){
self.scene.render();
},20)
/*var self = this;
//背景
this.bg = new Background();
//创建大地
this.land = new Land();
//小鸟
this.bird = new Bird();
setInterval(() => {
//擦除画布
self.ctx.clearRect(0,0,this.canvas.width,this.canvas.height)
//设置游戏主循环
self.loop();
}, 10);*/
}
//游戏的主循环
Game.prototype.loop = function () {
/* _.each(this.types,function (type) {
type.update();
type.render();
})
this.frame ++;
if(this.frame % 70 == 0){
this.pipes.push(new Pipe());
}
//帧编号
this.ctx.font = "14px 微软雅黑";
this.ctx.fillText("帧编号" +this.frame,10,20)*/
}
Game.prototype.bindEvent = function () {
var self = this;
this.canvas.onclick = function () {
self.bird.fly()
}
}
Game.prototype.addScore = function () {
var ge = this.score % 10;
var shi = parseInt( this.score / 10);
console.log(ge,shi)
this.ctx.drawImage(this.Robj[ge],this.canvas.width/2,0,24,44)
this.ctx.drawImage(this.Robj[shi],this.canvas.width/2-30,0,24,44)
}
})()
三、Background类
(function () {
window.Background = function () {
game.types.push(this);
//背景图
this.image = game.Robj["bg_day"];
//初始位置
this.x = 0;
}
Background.prototype.update = function () {
//移动
this.x -=1;
//如果图片走出屏幕 归位
if(this.x < -game.canvas.width){
this.x = 0;
}
}
Background.prototype.render = function () {
//console.log(this.image)
game.ctx.drawImage(this.image,this.x,0,game.canvas.width,game.canvas.height);
//设置假图避免露白
game.ctx.drawImage(this.image,game.canvas.width+this.x,0,game.canvas.width,game.canvas.height);
}
})()
四、Land类
(function () {
window.Land = function () {
//类放到 game维护类的数组中
game.types.push(this);
//设置图片
this.image = game.Robj["land"];
//初始位置
this.x = 0;
}
Land.prototype.update = function () {
this.x -= 3;
if(this.x < -game.canvas.width){
this.x = 0;
}
}
Land.prototype.render = function () {
//画图
game.ctx.drawImage(this.image,this.x,game.canvas.height-112);
//设置假图
game.ctx.drawImage(this.image,game.canvas.width+this.x,game.canvas.height-112);
}
})()
五、PiPe类
(function(){
window.Pipe = function(){
//将自己存放到game中
game.types.push(this);
//设置管子图片
this.pipeDown = game.Robj["pipeUp"];
this.pipeUp = game.Robj["pipeDown"];
//随机数---上面管子的高度
this.randomTopHeight = _.random(30,220);
//两根管之间的距离
this.space = 120;
//下面管子的高度
//canvas的高度-上面管子的高度-固定空间的高度-大地的高度;
this.randomBottomHeight = game.canvas.height - this.randomTopHeight - this.space - 112;
//下面管子的位置 top值上面的管子的高度+固定空间高度
this.bottomPipePosition = this.randomTopHeight + this.space;
//console.log(this.bottomPipePosition)
//初始x轴坐标
this.x = 300;
}
Pipe.prototype.update = function(){
this.x -= 2;
//超出屏幕的管子 从数组中删除
if(this.x < -52){
game.pipes = _.without(game.pipes,this);
game.types = _.without(game.types,this);
}
//管子的坐标
this.x1 = this.x;
this.x2 = this.x + 52;
this.y1 = this.randomTopHeight;
this.y2 = this.randomTopHeight + this.space;
game.ctx.fillText("x1:"+ this.x1,this.x1 - 50, this.y1);
game.ctx.fillText("x2:"+ this.x2,this.x2 + 10, this.y1);
game.ctx.fillText("y1:"+ this.y1,this.x1, this.y1 + 20);
game.ctx.fillText("y2:"+ this.y2,this.x1, this.y1 + 100);
//碰管判断
if(game.bird.x2 > this.x1 && game.bird.x1 < this.x2 && game.bird.y2 > this.y2 ||
game.bird.x2 > this.x1 && game.bird.x1 < this.x2 && game.bird.y1 < this.y1 ||
game.bird.y2 > game.canvas.height - 112){
//alert("碰到了")
//碰到管子立即停止定时器
clearInterval(game.timer);
//重启定时器完成部分逻辑
game.timer = setInterval(() => {
//背景渲染
game.background.render();
//大地动起来
game.land.render();
//管子渲染
_.each(game.pipes,function (type) {
type.render();
})
//小鸟状态
//头朝下
game.bird.rot = 2.2;
//向下运动
game.bird.direction = "DOWN";
//更新渲染小鸟
game.bird.update();
game.bird.render();
//小鸟落地后清除定时器
if(game.bird.y >= game.canvas.height - 118){
clearInterval(game.timer);
//恢复场景
game.scene = new SceneManager();
//game.ctx.clearRect(0,0,game.canvas.width,game.canvas.height);
game.scene.enter(0);
//场景渲染更新
game.timer=setInterval(function () {
game.scene.render();
},20)
}
}, 20);
}else{
console.log(game.bird.x1)
if(game.bird.x1 == this.x2){
game.score ++;
}
}
}
Pipe.prototype.render = function(){
//渲染图片
game.ctx.drawImage(this.pipeDown,0,320-this.randomTopHeight,52,this.randomTopHeight,this.x,0,52,this.randomTopHeight);
game.ctx.drawImage(this.pipeUp,0,0,52,this.randomBottomHeight,this.x,this.bottomPipePosition,52,this.randomBottomHeight);
}
})()
六、Bird类
(function () {
window.Bird = function () {
//存到game的 维护类的数组中
game.types.push(this);
//小鸟的图片数组
this.imageArr = [game.Robj["bird0_0"],game.Robj["bird0_1"],game.Robj["bird0_2"]];
//小鸟默认第一张图片
this.wing = 0;
//小鸟默认位置
this.x = 60;
this.y = 100;
//小鸟初始化向下运动 有限状态机
this.direction = "DOWN";
//小鸟的宽高
this.width = 48;
this.height = 48;
//小鸟下落的 加速度
this.idx = 0;
//小鸟下落的时候 头朝下 利用变形的 rotate 设置默认角度
this.rot = 0.4;
}
Bird.prototype.update = function () {
/* this.wing ++ ;
if(this.wing > 2){
this.wing = 0;
} */
//效果同上
if(game.frame % 4 == 0){
this.wing = ++this.wing%3;
}
//判断上升还是下落
if(this.direction == "DOWN"){
//翅膀不挥舞
this.wing = 0;
this.rot = 0.4;
this.idx += 0.2;
this.y += this.idx;
}else{
//小鸟上升
this.rot = -0.4;
this.idx += -0.2;
// 如果idy的值为负数,则代表小鸟是一个下降的状态
if(this.idx > 0){
this.y -= this.idx;
}else{
// idy小于0 的逻辑,两个作用,一个是实际上更改有限状态机的状态为DOWN,第二个作用是小鸟如果是下落不应该挥动翅膀
this.direction = "DOWN";
}
}
//超出大地
if(this.y > game.canvas.height-118) this.y = game.canvas.height - 118;
//超出顶部
if(this.y < 8) this.y = 8;
//测试文字
game.ctx.font = "14px 微软雅黑";
game.ctx.fillStyle="red";
game.ctx.fillText("idx"+this.idx,20, 40);
game.ctx.fillText("y"+this.y,20,60);
//小鸟的坐标
this.x1 = this.x - 10;
this.x2 = this.x + 10;
this.y1 = this.y - 14 ;
this.y2 = this.y + 14;
game.ctx.fillText("x1:"+this.x1,this.x1 - 50,this.y1 + 15);
game.ctx.fillText("x2:"+this.x2,this.x2 + 10,this.y1 + 15);
game.ctx.fillText("y1:"+this.y1,this.x1 + 20,this.y1 - 15);
game.ctx.fillText("y2:"+this.y2,this.x1 + 20,this.y1 + 40);
}
Bird.prototype.render = function () {
//利用变形进行运动
//备份
game.ctx.save();
//平移
game.ctx.translate(this.x,this.y);
//旋转
game.ctx.rotate(this.rot);
//旋转中心为 图片中心
//game.ctx.drawImage(this.imageArr[this.wing],this.width,this.height);
game.ctx.drawImage(this.imageArr[this.wing],-this.width/2,-this.height/2);
game.ctx.restore();
}
Bird.prototype.fly = function () {
this.direction = "UP";
this.idx = 5;
}
})()
七、场景管理器
将所有的场景内容都封装到一个场景管理器的类中,所有的更新渲染逻辑都在场景管理器中,而不是在game中
(function(){
window.SceneManager = function(){
this.bindEvent();
//小鸟初始运动方向
this.moveDirection = "DOWN";
//帧编号
this.frame = 0;
}
//进入场景的方法
SceneManager.prototype.enter = function(num){
//对应场景初始化逻辑
game.sceneNum = num;
switch(game.sceneNum){
case 0 :
//入场背景图
this.w = game.canvas.width;
this.h = game.canvas.height;
//title 图片
this.titleY = -50;
//小鸟图
this.birdY = 150;
//渲染按钮
break;
case 1 :
//小鸟图
this.birdY = 40;
//引导图初始透明度
this.alpha = 1;
//是否变透明了
this.isAlpha = true;
break;
case 2 :
//维护管子类
game.pipes = [];
//初始化 管子 和 类
game.types = [];
//创建背景大地
game.background = new Background();
game.land = new Land();
//new 小鸟
game.bird = new Bird();
//分数初始化
game.score = 0;
break;
}
}
//渲染和更新
SceneManager.prototype.render = function(){
this.frame ++;
//对应场景渲染逻辑
switch(game.sceneNum){
case 0 :
//渲染入场背景图
game.ctx.drawImage(game.Robj["bg_night"],0,0);
//渲染title图
this.titleY += 2;
if(this.titleY > 100) this.titleY = 100;
game.ctx.drawImage(game.Robj["title"],game.canvas.width/2 - 89,this.titleY);
//小鸟图 让小鸟来回不同运动
if(this.moveDirection == "DOWN"){
this.birdY += 2;
if(this.birdY > 250) this.moveDirection = "UP";
}else{
this.birdY -= 2;
if(this.birdY < 150) this.moveDirection = "DOWN";
}
//渲染小鸟
game.ctx.drawImage(game.Robj["bird0_0"],game.canvas.width/2 - 24,this.birdY);
//渲染btn
game.ctx.drawImage(game.Robj["button"],game.canvas.width/2 - 58,300)
break;
case 1 :
//渲染背景图
game.ctx.drawImage(game.Robj["bg_day"],0,0);
//小鸟图 让小鸟来回不同运动
if(this.moveDirection == "DOWN"){
this.birdY += 2;
if(this.birdY > 120) this.moveDirection = "UP";
}else{
this.birdY -= 2;
if(this.birdY < 40) this.moveDirection = "DOWN";
}
//渲染小鸟
game.ctx.drawImage(game.Robj["bird0_0"],game.canvas.width/2 - 24,this.birdY);
//透明度 变形
game.ctx.save();
//不停的闪
if(this.isAlpha){
this.alpha -= 0.1;
if(this.alpha <= 0) this.isAlpha = false;
}else{
this.alpha += 0.1;
if(this.alpha >=1) this.isAlpha = true;
}
game.ctx.globalAlpha = this.alpha;
game.ctx.drawImage(game.Robj["tutorial"],game.canvas.width/2 - 57, 250)
game.ctx.restore();
//引导图
break;
case 2 :
//渲染背景大地
_.each(game.types,function (type) {
type.update();
type.render();
})
//new 管子
//game.pipes = new Pipe();
if(this.frame % 100 == 0){
game.pipes.push(new Pipe());
}
game.addScore()
break;
}
}
SceneManager.prototype.bindEvent = function(){
var self = this;
game.canvas.onclick = function(event){
//鼠标点击位置
var x = event.offsetX;
var y = event.offsetY;
switch (game.sceneNum) {
case 0:
if(x > game.canvas.width/2 - 58 && x < game.canvas.width/2 + 58 && y > 300 && y < 364){
self.enter(1);
}
break;
case 1:
if(x > game.canvas.width/2 - 57 && x < game.canvas.width/2 + 57 && y > 250 && y < 250+98){
self.enter(2);
}
break;
case 2:
game.bird.fly();
break;
}
}
}
})()
index.html
<body>
<canvas width="288" height="512"></canvas>
<script src="js/underscore.js"></script>
<script src="js/Game.js"></script>
<script src="js/Background.js"></script>
<script src="js/Land.js"></script>
<script src="js/Pipe.js"></script>
<script src="js/Bird.js"></script>
<script src="js/SceneManager.js"></script>
<script>
var game = new Game()
</script>
</body>