闲扯游戏编程之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 ...
随机推荐
- Linux内核原子(1) - spinlock的实现
spinlock的数据结构spinlock_t定义在头文件linux/spinlock_types.h里面: typedef struct { raw_spinlock_t raw_lock; #if ...
- webservice 实现json模式
直接上代码 public string GetUserInfoByOpenid(string openid) { var weixinuser = new WeiXinUser(); weixinus ...
- wampServer安装注意
http://www.glbwl.com/wampServer-403-forbidden.html http://jingyan.baidu.com/article/e75aca8578147d14 ...
- shell 转义字符的写法
在链接中,往往会遇到含有‘&'字符的情形,需要转义方能使用. 以下是它的写法样例,错误的写法勿要再犯!! 链接样例: http://my.example.cn/show/details/htd ...
- CentOS 6中MATLAB print函数“所见非所得”bug的解决方案
0 系统配置+软件版本 主机:Dell optiplex 390 MT (i5) 系统+软件:CentOS 6.5 x64, Matlab R2012, R2013 系统+软件:CentOS 6.7 ...
- running programmer——spring-01(初谈spring)
今天主要是通过一个简单的登录程序学习一些spring做基础的配置和功能. I.spring的核心配置applicationContext.xml 关于bean的配置官方给出的最基础的配置文件如下: & ...
- jQuery设置聚焦并使光标位置在文字最后
var editor = document.getElementById('btn'); editor.onfocus = function () { window.setTimeout(functi ...
- Flapper Bird的学习笔记(二)
因为有一个超屌的梦想,所以就绝不会做一个孬种的追梦人! 本章节主要目的: 实现小鸟的飞行. 实现管道的随机出现. 实现相机跟随. 上次主要是场景的布置,这节全力拿下代码. 1.小鸟飞行(一种制作2D动 ...
- [导读]Learning from Imbalanced Classes
原文:Learning from Imbalanced Classes 数据不平衡是一个非常经典的问题,数据挖掘.计算广告.NLP等工作经常遇到.该文总结了可能有效的方法,值得参考: Do nothi ...
- open nms安装教程
而在正式的任务,我被要求在Windows平台上部署开源网络管理系统.虽然工作的任务,我得到了一些问题,对此我无法在网上寻找解决的问题,我用的命中和试验方法得到了解决.然后我想就这些问题及其解决办法写. ...