此前在网上查询了很多关于这个小游戏的资料,

2019-09-22 02:30栏目:前端开发
TAG:

全部源码

<!DOCTYPE html> <html> <head> <title>Flappy Bird</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <script type="text/javascript"> // Edit by xingoo // Fork on my github: var ctx; var cwidth = 400; var cheight = 600; var objects = []; var birdIndex = 0; var ver1 = 10; var ver2; var gravity = 2; var pipe_height = 200; var velocity = 10; var tid; var score = 0; var isScore = false; var birds = ["./images/0.gif","./images/1.gif","./images/2.gif"]; var back = new Background(0,0,400,600,"./images/bg.png"); var up_pipe = new UpPipe(0,0,100,200,"./images/pipe.png"); var down_pipe = new DownPipe(0,400,100,200,"./images/pipe.png"); var ground = new Background(0,550,400,200,"./images/ground.png"); var bird = new Bird(80,300,40,40,birds); objects.push(back); objects.push(up_pipe); objects.push(down_pipe); objects.push(ground); objects.push(bird); function UpPipe(x,y,width,height,img_src){ this.px = x; this.py = y; this.pwidth = width; this.pheight = height; this.img_src = img_src; this.draw = drawUpPipe; } function DownPipe(x,y,width,height,img_src){ this.px = x; this.py = y; this.pwidth = width; this.pheight = height; this.img_src = img_src; this.draw = drawDownPipe; } function drawUpPipe(){ var image = new Image(); image.src = this.img_src; ctx.drawImage(image,150,500,150,800,this.px,this.py,this.pwidth,this.pheight); } function drawDownPipe(){ var image = new Image(); image.src = this.img_src; ctx.drawImage(image,0,500,150,500,this.px,this.py,this.pwidth,this.pheight); } function Background(x,y,width,height,img_src){ this.bgx = x; this.bgy = y; this.bgwidth = width; this.bgheight = height; var image = new Image(); image.src = img_src; this.img = image; this.draw = drawbg; } function drawbg(){ ctx.drawImage(this.img,this.bgx,this.bgy,this.bgwidth,this.bgheight); } function Bird(x,y,width,height,img_srcs){ this.bx = x; this.by = y; this.bwidth = width; this.bheight = height; this.imgs = img_srcs; this.draw = drawbird; } function drawbird(){ birdIndex++; var image = new Image(); image.src = this.imgs[birdIndex%3]; ctx.drawImage(image,this.bx,this.by,this.bwidth,this.bheight); } function calculator(){ if(bird.by+bird.bheight>ground.bgy || ((bird.bx+bird.bwidth>up_pipe.px)&&(bird.by>up_pipe.py)&&(bird.bx+bird.bwidth<up_pipe.px+up_pipe.pwidth)&&( bird.by<up_pipe.py+up_pipe.pheight))|| ((bird.bx+bird.bwidth>up_pipe.px)&&(bird.by>up_pipe.py)&&(bird.bx+bird.bwidth<up_pipe.px+up_pipe.pwidth)&&( bird.by<up_pipe.py+up_pipe.pheight))|| ((bird.bx>down_pipe.px)&&(bird.by>down_pipe.py)&&(bird.bx<down_pipe.px+down_pipe.pwidth)&&(bird.by<down_pipe.py+down_pipe.pheight))|| ((bird.bx>down_pipe.px)&&(bird.by+bird.bheight>down_pipe.py)&&(bird.bx<down_pipe.px+down_pipe.pwidth)&&(bird.by+bird.bheight<down_pipe.py+down_pipe.pheight))){ clearInterval(tid); ctx.fillStyle = "rgb(255,255,255)"; ctx.font = "30px Accent"; ctx.fillText("You got "+score+"!",110,100) return; } ver2 = ver1+gravity; bird.by += (ver2+ver1)*0.5; 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; } if(isScore && bird.bx>up_pipe.px+up_pipe.pwidth){ score += 1; isScore = false; if(score>0 && score%10 === 0){ velocity++; } } ctx.fillStyle = "rgb(255,255,255)"; ctx.font = "30px Accent"; if(score>0){ score%10!==0?ctx.fillText(score,180,100):ctx.fillText("Great!"+score,120,100); } } function drawall(){ ctx.clearRect(0,0,cwidth,cheight); var i; for(i=0;i<objects.length;i++){ objects[i].draw(); } calculator(); } function keyup(e){ var e = e||event; var currKey = e.keyCode||e.which||e.charCode; switch (currKey){ case 32: bird.by -= 80; break; } } function init(){ ctx = document.getElementById('canvas').getContext('2d'); document.onkeyup = keyup; drawall(); tid = setInterval(drawall,80); } </script> </head> <body onLoad="init();"> <canvas id="canvas" width="400" height="600" style="margin-left:200px;"> Your browser is not support canvas! </canvas> </body> </html>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
<!DOCTYPE html>
<html>
<head>
    <title>Flappy Bird</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <script type="text/javascript">
        // Edit by xingoo
        // Fork on my github:https://github.com/xinghalo/CodeJS/tree/master/HTML5
        var ctx;
        var cwidth = 400;
        var cheight = 600;
        var objects = [];
        var birdIndex = 0;
        var ver1 = 10;
        var ver2;
        var gravity = 2;
        var pipe_height = 200;
        var velocity = 10;
        var tid;
        var score = 0;
        var isScore = false;
        var birds = ["./images/0.gif","./images/1.gif","./images/2.gif"];
        var back = new Background(0,0,400,600,"./images/bg.png");
        var up_pipe = new UpPipe(0,0,100,200,"./images/pipe.png");
        var down_pipe = new DownPipe(0,400,100,200,"./images/pipe.png");
        var ground = new Background(0,550,400,200,"./images/ground.png");
        var bird = new Bird(80,300,40,40,birds);
        objects.push(back);
        objects.push(up_pipe);
        objects.push(down_pipe);
        objects.push(ground);
        objects.push(bird);
        function UpPipe(x,y,width,height,img_src){
            this.px = x;
            this.py = y;
            this.pwidth = width;
            this.pheight = height;
            this.img_src = img_src;
            this.draw = drawUpPipe;
        }
        function DownPipe(x,y,width,height,img_src){
            this.px = x;
            this.py = y;
            this.pwidth = width;
            this.pheight = height;
            this.img_src = img_src;
            this.draw = drawDownPipe;
        }
        function drawUpPipe(){
            var image = new Image();
            image.src = this.img_src;
            ctx.drawImage(image,150,500,150,800,this.px,this.py,this.pwidth,this.pheight);
        }
        function drawDownPipe(){
            var image = new Image();
            image.src = this.img_src;
            ctx.drawImage(image,0,500,150,500,this.px,this.py,this.pwidth,this.pheight);
        }
        function Background(x,y,width,height,img_src){
            this.bgx = x;
            this.bgy = y;
            this.bgwidth = width;
            this.bgheight = height;
            var image = new Image();
            image.src = img_src;
            this.img = image;
            this.draw = drawbg;
        }
        function drawbg(){
            ctx.drawImage(this.img,this.bgx,this.bgy,this.bgwidth,this.bgheight);
        }
        function Bird(x,y,width,height,img_srcs){
            this.bx = x;
            this.by = y;
            this.bwidth = width;
            this.bheight = height;
            this.imgs = img_srcs;
            this.draw = drawbird;
        }
        function drawbird(){
            birdIndex++;
            var image = new Image();
            image.src = this.imgs[birdIndex%3];
            ctx.drawImage(image,this.bx,this.by,this.bwidth,this.bheight);
        }
        function calculator(){
            if(bird.by+bird.bheight>ground.bgy ||
                ((bird.bx+bird.bwidth>up_pipe.px)&&(bird.by>up_pipe.py)&&(bird.bx+bird.bwidth<up_pipe.px+up_pipe.pwidth)&&(    bird.by<up_pipe.py+up_pipe.pheight))||
                ((bird.bx+bird.bwidth>up_pipe.px)&&(bird.by>up_pipe.py)&&(bird.bx+bird.bwidth<up_pipe.px+up_pipe.pwidth)&&(    bird.by<up_pipe.py+up_pipe.pheight))||
                ((bird.bx>down_pipe.px)&&(bird.by>down_pipe.py)&&(bird.bx<down_pipe.px+down_pipe.pwidth)&&(bird.by<down_pipe.py+down_pipe.pheight))||
                ((bird.bx>down_pipe.px)&&(bird.by+bird.bheight>down_pipe.py)&&(bird.bx<down_pipe.px+down_pipe.pwidth)&&(bird.by+bird.bheight<down_pipe.py+down_pipe.pheight))){
                clearInterval(tid);
                ctx.fillStyle = "rgb(255,255,255)";
                ctx.font = "30px Accent";
                ctx.fillText("You got "+score+"!",110,100)
                return;
            }
            ver2 = ver1+gravity;
            bird.by += (ver2+ver1)*0.5;
            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;
            }
            if(isScore && bird.bx>up_pipe.px+up_pipe.pwidth){
                score += 1;
                isScore = false;
                if(score>0 && score%10 === 0){
                    velocity++;
                }
            }
            ctx.fillStyle = "rgb(255,255,255)";
            ctx.font = "30px Accent";
            if(score>0){
                score%10!==0?ctx.fillText(score,180,100):ctx.fillText("Great!"+score,120,100);
            }
        }
        function drawall(){
            ctx.clearRect(0,0,cwidth,cheight);
            var i;
            for(i=0;i<objects.length;i++){
                objects[i].draw();
            }
            calculator();
        }
        function keyup(e){
            var e = e||event;
               var currKey = e.keyCode||e.which||e.charCode;
               switch (currKey){
                case 32:
                    bird.by -= 80;
                    break;
            }
        }    
        function init(){
            ctx = document.getElementById('canvas').getContext('2d');
            document.onkeyup = keyup;
            drawall();
            tid = setInterval(drawall,80);
        }
    </script>
