闲扯游戏编程之html5篇--山寨版《flappy bird》源码
新年新气象,最近事情不多,继续闲暇学习记点随笔,欢迎拍砖。之前的〈简单游戏学编程语言python篇〉写的比较幼稚和粗糙,且告一段落。开启新的一篇关于javascript+html5的从零开始的学习。仍然以咱们有兴趣写的小游戏开始,〈flappy bird〉最近真是火的离谱,我也是昨天才开始找这个游戏试玩一下,果然难度不小,只能玩到33分了 ,哈哈。这游戏的评论网上已经铺天盖地了,这里不做过多评论,毕竟个人属于这个移动游戏圈子之外的。不过还是忍不住说一下,这游戏创意已经不算新颖,像素级的入门游戏精美度更是差上很多,开发难度也就是入门级的水平(相对来说)。不过作为菜鸟的门外汉来说,这游戏的设计思路和开发细节还是比较值得新手去研究和作为练手的案例研究一下。于是找到网上牛人放出的山寨版《flappy bird》之clumsy-bird,来简单研究一下源码吧,顺便从零学习一下canvas和Js一些东西,作为记录。
clumsy-bird的github地址为:https://github.com/ellisonleao/clumsy-bird
在线试玩地址:http://ellisonleao.github.io/clumsy-bird/(保证你的浏览器支持html5哟)
一、源码框架介绍
这个游戏呢,采用开源的html5游戏引擎melonJS作为框架,这个引擎比较轻量级,比较简单易懂。了解完源码整体框架就明白了整个引擎的框架了。
首先看一下游戏入口这里(game.js):大部分是框架相关的,非框架代码则是data的补充定义,用户按键事件绑定这些。
var game = {
data : {
score : 0,
timer: 0,
start: false
},
"onload" : function () {
if (!me.video.init("screen", 900, 600, true, 'auto')) {
alert("Your browser does not support HTML5 canvas.");
return;
}
me.audio.init("mp3,ogg");
me.loader.onload = this.loaded.bind(this);
me.loader.preload(game.resources);
me.state.change(me.state.LOADING);
},
"loaded" : function () {
me.state.set(me.state.MENU, new game.TitleScreen());
me.state.set(me.state.PLAY, new game.PlayScreen());
me.state.set(me.state.GAME_OVER, new game.GameOverScreen());
me.state.transition("fade", "#000", 100);
me.input.bindKey(me.input.KEY.SPACE, "fly", true);
me.input.bindTouch(me.input.KEY.SPACE);
me.state.change(me.state.MENU);
}
};
onload 预加载的game.resources主要是图片如下的一些素材。

