计算机教程

必赢娱乐棋牌:[Canvas前端游戏开发]——FlappyBird详解

21 3月 , 2019  

[Canvas前端游戏开发]——FlappyBird详解

2016/01/03 · HTML5 ·
Canvas

原文出处: xingoo   

一直想自己做点小东西,直到最近看了本《HTML5游戏开发》,才了解游戏开发中的一点点入门知识。

本篇就针对学习的几个样例,自己动手实践,做了个FlappyBird,源码共享在度盘 ;也可以参考github,里面有更多的游戏样例。

将整个游戏细化:

我们采用面向对象的思路来制作,具体的事物用构造函数来创建,方法放到构造函数的原形对象中。

游戏细化这个过程不是一蹴而就的,如果在没有相关指导的情况下,自己要不断的结合自己的想法去试错。

本人使用的方式是使用Xmind将流程以脑图的形式绘制下来,分块去做,不断细化记录自己的思路,最终呈现的效果如下:

(顺序按照图片中的序号去看
 脑图、素材、及完整源码下载地址:http://pan.baidu.com/s/1c130V7M
想练习的同学可以点这里)

脑图分为三大块:1、准备阶段 2、主函数 3、游戏优化。

必赢娱乐棋牌 1

必赢娱乐棋牌 2

 

 

游戏截图

必赢娱乐棋牌 3

必赢娱乐棋牌 4

一、前言

像素小鸟这个简单的游戏于2014年在网络上爆红,游戏上线一段时间内appleStore上的下载量一度达到5000万次,风靡一时,

近年来移动web的普及为这样没有复杂逻辑和精致动画效果,但是趣味十足的小游戏提供了良好的环境,

同时借助各大社交软件平台的传播效应,创意不断的小游戏有着良好的营销效果,得到了很多的关注。

此前在网上查询了很多关于这个小游戏的资料,但是大多杂乱无章,自己的结合相关教程将这个游戏的主要框架整理出来,供大家一起学习。

总结

在学习游戏开发的时候,我突然怀念起大学的物理。当时很纳闷,学计算机学什么物理,后来再接触游戏开发才知道,没有一定的物理知识,根本无法模拟游戏中的各个场景。

而通过这个简单的小游戏,也捡起来了很多旧知识。

flappy bird制作全流程:

必赢娱乐棋牌 5

FlappyBird原理解析

其实这个游戏很简单,一张图就可以看懂其中的奥妙:

必赢娱乐棋牌 6

其中背景和地面是不动的。

小鸟只有上和下两个动作,可以通过控制小鸟的y坐标实现。

上下的管子只会向左移动,为了简单实现,游戏中一个画面仅仅会出现一对管子,这样当管子移出左边的背景框,就自动把管子放在最右边!

if(up_pipe.px+up_pipe.pwidth>0){ up_pipe.px -= velocity;
down_pipe.px -= velocity; }else{ up_pipe.px = 400; down_pipe.px =
400; up_pipe.pheight = 100+Math.random()*200; down_pipe.py =
up_pipe.pheight+pipe_height; down_pipe.pheight = 600-down_pipe.py;
isScore = true; }

1
2
3
4
5
6
7
8
9
10
11
if(up_pipe.px+up_pipe.pwidth>0){
                up_pipe.px -= velocity;
                down_pipe.px -= velocity;
            }else{
                up_pipe.px = 400;
                down_pipe.px = 400;
                up_pipe.pheight = 100+Math.random()*200;
                down_pipe.py = up_pipe.pheight+pipe_height;
                down_pipe.pheight = 600-down_pipe.py;
                isScore = true;
            }

很简单吧!

由于该游戏一共就这几个元素,因此把他们都放入一个Objects数组中,通过setInteral()方法,在一定间隔时间内,执行一次重绘

重绘的时候会先清除画面中的所有元素,然后按照新的元素的坐标一次绘制图形,这样就会出现移动的效果。

整个游戏的逻辑比较简单:

首先游戏规则:鸟撞到管道上,地上要死亡,飞到屏幕外要死亡。

其次:鸟在飞翔的过程中,会掉落,类似落体运动,需要玩家不断点击屏幕让鸟向上飞。

再次就是:鸟和背景元素的相对移动的过程,鸟不动,背景左移。