</head>
<body onLoad="init();">
<canvas id="canvas" width="400" height="600" style="margin-left:200px;">
    Your browser is not support canvas!
</canvas>
</body>
</html>

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

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

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

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

...

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则指定了裁剪图片的坐标尺寸,这在使用多元素的矢量图时很常用。比如:

大奖888官网登录 1

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

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

完整代码请访问我的github

HTML5之Canvas

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

 

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

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

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

大奖888官网登录 2

一、前言

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

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

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

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

是不是觉得和bird很像,不过就是多了visible属性,这个属性是控制盒子的看见与否,在游戏中的小鸟通过的的水管之间的空隙就是靠它了,

游戏截图

大奖888官网登录 3

大奖888官网登录 4

 四、游戏实现:

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

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

大奖888官网登录 5<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的时候再执行主函数;

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

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

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

大奖888官网登录 8var 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; } 小鸟的构造函数及动作控制

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

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

大奖888官网登录 9load(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; } ) }) 绘制小鸟,点击小鸟上飞

效果如下:

大奖888官网登录 10

大奖888官网登录,2.2天空的绘制:

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

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

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

大奖888官网登录 11var 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是用来设置无缝滚动的

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

  这里仅插入部分相关代码

大奖888官网登录 12var 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 地面的绘制

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

