flappy bird制作全流程:

一、前言

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

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

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

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

二、技术要点

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

三、思路整理

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

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

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

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

将整个游戏细化:

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

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

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

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

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

四、游戏实现:

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

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

<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的时候再执行主函数;

function  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()这个定时器的好处是根据浏览器的性能来执行一个函数,我们用来获取两次绘制的间隔时间;

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

var 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;
  •  小鸟的下落需要用到一定物理知识,但是都很简单啦。 我们都是通过速度×时间来实现;
var 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;
}

小鸟的构造函数及动作控制

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

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

load(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;
} )
})

绘制小鸟,点击小鸟上飞

效果如下:

2.2天空的绘制:

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

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

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

var 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是用来设置无缝滚动的

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

  这里仅插入部分相关代码

var 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 地面的绘制

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

var 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; //无缝滚动的实现
}
}

地面的构造函数及运动函数

//创建----放置在创建区域
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绘制管道

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

  要点:

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

  

var  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; //切换后的管道必须重新设置一个高度,给用户一个新管道的错觉
}
}

管道的构造函数及运动函数

//创建区域
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 判断游戏是否犯规

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

简单判读gameover

  2. 碰到管道结束游戏

//x和y到时候我们传入小鸟的运动轨迹,每次重绘管道都有判断
Pipe.prototype.hitTest = function(x,y){
return (x > this.x && x < this.x + 52) //在管子横向中间
&&(! (y >this.r && y < this.r +150)); //在管子竖向中间
}

判断是否碰到管子

 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);
}

主循环的判断条件整合

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

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

3.游戏的优化

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

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

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

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

Bird.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();
}

加入小鸟旋转效果

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

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

最后做出的效果如下:

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

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

canvas 制作flappy bird(像素小鸟)全流程的更多相关文章

  1. 【Unity3D基础教程】给初学者看的Unity教程(四):通过制作Flappy Bird了解Native 2D中的RigidBody2D和Collider2D

    作者:王选易,出处:http://www.cnblogs.com/neverdie/ 欢迎转载,也请保留这段声明.如果你喜欢这篇文章,请点[推荐].谢谢! 引子 在第一篇文章[Unity3D基础教程] ...

  2. 【转】通过制作Flappy Bird了解Native 2D中的RigidBody2D和Collider

    作者:王选易,出处:http://www.cnblogs.com/neverdie/ 欢迎转载,也请保留这段声明.如果你喜欢这篇文章,请点[推荐].谢谢! 引子 在第一篇文章[Unity3D基础教程] ...

  3. 通通制作Html5小游戏——第二弹(仿flappy bird像素鸟)

    亲爱的博友们,我又回来啦~因为我们技术宅的思想只有技术宅懂得,好不容易写了点好玩的东西发QQ空间,结果只有11的UV,0回复....10分钟ps一个女神的素描效果发QQ空间朋友圈,一大堆回复加赞,作为 ...

  4. 【Unity3D基础教程】给初学者看的Unity教程(三):通过制作Flappy Bird了解Native 2D中的Sprite,Animation

    作者:王选易,出处:http://www.cnblogs.com/neverdie/ 欢迎转载,也请保留这段声明.如果你喜欢这篇文章,请点[推荐].谢谢! 引子 上一次我们讲了MonoBehaviou ...

  5. 【转】通过制作Flappy Bird了解Native 2D中的Sprite,Animation

    作者:王选易,出处:http://www.cnblogs.com/neverdie/ 欢迎转载,也请保留这段声明.如果你喜欢这篇文章,请点[推荐].谢谢! 引子 上一次我们讲了MonoBehaviou ...

  6. windows下如何制作和应用数字签名证书 全流程

    目前我们在发布应用程序时,有时用户下载后会被360杀毒当做木马直接隔离.为应用程序可执行文件打上数字签名可以让360杀毒放宽检测规则.下文是讲述如何制作数字签名证书的过程. 需要准备的工具:makec ...

  7. 自己动手写游戏:Flappy Bird

    START:最近闲来无事,看了看一下<C#开发Flappy Bird游戏>的教程,自己也试着做了一下,实现了一个超级简单版(十分简陋)的Flappy Bird,使用的语言是C#,技术采用了 ...

  8. 用Phaser来制作一个html5游戏——flappy bird (一)

    Phaser是一个简单易用且功能强大的html5游戏框架,利用它可以很轻松的开发出一个html5游戏.在这篇文章中我就教大家如何用Phaser来制作一个前段时间很火爆的游戏:Flappy Bird,希 ...

  9. 用Phaser来制作一个html5游戏——flappy bird (二)

    在上一篇教程中我们完成了boot.preload.menu这三个state的制作,下面我们就要进入本游戏最核心的一个state的制作了.play这个state的代码比较多,我不会一一进行说明,只会把一 ...

随机推荐

  1. 关于Linux和Windows下部署mysql.data.dll的注册问题

    mysql ado.net connector下载地址: http://dev.mysql.com/downloads/connector/net/ 选择版本: Generally Available ...

  2. 微信官方开源UI库-WeUI

    概述 WeUI是一套同微信原生视觉体验一致的基础样式库,为微信Web开发量身设计,可以令用户的使用感知更加统一.包含button.cell.dialog.toast.article.icon等各式元素 ...

  3. 微信小程序开发工具测评

    1月9日微信小程序正式上线.很多企业都希望能在这个.但是在技术开发的问题上,却不知道该如何下手.经过一些程序员不辞辛苦连夜测试,终于从十余款工具呕心沥血筛选出四款比较靠谱实用的微信小程序开发工具.接下 ...

  4. 网站使用https协议

    了解https HTTPS 是以安全为目标的 HTTP 通道,即 HTTP 下加入 SSL 加密层.HTTPS 不同于 HTTP 的端口,HTTP默认端口为80,HTTPS默认端口为443. SSL ...

  5. Atitit.每月数据采集与备份 v4

    Atitit.每月数据采集与备份 v4 备份检查表 r12 00cate 00item im Inputmethod  ok ok Log Log ok cyar Cyar log  ... ok c ...

  6. Atitit 图像处理的摩西五经attilax总结

    Atitit 图像处理的摩西五经attilax总结 1. 数字图像处理(第三版)1 2. 图像处理基础(第2版)(世界著名计算机教材精选)1 3. 计算机视觉特征提取与图像处理(第三版)2 4. Op ...

  7. 动态给textView加图片

    Drawable img = layout.getResources().getDrawable(R.drawable.icon); // 调用setCompoundDrawables时,必须调用Dr ...

  8. SQL Server 存储中间结果集

    在SQL Server中执行查询时,有一些操作会产生中间结果集,例如:排序操作,Hash Join和Hash Aggregate操作产生的Hash Table,游标等,SQL Server查询优化器使 ...

  9. Ninesky源代码从Codeplex迁移到开源中国

    原来Ninesky代码一直发在Codeplex.com上,最近两三个星期了代码一直迁入不上去,网站访问也经常出错. 所以把代码放到开源中国去了,项目地址https://git.oschina.net/ ...

  10. jQuery:实现两个<select>控件的互移操作

    一.直接上代码 <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> < ...