HTML5之Canvas

Canvas是Html5中用于绘图的元素,它可以绘制各种图形,比如长方形,多边形,圆形等等。如果想要了解Canvas的使用可以参考:

http://www.w3school.com.cn/tags/html_ref_canvas.asp

 

//如果想要使用canvas,首先需要获得上下文对象: ctx =
document.getElementById(‘canvas’).getContext(‘2d’);
//然后使用这个ctx绘制图形

1
2
3
//如果想要使用canvas,首先需要获得上下文对象:
ctx = document.getElementById(‘canvas’).getContext(‘2d’);
//然后使用这个ctx绘制图形

在cavas每个绘制都是独立的操作。比如下图的两个绘制图形,第二个会以覆盖的形式绘制,因此绘制图形的顺序就显得十分重要了。

必赢娱乐棋牌 7

二、技术要点

 基本JavaScript基础 ,canvas 基础, 面向对象的思想;

canvas之drawImage()

本篇的游戏开发中,主要使用的是依据图片绘制的api:drawImage(),它有两个基本的使用方法:

ctx.drawImage(image,this.bx,this.by,this.bwidth,this.bheight);
ctx.drawImage(image,x,y,width,height,this.px,this.py,this.pwidth,this.pheight);

1
2
ctx.drawImage(image,this.bx,this.by,this.bwidth,this.bheight);
ctx.drawImage(image,x,y,width,height,this.px,this.py,this.pwidth,this.pheight);

第一个api中,指定Image对象,然后给出绘制图片的x,y坐标以及宽度和高度即可。

第二个api中,第一组x,y,width,height则指定了裁剪图片的坐标尺寸,这在使用多元素的矢量图时很常用。比如:

必赢娱乐棋牌 8

上面的图片中为了减少图片资源的请求数量,把很多的元素放在了一个图片中,此时就需要通过裁剪的方式,获取指定的图片元素。

 四、游戏实现:

现在结合脑图来逐步实现我们的游戏。

1.设置canvas画布,准备图片数据,当图片加载完成后执行回调函数;

必赢娱乐棋牌 9<canvas
id=”cvs” width=”800″ height=”600″></canvas> <script> var
imglist = [ { “name”:”birds”,”src”:”res/birds.png”}, {
“name”:”land”,”src”:”res/land.png”}, {
“name”:”pipe1″,”src”:”res/pipe1.png”}, {
“name”:”pipe2″,”src”:”res/pipe2.png”}, {
“name”:”sky”,”src”:”res/sky.png”} ]; var cvs =
document.getElementById(“cvs”); var ctx = cvs.getContext(“2d”);
</script> 画布准备
,图片数据准备

这里这个入口函数的设置要注意,必须保证图片资源加载完成后再执行其他操作,每加载一张图片我们让imgCount–,减到0的时候再执行主函数;

必赢娱乐棋牌 10function
load (source, callback ){ var imgEls={}; var imgCount=source.length; for
(var i = 0; i < imgCount; i++) { var name = source[i].name; var
newImg = new Image (); newImg.src = source[i].src; imgEls[name] =
newImg; imgEls[name].addEventListener(“load”,function(){ imgCount–;
if(imgCount==0){ callback(imgEls); }; }) }; }; 入口函数设置

主循环的设置:这里我们不使用setInterval来控制循环次数,我们使用一个叫requestAnimationFrame()的定时器

       因为setInterval会产生时间误差,setInterval只能根据时间来移动固定距离。

       这对于轮播图一类几千毫秒切换一次的动作来说并没有什么关系,但是对于我们16-18毫秒绘制一次的动画是非常不准确的;

       requestAnimationFrame()这个定时器的好处是根据浏览器的性能来执行一个函数,我们用来获取两次绘制的间隔时间;

       移动距离的计算改变成速度×间隔时间的方式,来解决绘图不准确的问题。

必赢娱乐棋牌 11var
preTime= Date.now(); //获取当前时间 function run(){ var now =
Date.now(); //获取最新时间 dt = now – preTime; //获取时间间隔 preTime =
now; //更新当前时间 ctx.clearRect(0,0,800,600); //清空画布
//——————————————— 绘制代码执行区域
//———————————————–
requestAnimationFrame(run); //再次执行run函数 }
requestAnimationFrame(run); //首次执行run函数; 设置绘制方式