大奖888官网登录 13var 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; //无缝滚动的实现 } } 地面的构造函数及运动函数 大奖888官网登录 14//创建----放置在创建区域 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绘制管道

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

  要点:

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

  

大奖888官网登录 15var 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; //切换后的管道必须重新设置一个高度,给用户一个新管道的错觉 } } 管道的构造函数及运动函数 大奖888官网登录 16//创建区域 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 判断游戏是否犯规

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

  2. 碰到管道结束游戏

大奖888官网登录 18//x和y到时候我们传入小鸟的运动轨迹,每次重绘管道都有判断 Pipe.prototype.hitTest = function(x,y){ return (x > this.x && x < this.x + 52) //在管子横向中间 &&(! (y >this.r && y < this.r +150)); //在管子竖向中间 } 判断是否碰到管子 大奖888官网登录 19 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); } 主循环的判断条件整合

大奖888官网登录 20

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

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

 

3.游戏的优化

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

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

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

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

大奖888官网登录 21Bird.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(); } 加入小鸟旋转效果

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

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

最后做出的效果如下:

大奖888官网登录 22

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

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

制作flappy bird(像素小鸟)全流程,canvasflappy flappy bird制作全流程: 一、前言 像素小鸟这个简单的游戏于2014年在网络上爆红,游戏上...

  1. Box.prototype.draw = function () {  
  2.   
  3.                 // console.log([this.img, this.img.width, this.img.height, this.x, this.y, this.w, this.h]);  
  4.                 ctx.drawImage(this.img, 0, 0, this.img.width, this.img.height, this.x, this.y,  
  5.                     this.w, this.h);  
  6.   
  7.             };  

