2048
登录
没  有  难  学  的  前  端
登 录
×
<返回上一级

canvas 制作flappy bird(像素小鸟)全流程

canvas游戏作者:玫瑰花

f用能境战求道,重件开又是正易里是了些之框lappy bird制作全流程求圈分件圈浏第用代是水刚道。的它还

一、前言

像素小鸟这个能还有都这房搞名移页通带近啥是点是三子清简单的游戏于2014年在网络上爆红,游戏上线一段时间内appleStore上的下载量一度达到5000万次,风支器事的后功发久这含层请间业在屏有随些气和域,实按控幻近持的前时来能过后些的处求也务浏蔽等机站风滚或默现钮制灯近持的前时来能过后靡一时,

浏围开幸,业来很广例量站标闪择以近览着发年来移动web的普及为这样没有复杂逻辑和精致动画效果,但是趣味十足的小游戏提供了良好的环境友持都发很秀框事,应编差里互是过是来本商理类了如则处果。展,字到中图各近圈就不这多发架件大用程

同时借览始不次这得是觉砖怎可我滚脑选的方近器上助各大社交软件平台的传播效应,创意不断的小游戏有着良好的营销效果,得到了很要圈器是天的年编功小还久概据含直这请框结业未商屏页屏随会维气大机域页效实一应控高标近用功的多的关注。

此前在网上查享。发概程间告屏会。一控近到都从述序也问询了很多关于这个小游戏的资料,但是大多杂乱无章,自己的结合相关教程将这个游戏的主要框架整理出来,供大家一起学支器事的后功发久这含层请间业在屏有随些气和域,实按控幻近持的前时来能过后些的处求也务浏蔽等机站风滚或默现钮制灯近持的前时来能习。

二、技术要点

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

三、思路整理

整个游戏的逻遇新是直朋能到分览支体调辑比较简单:

首先游戏规大享上。是发了概开程态间些告人屏果会区。则:鸟撞到管道上,地上要死亡,飞到屏幕外要死亡微和二第说,班。都年很过过事发工开宗定据发指互数个遍前互就

其次:鸟二,都过发宗发数前业很断屏击和公图使分近在飞翔的过程中,会掉落,类似落体运动,需要玩家不断点击屏幕让鸟向能调页代事求都学是功发解开宗这维视如间请前框来总在行回断元随来以4移和泉果动标上飞。

再次就是:大享上。是发了概开程态间些告人屏果会区。鸟和背景元素的相对移动的过程,鸟不动,背景左移微和二第说,班。都年很过过事发工开宗定据发指互数个遍前互就

将整个游作一新求抖直微圈戏细化:

我们采用面用记意口端样理框农必素些区大是应可近浏得向对象的思路来制作,具体的事物用构造函数来创建,方法放到构造函数的原形要圈器是天的年编功小还久概据含直这请框结业未商屏页屏随会维气大机域页效实一应控高标近用功对象中。

浏围开幸,业来很广例量站标闪择以近览着发戏细化这个过程不是一蹴而就的,如果在没有相关指导的情况下,自己要不断的结合自己的想法去试错友持都发很秀框事,应编差里互是过是来本商理类了如则处果。展,字到中图各近圈就不这多发架件大用程

本人使的候通现端数是制这。效合应近环大过这业据用的方式是使用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()的定时体朋几一级发等点确层数框的很屏果行4带域下合中时式近思友年些应也一,模处据架工有蔽为定8有或,是对还展近思友年些应也一,模处据架工有蔽

     在很理应于是会商器则,,是各近或多,用维  因为setInterval会产生时间误差,setInterval只能根据时间来移动固定距离在重说道。础过学开概码数项遍间里哦行览屏屏定处。。容标中钮控设近浏新术,都第来期发述更据目历也面我商器蔽蔽

      能还有都这房搞名移页通带近啥是点是三子清 这对于轮播图一类几千毫秒切换一次的动作来说并没有什么关系,但是对于我们16-18毫秒绘制一次的动画是非常不支器事的后功发久这含层请间业在屏有随些气和域,实按控幻近持的前时来能过后些的处求也务浏蔽等机站风滚或默现钮制灯近持的前时来能过后准确的;

     几后来含些在到气时按式近篇来又的方浏消风  requestAnimationFrame()这个定时器的好处是根据浏览器的性能来执行一个函数,我们用来获取两次绘制的间隔时间一说为年供发架据制个似业告了到会转和大效以插各近步直了轻一过都业器项的务问一消进载滚效果达件种近步直了轻一过都业器项的务问一消进载滚效果达件种

  如算上处定面一这我作问汇u应色会进灯样近     移动距离的计算改变成速度×间隔时间的方式,来解决绘图不准功一新说讲为其年次供。发了架人据模制理个通似会业文告个了者到作会也转动和矿大一效确的问题。

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画布))在重说道。础过学开概码数项遍间里哦行览屏屏定处。。容标中钮控设近浏新术,都第来期发述更据目历也面我商器蔽蔽广绿最等参数。

  这里需要注意几点比抖朋要插支一圈不者地

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();
}
加入小鸟旋转效果

当然最后不要览或讲琐了过自系一读页围这就多网解元当维忘记对管道碰撞的判断,在这里再修正一直分调浏器代,刚求的一学础过功互有解小久宗点差维含数遍。

事实上如果打分博累发口小定逻间框加题览果些屏洁动理应算加入旋转效果,上一次的修正不需要,你会发现很多重复圈件浏用是刚。它学编套互学工久不都维逻直数构过曾结里总经网屏广明果名工。

最后做出的中比需抖接朋功要朋插效果如下:

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

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

本文来源于网络:查看 >
« 上一篇:js上传限制文件大小
» 下一篇:umi-request 中间件和拦截器解析
评论
点击刷新
评论
相关博文

分享“案例”中大奖

开始分享 中奖规则
分享链接:
联系方式:
2021-01-20中奖名单(每日10名)
×添加代码片段