2、主函数分为两部分功能
,简单说就是把图画上去,然后处理动态效果,再判断一下是否犯规。

2.1 小鸟的绘制:

  小鸟本身有一个翅膀扇动的效果,和一个下落的过程。

  翅膀扇动的过程是一张精灵图三幅画面的的切换(设置一个index属性,控制精灵图的位置),下落过程是其y坐标在画布上的移动();

  所以小鸟的构造函数中应该包括(图源,x坐标,y坐标,速度,下落加速度,ctx(context画布))等参数。

  这里需要注意几点:

  •  小鸟的绘制采用canvas
    drawImage的九参数模式(分别是图片,原图的裁切起点,原图的宽高,贴到画布上的位置,贴到画布上的宽高);
  •  小鸟的翅膀扇动不能太快,所以我们设置一个阀门函数,当累计计时超过100ms的时候切换一下图片,然后在让累计计时减去100ms;
  •  小鸟的下落需要用到一定物理知识,但是都很简单啦。
    我们都是通过速度×时间来实现;

必赢娱乐棋牌 12var Bird
= function (img,x,y,speed,a,ctx){ this.img = img; this.x = x; this.y =
y; this.speed = speed; this.a =a ; this.ctx = ctx; this.index = 0;
//用于制作小鸟扇翅膀的动作 } Bird.prototype.draw = function (){
this.ctx.drawImage( this.img,52*this.index,0,52,45, this.x,this.y,52,45
) } var durgather=0; Bird.prototype.update = function(dur){
//小鸟翅膀扇动每100ms切换一张图片 durgather+=dur; if(durgather>100){
this.index++; if(this.index===2){ this.index=0; } durgather -= 100; }
//小鸟下落动作 this.speed = this.speed + this.a *dur; this.y = this.y +
this.speed * dur; } 小鸟的构造函数及动作控制

 
构造一个小鸟,并且将其动作刷新函数和绘制函数放置在我们上面提到的绘制区域,此后构造出的类似对象都是这样的操作步骤:

 
这里需要注意的一点是,如何让小鸟顺畅的向上飞翔,其实还是物理知识,由于加速度的作用,我们给小鸟一个向上的顺时速度就可以了。

必赢娱乐棋牌 13load(imglist
,function(imgEls){ //创建对象 //在主函数中创建一个小鸟 var bird = new
Bird(imgEls[“birds”],150,100,0.0003,0.0006,ctx); //主循环 var preTime=
Date.now(); function run(){ var now = Date.now(); dt = now – preTime;
preTime = now; ctx.clearRect(0,0,800,600); //——–图片绘制区域——-
bird.update(dt) bird.draw(); //————————-
requestAnimationFrame(run); } requestAnimationFrame(run);
//设置点击事件。给小鸟一个瞬时的向上速度
cvs.addEventListener(“click”,function(){ bird.speed = -0.3; } ) }) 绘制小鸟,点击小鸟上飞

效果如下:

必赢娱乐棋牌 14

2.2天空的绘制:

  天空的绘制比较简单了,只要使用canvas
drawImage的三参数模式就可以(图源,画布上的坐标)。

  这里唯一注意的一点是,无缝滚动的实现,对于800*600分辨率这种情况我们创建两个天空对象就可以了,但是为了适配更多的情况,我们将这个功能写活

  在天空的构造函数上加一个count属性设置几个天空图片,count属性让实例通过原形中的方法访问。后面涉及到重复出现的地面和管道,都给它们添加这种考虑。