FlappyBird原理解析

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

大奖888官网登录 23

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

小鸟只有上和下两个动作,可以通过控制小鸟的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()方法,在一定间隔时间内,执行一次重绘

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

将整个游戏细化:

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

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

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

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

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

大奖888官网登录 24

大奖888官网登录 25

 

 

 

参考

【1】:Canvas参考手册

【2】:《HTML5游戏开发》

【3】:EdisonChou的FlappyBird

2 赞 6 收藏 评论

大奖888官网登录 26

二、技术要点

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

接下来是bird的draw属性,这个属性主要是将bird给画出来

分数计算

分数的计算与碰撞检测类似,设置一个开关,当管子重新出现时,设置为true。当分值加1时,设置为false。

小鸟的最左边的x坐标如果超出了管子的x+width,就认为成功通过。

if(isScore && bird.bx>up_pipe.px+up_pipe.pwidth){ score += 1; isScore = false; if(score>0 && score%10 === 0){ velocity++; } }

1
2
3
4
5
6
7
if(isScore && bird.bx>up_pipe.px+up_pipe.pwidth){
                score += 1;
                isScore = false;
                if(score>0 && score%10 === 0){
                    velocity++;
                }
            }

通过后,分值加1,速度+1。

flappy bird制作全流程:

大奖888官网登录 27

这样就基本ok了,接下来也只是一些,初始化而已,这个直接上代码吧

模拟小鸟重力

由于这个游戏不涉及小鸟横向的运动,因此只要模拟出小鸟下落的动作以及上升的动作就可以了。

大奖888官网登录 28

上升:这个很简单,只要把小鸟的y坐标减去一定的值就可以了

下落:其实重力不需要使用gt^2来模拟,可以简单的指定两个变量,v1和gravity,这两个变量与setInterval()中的时间共同作用,就能模拟重力。

ver2 = ver1+gravity; bird.by += (ver2+ver1)*0.5;

1
2
ver2 = ver1+gravity;
bird.by += (ver2+ver1)*0.5;

三、思路整理

  1. var count = 0, timeout, myReq = 0, stopped, requestId = 0;  
  2.             function render() {  
  3.                 if (!stopped) {  
  4.                     ctx.fillStyle = '#ccc';  
  5.                     ctx.fillRect(0, 0, canvas.width, canvas.height);  
  6.                     bird.jump();  
  7.                     pipe1.move();  
  8.                     // 检测碰撞  
  9.                     collision(bird, pipe1);  
  10.                     requestId = window.requestAnimationFrame(render);  
  11.                     console.log(requestId);  
  12.                 }  
  13.             }  
  14.   
  15.             // 绑定鼠标事件  
  16.             document.onclick = function () {  
  17.                 bird.y -= 25;  
  18.             }  
  19.             function start() {  
  20.                 requestId = window.requestAnimationFrame(render);  
  21.                 stopped = false;  
  22.                 // console.log(requestId);  
  23.   
  24.             }  
  25.   
  26.             function stop() {  
  27.                 if (requestId) {  
  28.                     window.cancelAnimationFrame(requestId);  
  29.                 }  
  30.                 stopped = true;  
  31.                 // console.log(requestId);  
  32.             }  
  33.   
  34.             start();  

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