从界面加载完"loaded"函数看起,有三个状态 Menu 对应game.TitleScreen()是我们的标题界面处理,PLAY是我们的game.PlayScreen(),这个就是游戏开始的相关部分,即我们主要研究的部分--screens/play.js
这里面主要继承重写了ScreenObject的init初始化函数onResetEvent状态改变函数 及刷新界面函数update。
init定义了主要的变量管道长度this.pipeHoleSize = 1240;左右相邻管道出现的间隔时间this.pipeFrequency = 92;
update函数中处理逻辑即每隔pipeFrequency生成上下两个管道和碰撞体(这个实际并不渲染,后面代码中实体的alpha渲染做透明出现,只作为碰撞检测用),两个管道的位置简单画一下应该可求出(pipe1是下管道,保证中间距离是100,且最短管道要保证有100)
if (this.generate++ % this.pipeFrequency == 0){
var posY = this.getRandomInt(
me.video.getHeight() - 100,
200
);
var posY2 = posY - me.video.getHeight() - this.pipeHoleSize;
var pipe1 = new me.entityPool.newInstanceOf("pipe", this.posX, posY);
var pipe2 = new me.entityPool.newInstanceOf("pipe", this.posX, posY2);
var hitPos = posY - 100;
var hit = new me.entityPool.newInstanceOf("hit", this.posX, hitPos);
pipe1.renderable.flipY();
me.game.add(pipe1, 10);
me.game.add(pipe2, 10);
me.game.add(hit, 11);
}
接下来是游戏界面状态处理函数onResetEvent
me.input.bindKey(me.input.KEY.SPACE, "fly", true);
//this.start = false;
game.data.score = 0;
game.data.timer = 0;
game.data.start = false; me.game.add(new BackgroundLayer('bg', 1)); var groundImage = me.loader.getImage('ground'); this.ground = new me.SpriteObject(
0,
me.video.getHeight() - groundImage.height,
groundImage
);
me.game.add(this.ground, 11); this.HUD = new game.HUD.Container();
me.game.world.addChild(this.HUD); me.entityPool.add("clumsy", BirdEntity);
me.entityPool.add("pipe", PipeEntity, true);
me.entityPool.add("hit", HitEntity, true); this.bird = me.entityPool.newInstanceOf("clumsy", 60,
me.game.viewport.height/2 - 100);
me.game.add(this.bird, 10);
this.posX = me.game.viewport.width; //inputs
me.input.bindMouse(me.input.mouse.LEFT, me.input.KEY.SPACE);
me.state.transition("fade", "#fff", 100); this.getReady = new me.SpriteObject(
me.video.getWidth()/2 - 200,
me.video.getHeight()/2 - 100,
me.loader.getImage('getready')
);
me.game.add(this.getReady, 11);
var popOut = new me.Tween(this.getReady.pos).to({y: -132}, 2000)
.easing(me.Tween.Easing.Linear.None)
.onComplete(function(){ game.data.start = true;}).start();
},
这里面主要完成界面背景层的加载,HUd作为分数显示,及重要游戏对象生成。
me.entityPool.add("clumsy", BirdEntity); 小鸟实体类
me.entityPool.add("pipe", PipeEntity, true); 管道实体类
me.entityPool.add("hit", HitEntity, true); 碰撞体类
this.bird = me.entityPool.newInstanceOf("clumsy", 60,
me.game.viewport.height/2 - 100);
me.game.add(this.bird, 10); 首先只有小鸟新实例对象生成,游戏正式开始才有管道等。EntityPool 就是作为引擎管理游戏中实例化对象而存在的。
二、游戏对象类的实现
这块者重介绍主重要的上面提到的那三个游戏对象类。(entities.js)
实现还是比较简单的,重写ObjectEntity重要的几个函数就行了。就是 init 和 update这两个函数,分别完成对象初始化和每帧的刷新。主要学习的是update里面逻辑的处理。这里主要介绍小鸟的处理,那两个基本上没多少代码处理。
var BirdEntity = me.ObjectEntity.extend({
init: function(x, y){
var settings = {};
settings.image = me.loader.getImage('clumsy');
settings.spritewidth = 85;
settings.spriteheight= 60;
this.parent(x, y, settings);
this.alwaysUpdate = true;
this.gravity = 0.2;
this.gravityForce = 0.01;
this.maxAngleRotation = Number.prototype.degToRad(30);
this.maxAngleRotationDown = Number.prototype.degToRad(90);
this.renderable.addAnimation("flying", [0, 1, 2]);
this.renderable.addAnimation("idle", [0]);
this.renderable.setCurrentAnimation("flying");
this.animationController = 0;
this.updateColRect(10, 70, 2, 58);
},
update: function(x, y){
// mechanics
if (game.data.start) {
if (me.input.isKeyPressed('fly')){
this.gravityForce = 0.01;
var currentPos = this.pos.y;
tween = new me.Tween(this.pos).to({y: currentPos - 72}, 100);
tween.easing(me.Tween.Easing.Exponential.InOut);
tween.start();
this.renderable.angle = -this.maxAngleRotation;
}else{
this.renderable.setCurrentAnimation("flying");
this.gravityForce += 0.2;
this.pos.add(new me.Vector2d(0, me.timer.tick * this.gravityForce));
this.renderable.angle += Number.prototype.degToRad(3) * me.timer.tick;
if (this.renderable.angle > this.maxAngleRotationDown)
this.renderable.angle = this.maxAngleRotationDown;
}
}
//manual animation
var actual = this.renderable.getCurrentAnimationFrame();
if (this.animationController++ % 2){
actual++;
this.renderable.setAnimationFrame(actual);
}
res = this.collide();
var hitGround = me.game.viewport.height - (96 + 60);
var hitSky = -80; // bird height + 20px
if (res) {
if (res.obj.type != 'hit'){
me.state.change(me.state.GAME_OVER);
return false;
}
me.game.remove(res.obj);
game.data.timer++;
return true;
}else if (this.pos.y >= hitGround || this.pos.y <= hitSky){
me.state.change(me.state.GAME_OVER);
return false;
}
var updated = (this.vel.x != 0 || this.vel.y != 0);
if (updated){
this.parent();
return true;
}
return false;
},
});
在小鸟初始化函数完成了动画帧的加载
三个序列动画[0,1,2]作为一次动画过程,
this.renderable.addAnimation("flying", [0, 1, 2]);this.renderable.addAnimation("idle", [0]);this.renderable.setCurrentAnimation("flying");
this.gravity = 0.2;this.gravityForce = 0.01;完成重力设定。
update函数中小鸟动画实现为
//manual animation
var actual = this.renderable.getCurrentAnimationFrame();
if (this.animationController++ % 2){
actual++;
this.renderable.setAnimationFrame(actual);
} 如上变实现小鸟的动画。
update函数主要功能除了上面,还主要负责完成用户按键处理和碰撞检测等处理逻辑:
当按下space键或者点击鼠标执行如下动作 (好像是利用tween增强了物理效果,具体没深究这里。)
this.gravityForce = 0.01;var currentPos = this.pos.y;
tween = new me.Tween(this.pos).to({y: currentPos - 72}, 100);
tween.easing(me.Tween.Easing.Exponential.InOut);
tween.start();
this.renderable.angle = -this.maxAngleRotation;
当没有按下相应键处理为
this.renderable.setCurrentAnimation("flying");
this.gravityForce += 0.2;
this.pos.add(new me.Vector2d(0, me.timer.tick * this.gravityForce));
this.renderable.angle += Number.prototype.degToRad(3) * me.timer.tick; 每帧增加0.2重力加速度值,并且y值每秒增加g*t位移,角度加大,就是实现小鸟下坠效果。
res = this.collide();
var hitGround = me.game.viewport.height - (96 + 60);
var hitSky = -80; // bird height + 20px
if (res) {
if (res.obj.type != 'hit'){
me.state.change(me.state.GAME_OVER);
return false;
}
me.game.remove(res.obj);
game.data.timer++;
return true;
}else if (this.pos.y >= hitGround || this.pos.y <= hitSky){
me.state.change(me.state.GAME_OVER);
return false;
}
判断当前发生碰撞的对象是不是‘hit’,当不是之前的"hit"类型时说明发生碰撞对象改变,即小鸟撞管子上了 ,报游戏结束,并清理对象。
上面就是主要的代码逻辑了,感兴趣的话可以从git上fork一份自己研究一下,这里不做过多探讨了,闲扯之余顺便学习一下Js和html5也是收获。
以上素材及代码均来自网络,仅供学习研究,转载请注明出处,谢谢支持。
闲扯游戏编程之html5篇--山寨版《flappy bird》源码的更多相关文章
- Flappy Bird 源码走读
参考:https://github.com/kirualex/SprityBird 该项目基于spritekit,代码的结构很清楚,感觉用来学习spritekit非常不错. 1.项目只有一个viewC ...
- 【教程】HTML5+JavaScript编写flappy bird
作者: 风小锐 新浪微博ID:永远de风小锐 QQ:547953539 转载请注明出处 PS:新修复了两个bug,已下载代码的同学请查看一下 大学立即要毕业了. ...
- 8个前沿的 HTML5 & CSS3 效果【附源码下载】
作为一个前沿的 Web 开发者,对于 HTML5 和 CSS3 技术或多或少都有掌握.前几年这些新技术刚萌芽的时候,开发者们已经使用它们来小试牛刀了,如今这些先进技术已经遍地开发,特别是在移动端大显身 ...
- 网狐6603 cocos2dx 棋牌、捕鱼、休闲类游戏《李逵捕鱼》手机端完整源码分析及分享
该资源说明: cocos2d 棋牌.捕鱼.休闲类游戏<李逵捕鱼>手机端完整源码,网狐6603配套手机版源码,可以选桌子,适合新手学习参考,小编已亲测试,绝对完整可编译手机端,下载后将文件考 ...
- 8个超震撼的HTML5和纯CSS3动画源码
HTML5和CSS3之所以强大,不仅因为现在大量的浏览器的支持,更是因为它们已经越来越能满足现代开发的需要.Flash在几年之后肯定会消亡,那么HTML5和CSS3将会替代Flash.今天我们要给大家 ...
- 第五篇:白话tornado源码之褪去模板的外衣
上一篇<白话tornado源码之请求来了>介绍了客户端请求在tornado框架中的生命周期,其本质就是利用epoll和socket来获取并处理请求.在上一篇的内容中,我们只是给客户端返回了 ...
- 第三篇:白话tornado源码之请求来了
上一篇<白话tornado源码之待请求阶段>中介绍了tornado框架在客户端请求之前所做的准备(下图1.2部分),本质上就是创建了一个socket服务端,并进行了IP和端口的绑定,但是未 ...
- 让你心动的 HTML5 & CSS3 效果【附源码下载】
这里集合的这组 HTML5 & CSS3 效果,有的是网站开发中常用的.实用的功能,有的是先进的 Web 技术的应用演示.不管哪一种,这些案例中的技术都值得我们去探究和学习. 超炫的 HTML ...
- 动态方式破解apk进阶篇(IDA调试so源码)
动态方式破解apk进阶篇(IDA调试so源码) 来源 https://blog.csdn.net/qq_21051503/article/details/74907449 下面就说关于在IDA中And ...
随机推荐
- java list 简述
list中可以添加任何对象,我可以给你举个例子:class Person{ .....}上面定义了一个Person类,下面看好如何使用ListPerson p1=new Person();Person ...
- SQLPlus 在连接时通常有四种方式
1. sqlplus / as sysdba 操作系统认证,不需要数据库服务器启动listener,也不需要数据库服务器处于可用状态.比如我们想要启动数据库就可以用这种方式进入 sqlpl ...
- C# 自动生成代码API文档
暂时没学会正规的转载style临时记录一下 NET中的规范标准注释(一) -- XML注释标签讲解 http://www.cnblogs.com/mq0036/p/3544264.html NET中的 ...
- sed 使用
Sed简介 sed 是一种在线编辑器,它一次处理一行内容.处理时,把当前处理的行存储在临时缓冲区中,称为"模式空间"(pattern space),接着用sed命令处理缓冲区中的内 ...
- 通过select的value值来改变textarea内字体和大小
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- css3各个属性的兼容
1.transition:IE10. Firefox.Opera.Chrome支持: Safari支持替代的-webkit-transition属性: 2.animation: IE10.FIrefo ...
- 处理GitHub不允许上传大于100M文件问题
第一步输入命令 cd /Users/Dora/Desktop/XXX(cd后面的这个路径要换成你自己项目的路径) 第二步输入命令 git rm --cached/Users/Dora/Desktop/ ...
- JAVA动手动脑多态
动手实验一:下列语句哪一个将引起编译错误?为什么?哪一个会引起运行时错误?为什么? m=d; d=m; d=(Dog)m; d=c; c=(Cat)m; 先进行自我判断,得出结论后,运行TestCas ...
- 转: Hibernate HQL查询 插入 更新(update)实例
1.实体查询:有关实体查询技术,其实我们在先前已经有多次涉及,比如下面的例子:String hql=”from User user ”;List list=session.CreateQuery(hq ...
- 【Python扩展阅读【转】】字符串的方法及注释
capitalize() 把字符串的第一个字符改为大写 casefold() 把整个字符串的所有字符改为小写 center(width) 将字符串居中,并使用空格填充至长度wi ...