必赢娱乐棋牌 15var Sky =
function(img,x,speed,ctx) { this.img = img ; this.ctx = ctx; this.x = x;
this.speed = speed; } Sky.prototype.draw = function(){
this.ctx.drawImage( this.img ,this.x,0 ) } Sky.prototype.setCount =
function(count){ Sky.count = count; } Sky.prototype.update =
function(dur){ this.x = this.x+ this.speed * dur; if(this.x<-800){
//天空图片的宽度是800 this.x = Sky.count * 800 + this.x;
//当向左移动了一整张图片后立刻切回第一张图片 } } 天空构造函数及运动函数

  同理在主函数中创建2个天空对象,并将更新函数和绘制函数放置在主循环的绘制区域;

  setcount是用来设置无缝滚动的

  注意一点:绘制上的图片是有一个层级关系的,不能把鸟画到天空的下面,那当然最后画鸟了,下面涉及到的覆盖问题不再专门提到。

  这里仅插入部分相关代码

必赢娱乐棋牌 16var bird
= new Bird(imgEls[“birds”],150,100,0.0003,0.0006,ctx); var sky1 = new
Sky(imgEls[“sky”],0,-0.3,ctx); var sky2 = new
Sky(imgEls[“sky”],800,-0.3,ctx); //主循环 var preTime= Date.now();
function run(){ var now = Date.now(); dt = now – preTime; preTime = now;
ctx.clearRect(0,0,800,600); //——–图片绘制区域——-
sky1.update(dt); sky1.draw() sky2.update(dt); sky2.draw()
sky1.setCount(2); bird.update(dt) bird.draw();
//————————- 绘制天空

2.3 地面的绘制

  和天空的绘制完全一样,由于地面图片尺寸较小,所以我们要多画几个

必赢娱乐棋牌 17var Land
= function(img,x,speed,ctx){ this.img = img ; this.x = x; this.speed =
speed; this.ctx = ctx ; } Land.prototype.draw = function(){
this.ctx.drawImage ( this.img , this.x ,488 ) } Land.prototype.setCount=
function(count){ Land.count = count; } Land.prototype.update =
function(dur){ this.x = this.x + this.speed * dur; if (this.x <-
336){ this.x = this.x + Land.count * 336; //无缝滚动的实现 } } 地面的构造函数及运动函数
必赢娱乐棋牌 18//创建—-放置在创建区域
var land1 = new Land(imgEls[“land”],0,-0.3,ctx); var land2 = new
Land(imgEls[“land”],336*1,-0.3,ctx); var land3 = new
Land(imgEls[“land”],336*2,-0.3,ctx); var land4 = new
Land(imgEls[“land”],336*3,-0.3,ctx); //绘制 —-放置在绘制区域
land1.update(dt); land1.draw(); land2.update(dt); land2.draw();
land3.update(dt); land3.draw(); land4.update(dt); land4.draw();
land1.setCount(4); //设置无缝滚动 绘制地面主要代码

2.4绘制管道

  管道的绘制有一个难点是管道高度的确定

  要点:

  •  为了保障游戏可玩性,管道必须有一个固定高度+一个随机高度,且上下管道之间的留白是固定的宽度。
  • 管道不是连续的,两个相邻的管道之间有间隔
  • 注意管道在无缝播放,抽回后必须付给一个新的随机高度,给用户一种错觉,以为又一个管道飘了过来。

  

必赢娱乐棋牌 19var Pipe
= function(upImg,downImg,x,speed,ctx){ this.x = x; this.upImg = upImg ;
this.downImg = downImg; this.speed = speed; this.ctx = ctx; this.r =
Math.random() *200 + 100; //随机高度+固定高度 } Pipe.prototype.draw =
function(){ this.ctx.drawImage( this.upImg, this.x , this.r – 420
//管道图片的长度是420 ) this.ctx.drawImage( this.downImg, this.x ,
this.r +150 //管道中建的留白是150px ) } Pipe.prototype.setCount =
function( count,gap ){ Pipe.count = count; Pipe.gap = gap;
//这里是这次绘制的特别之处,加入了间隔 } Pipe.prototype.update
=function( dur ){ this.x = this.x + this.speed*dur; if(this.x <-
52){ //管道宽度52px this.x = this.x + Pipe.count * Pipe.gap; //无缝滚动
this.r = Math.random() *200 + 150;
//切换后的管道必须重新设置一个高度,给用户一个新管道的错觉 } } 管道的构造函数及运动函数
必赢娱乐棋牌 20//创建区域
var pipe1 = new Pipe(imgEls[“pipe2”],imgEls[“pipe1”],400, -0.1,ctx);
var pipe2 = new Pipe(imgEls[“pipe2”],imgEls[“pipe1”],600, -0.1,ctx);
var pipe3 = new Pipe(imgEls[“pipe2”],imgEls[“pipe1”],800, -0.1,ctx);
var pipe4 = new Pipe(imgEls[“pipe2”],imgEls[“pipe1”],1000,-0.1,ctx);
var pipe5 = new Pipe(imgEls[“pipe2”],imgEls[“pipe1”],1200,-0.1,ctx);
//绘制区域 pipe1.update(dt); pipe1.draw(); pipe2.update(dt);
pipe2.draw(); pipe3.update(dt); pipe3.draw(); pipe4.update(dt);
pipe4.draw(); pipe5.update(dt); pipe5.draw(); pipe1.setCount(5,200);
//设置管道数量和间隔 管道的绘制主要代码

到这一步我们的主要画面就制作出来了,是不是很简单呢O(∩_∩)O~

2.5 判断游戏是否犯规

必赢娱乐棋牌 21
//我们改造一下主循环,设置一个gameover为false来控制函数的执行
//任何违规都会触发gameover=true; var gameover = false; if(bird.y < 0
|| bird.y > 488 -45/2 ){ //碰到天和地 gameover = true ; }
if(!gameover){ //如果没有结束游戏则继续游戏 requestAnimationFrame(run);
} 简单判读gameover

  2. 碰到管道结束游戏

必赢娱乐棋牌 22//x和y到时候我们传入小鸟的运动轨迹,每次重绘管道都有判断
Pipe.prototype.hitTest = function(x,y){ return (x > this.x && x <
this.x + 52) //在管子横向中间 &&(! (y >this.r && y < this.r
+150)); //在管子竖向中间 } 判断是否碰到管子
必赢娱乐棋牌 23 var
gameover = false; gameover = gameover || pipe1.hitTest(bird.x ,bird.y);
gameover = gameover || pipe2.hitTest(bird.x ,bird.y); gameover =
gameover || pipe3.hitTest(bird.x ,bird.y); gameover = gameover ||
pipe4.hitTest(bird.x ,bird.y); gameover = gameover ||
pipe5.hitTest(bird.x ,bird.y); //逻辑终端 if(bird.y < 0 || bird.y
> 488 -45/2 ){ gameover = true ; } if(!gameover){
requestAnimationFrame(run); } 主循环的判断条件整合

必赢娱乐棋牌 24

到这一步我们的游戏完成的差不多了,剩下的就是部分数据的修正

主要需要修正的一个点是碰撞的计算,因为我们所有的碰撞都是按照小鸟图片的左上角计算的,这样就会有不准确的问题,通过测试很容易将这个距离加减修正了

 

3.游戏的优化

 小鸟游戏的鸟儿在上下的过程中会随着点击,抬头飞翔,或低头冲刺,如何做到这个效果呢?

 答案就是移动canvas 坐标系和选择坐标系的角度
 ctx.translate()和ctx.rotate();

 为了防止整个坐标系的整体旋转移动

 需要在小鸟绘制函数Bird.prototype.draw里面前后端加入ctx.save()
和ctx.restore()来单独控制小鸟画布

必赢娱乐棋牌 25Bird.prototype.draw
= function (){ this.ctx.save(); this.ctx.translate(this.x ,this.y);
//坐标移动到小鸟的中心点上 this.ctx.rotate((Math.PI /6) * this.speed /
0.3 ); //小鸟最大旋转30度,并随着速度实时改变角度 this.ctx.drawImage(
this.img,52*this.index,0,52,45, -52/2,-45/2,52,45
//这里很重要的一点是,整个小鸟坐标系开始移动 ) this.ctx.restore(); }
加入小鸟旋转效果

当然最后不要忘记对管道碰撞的判断,在这里再修正一遍。

事实上如果打算加入旋转效果,上一次的修正不需要,你会发现很多重复工。

最后做出的效果如下:

必赢娱乐棋牌 26

 主体效果和逻辑已经全部实现。更多的效果可以自行添加。

 如果想自己练习一下,请点击游戏细化部分的链接下载相关素材和全部源码。

http://www.bkjia.com/HTML5/1152289.htmlwww.bkjia.comtruehttp://www.bkjia.com/HTML5/1152289.htmlTechArticlecanvas 制作flappy
bird(像素小鸟)全流程,canvasflappy flappy bird制作全流程: 一、前言
像素小鸟这个简单的游戏于2014年在网络上爆红,游戏上…


相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图