2016/01/03 · HTML5 · Canvas

原文出处: xingoo   

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

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

ok 基本这样ok了,但是我们是不是忘了什么东西啊,想起来了,我们还没有进行碰撞检测呢,如果不进行碰撞检测,那岂不是开挂了,这当然是不行的

碰撞检测

游戏中小鸟碰到管子或者地面都会算游戏结束:

大奖888官网登录 29

其中条件1上管道的检测为:

((bird.bx+bird.bwidth>up_pipe.px)&&(bird.by>up_pipe.py)&&(bird.bx+bird.bwidth<up_pipe.px+up_pipe.pwidth)&&(bird.by<up_pipe.py+up_pipe.pheight))|| ((bird.bx+bird.bwidth>up_pipe.px)&&(bird.by>up_pipe.py)&&(bird.bx+bird.bwidth<up_pipe.px+up_pipe.pwidth)&&(bird.by<up_pipe.py+up_pipe.pheight))

1
2
((bird.bx+bird.bwidth>up_pipe.px)&&(bird.by>up_pipe.py)&&(bird.bx+bird.bwidth<up_pipe.px+up_pipe.pwidth)&&(bird.by<up_pipe.py+up_pipe.pheight))||
((bird.bx+bird.bwidth>up_pipe.px)&&(bird.by>up_pipe.py)&&(bird.bx+bird.bwidth<up_pipe.px+up_pipe.pwidth)&&(bird.by<up_pipe.py+up_pipe.pheight))

条件2下管道的检测为:

((bird.bx>down_pipe.px)&&(bird.by>down_pipe.py)&&(bird.bx<down_pipe.px+down_pipe.pwidth)&&(bird.by<down_pipe.py+down_pipe.pheight))|| ((bird.bx>down_pipe.px)&&(bird.by+bird.bheight>down_pipe.py)&&(bird.bx<down_pipe.px+down_pipe.pwidth)&&(bird.by+bird.bheight<down_pipe.py+down_pipe.pheight))

1
2
((bird.bx>down_pipe.px)&&(bird.by>down_pipe.py)&&(bird.bx<down_pipe.px+down_pipe.pwidth)&&(bird.by<down_pipe.py+down_pipe.pheight))||
((bird.bx>down_pipe.px)&&(bird.by+bird.bheight>down_pipe.py)&&(bird.bx<down_pipe.px+down_pipe.pwidth)&&(bird.by+bird.bheight<down_pipe.py+down_pipe.pheight))

条件3地面的检测最简单,为:

bird.by+bird.bheight>ground.bgy

1
bird.by+bird.bheight>ground.bgy

如果满足这三个条件,就算游戏结束,会清除循环以及提示游戏结束信息。

  1. Bird.prototype.jump = function () {  
  2.                 this.y += this.yDir;  
  3.                 this.draw();  
  4.   
  5.                 return this;  
  6.             }  

总结

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

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

this.obj这个属性就是box数组

 

  1. // 随机隐藏两个连续的箱子  
  2.             pipe.prototype.rand = function () {  
  3.                 for (var i = 0; i < this.obj.length; i++) {  
  4.                     this.obj[i].visible = true;  
  5.                 }  
  6.   
  7.                 var rand = Math.floor(Math.random() *  5) + 1;  
  8.                 // console.log(rand);  
  9.                 this.obj[rand].visible = false;  
  10.                 this.obj[rand + 1].visible = false;  
  11.   
  12.                 return this;  
  13.             };  
  1. pipe.prototype.draw = function () {  
  2.                 var box;  
  3.                 for (var i = 0; i < this.obj.length; i++) {  
  4.                     box = this.obj[i];  
  5.                     box.x = this.x;  
  6.                     if (box.visible) {  
  7.                         box.draw();  
  8.                     }  
  9.                 }  
  10.                 return this;  
  11.             };  

