闲扯游戏编程之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 ...
随机推荐
- mybatis中crud操作范例
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-/ ...
- java实现的类和表持久化
//映射的过程: package com.ly.orm; import java.lang.reflect.Field; import java.util.ArrayList; import java ...
- 怎么让我们自己开发的Android程序设为默认启动
怎么让我们自己开发的Android程序设为默认启动呢?其实很简单,只要在AndroidManifest.xml文件中配置一下首次启动的那个Activity即要. <activity ...
- 【Delphi7】 解决“程序第一次可以正常编译,但再次编译的时候会报错,必须重新打开Delphi”的问题
报错如下: Access violation at address 00495044 in module 'coreide70.bpl'. Read of address...Access viola ...
- Ajax&Java
AJAX即“Asynchronous Javascript And XML”(异步JavaScript和XML) 是一种基于浏览器的XMLHttpRequest对象实现的创建交互式网页应用的网页开发技 ...
- 将centos7打造成桌面系统
前言以下所有操作默认在root权限下执行,桌面环境是kde,使用gnome的也可以参考一下.我收集的以下要用到的一些安装包,360网盘http://yunpan.cn/csMhBAp92vTgN 提取 ...
- JS学习笔记--轮播图效果
希望通过自己的学习收获哪怕收获一点点,进步一点点都是值得的,加油吧!!! 本章知识点:index this for if else 下边我分享下通过老师教的方式写的轮播图,基础知识实现: 1.css代 ...
- db2命令
把远程的数据库信息加载到本地 第一步,catalog server端的node ,命令如下: db2 catalog tcpip node db2node remote hostname server ...
- SpringAOP实现(原理)
AOP原理: AOP分为:JDK动态代理和CGLIB代理 静态代理:由程序员创建或特定工具自动生成源代码,再对其编译.在程序运行前,代理类的.class文件就已经存在了. 注 ...
- 点击document隐藏某个div
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...