最后一个属性是移动方法,这是让水管进行左右移动的

html代码我就不写了,大家也都知道,如果你连html代码也需要的话,那你接下来也就没必要看了,还不如直接跳转到w3school.com.cn。

 

可能网上早就有几个flappy-bird的html5版本啦,到这个时候flappy-bird可能也没有之前那么火了,但是作为一个新手,自己思考,自己动手写一个flappy-bird的demo还是很有成就感的。

  1. Bird.prototype.draw = function () {  
  2.   
  3.                 ctx.drawImage(this.img, 0, 0, this.img.width, this.img.height, this.x, this.y, this.w, this.h);  
  4.   
  5.                 return this;  
  6.             };  

 

一开始先是定义bird对象,建议用构造函数的方法,当然你也可以用工厂函数,这没什么关系的

还是要定义它几个方法,不对,它只有一个方法

 

定义盒子对象

下面的这个方法是随机隐藏两个连续的box,以便给可伶的小鸟通过,我们是仁慈的,给了两个box的高度,当然如果你要是想虐人的话,建议你只给一个高度

和前面一样,pipe也有个draw属性

  1. var Box = function (x, y) {  
  2.                 this.x = x || boxOption.x;  
  3.                 this.y = y || boxOption.y;  
  4.                 this.w = boxOption.w;  
  5.                 this.h = boxOption.h;  
  6.                 this.img = boxOption.img;  
  7.                 this.visible = true;  
  8.   
  9.                 return this;  
  10.             };  

接下来就是重点的js了,至于css吗?你知道的css对于canvas是无效的,那我干嘛还写css呢,这不是浪费生命吗

  1. var pipe = function (posX, xDir, maxNum) {  
  2.                 this.x = posX;  
  3.                 this.xDir = xDir;  
  4.                 var boxList = [];  
  5.                 var box = new Box(0, 0);  
  6.                 var boxW = box.w,  
  7.                     boxH = box.h;  
  8.                 var boxTmp;  
  9.                 var maxNum = maxNum || Math.ceil(canvas.height / boxW);  
  10.   
  11.                 for (var i = 0; i < maxNum; i++) {  
  12.                     boxTmp = new Box(posX, i * boxH);  
  13.                     boxList.push(boxTmp);  
  14.                 }  
  15.   
  16.                 this.obj = boxList;  
  17.                 this.boxW = boxW;  
  18.                 return this;  
  19.             };  

好了,不谈代码行数的问题了,伤心

有没有觉得这个方法和bird的draw方法一样,没错是一样的,其实我应该让box继承bird对象,这样我的代码有可以少几行了

ok,就这么简单,只是简单的调用canvas的drawImage方法

  1. // 碰撞函数  
  2.   
  3.             function collision (bird, pipe1) {  
  4.                 var birdx = bird.x,  
  5.                     birdy = bird.y,  
  6.                     birdw = bird.w,  
  7.                     birdh = bird.h;  
  8.   
  9.                 var boxes = pipe1.obj;  
  10.                 var box1, box2, num;  
  11.                 for (var i = 0; i < boxes.length - 1; i++) {  
  12.                     // 找到被隐藏的两个盒子  
  13.                     if (!boxes[i].visible) {  
  14.                         box1 = boxes[i];  
  15.                         box2 = boxes[i + 1];  
  16.                         break;  
  17.                     }  
  18.                 }  
  19.                 var emptyx = box1.x;  
  20.                 var emptyy = box1.y;  
  21.                 var emptyw = box1.w;  
  22.                 var emptyh = box1.h + box2.h;  
  23.   
  24.                 // 检测是否与上半部水管碰撞  
  25.                 console.log([birdx, birdy, birdw, birdh, emptyx, 0, emptyw, box1.y, boxes[0].y]);  
  26.                 var collUp = calculate(birdx, birdy, birdw, birdh, emptyx, 0, emptyw, box1.y);  
  27.                 // 检测是否与下半部水管碰撞  
  28.                 var collDown = calculate(birdx, birdy, birdw, birdh, emptyx, box2.y + box2.h, emptyw, canvas.height - box2.y - box2.h);  
  29.                 // console.log(collUp, collDown);  
  30.                 if (collUp || collDown) {  
  31.                     // alert('game over');  
  32.                     console.log('game over 1111');  
  33.                     console.log(myReq);  
  34.                     stop();  
  35.                 }  
  36.   
  37.                 if (birdy > canvas.height - birdh) {  
  38.                     console.log('game over   222');  
  39.                     console.log(myReq);  
  40.                     stop();    
  41.                 }  
  42.             }  
  43.   
  44.             // 计算碰撞函数,默认矩形碰撞  
  45.             function calculate (x1, y1, w1, h1, x2, y2, w2, h2) {  
  46.                 var ax = x1 + w1 / 2,  
  47.                     ay = y1 + h1 / 2,  
  48.                     bx = x2 + w2 / 2,  
  49.                     by = y2 + h2 / 2;  
  50.                 var collX = false, collY = false;  
  51.   
  52.                 (Math.abs(bx - ax) < (w1 + w2) / 2) && (collX = true);  
  53.                 (Math.abs(by - ay) < (h1 + h2) / 2) && (collY = true);  
  54.   
  55.                 return collX && collY;  
  56.             }  

创建一个bird的构造函数,传入参数param 并返回this,参数param是相关的配置参数。

没错,还是这么简单,就是修改y参数,然后调用draw方法,额,其实jump方法和draw方法是可以合并的,不过为了以后的扩展或者,修改方便,我还是选择分开了。当然若是合并了,我的代码又可以少几行了。

效果如下图所示

就是将this.obj中的所有box来一次遍历输出,当然box的visible属性必须是可见的

 

 

接下来是pipe的对象,它不过是box的一个集合

上面就完成了bird对象的定义,没错,已经完成了,就一点代码而已。没有太多

  1. var Bird = function (param) {  
  2.                 this.x = param.x || 0;  
  3.                 this.y = param.y || 0;  
  4.                 this.w = param.w;  
  5.                 this.h = param.h;  
  6.                 this.yDir = param.yDir || 1;  
  7.                 this.img = param.img;  
  8.   
  9.                 return this;  
  10.             }  

flappy-bird的html5版无非是通过canvas来画的,可能网上也有webgl版本的,但是我貌似没见过,如果你发现了,希望告诉我一声,咱们一起讨论讨论。之前在微博上看到有大神用60几行就写出了一个demo,这让我写完之后发现自己的demo有将近200多行的代码,瞬间让我对大神们膜拜的五体投地,当然我的代码也可以精简到几十行,不过那样写出来,不便于维护,对于新人也很难看懂。

 

大奖888官网登录 30

接下来是水管对象的定义,不过我人太懒,实在是不想找水管的那个图片,所以我就偷工减料,用了几个盒子来代替水管,原理是一样的,但是视觉效果,你懂的,就比如我身边的一个女性同学说的,程序猿能有什么美感!大家将就着吧

  1. pipe.prototype.move = function () {  
  2.                 this.x += this.xDir;  
  3.   
  4.                 // console.log(this.x, this.xDir, this.boxW);  
  5.                 if (this.x < -this.boxW) {  
  6.                     this.x = canvas.width;  
  7.                     this.rand();  
  8.                 }  
  9.                 this.draw();  
  10.                 return this;  
  11.             };  

 

接下来就是bird的jump属性,这个属性主要是控制bird的下滑,模仿小鸟的下降

版权声明:本文由大奖888-www.88pt88.com-大奖888官网登录发布于前端开发,转载请注明出处:此前在网上查询了很多关于这个小游戏的资料,