http://hiloteam.github.io/tutorial/flappybird.html#_9
Flappy Bird是一款前不久风靡世界的休闲小游戏。虽然它难度超高,但是游戏本身却非常简单。下面我们就使用Hilo来快速开发HTML5版的Flappy Bird。
源文件结构
大家可以先下载Flappy Bird的项目源文件作为参考,以下是整个项目的文件结构:
flappybird/
├── index.html //游戏主页面 ├── js/ │ ├── game.js //游戏主模块 │ ├── Asset.js //素材加载类 │ ├── ReadyScene.js //准备场景 │ ├── OverScene.js //结束场景 │ ├── Bird.js //小鸟类 │ ├── Holdbacks.js //障碍类 │ ├── hilo-standalone.js //hilo独立版 └── images/
预加载图片
为了让玩家有更流畅的游戏体验,图片素材一般需要预先加载。Hilo提供了一个队列下载工具LoadQueue,使用它可以预加载图片素材。如下所示,在Asset类中,我们定义了load方法:
load: function(){ var resources = [ {id:'bg', src:'images/bg.png'}, {id:'ground', src:'images/ground.png'}, {id:'ready', src:'images/ready.png'}, {id:'over', src:'images/over.png'}, {id:'number', src:'images/number.png'}, {id:'bird', src:'images/bird.png'}, {id:'holdback', src:'images/holdback.png'} ]; this.queue = new Hilo.LoadQueue(); this.queue.add(resources); this.queue.on('complete', this.onComplete.bind(this)); this.queue.start(); }
从上面代码中可以看到,resources是我们要下载的图片素材列表,使用queue.add()方法把素材列表加入到下载队列中,再使用queue.start()方法来启动下载队列。
提示:下载队列LoadQueue当前仅支持图片等资源的下载,其他资源文件可通过自定义扩展方式实现。具体可参考API文档。
为了获得下载情况,LoadQueue提供了三个事件:
load
- 当单个资源下载完成时发生complete
- 当所有资源下载完成时发生error
- 当某一资源下载出错时发生
在这里我们仅监听了complete事件。
onComplete: function(e){ this.bg = this.queue.get('bg').content; this.ground = this.queue.get('ground').content; this.birdAtlas = new Hilo.TextureAtlas({ image: this.queue.get('bird').content, frames: [ [0, 120, 86, 60], [0, 60, 86, 60], [0, 0, 86, 60] ], sprites: { bird: [0, 1, 2] } }); //删除下载队列的complete事件监听 this.queue.off('complete'); //发送complete事件 this.fire('complete'); }
下载完成后会触发onComplete回调方法。我们可以通过queue.get(id).content来获取指定id的图片素材下载完成后的Image对象。 在这里我们创建了游戏中需要用到的素材以及精灵纹理集等。
其中纹理集TextureAtlas实例由三部分组成:
image
- 纹理集图片。frames
- 纹理集图片帧序列。每个图片帧由图片在纹理集中的坐标x/y和宽高width/height组成,即[x, y, width, height]。sprites
- 精灵定义。sprites可包含多个精灵定义。每个精灵由多个frames中的图片帧组成,其中数值代表图片帧在frames中的索引位置。比如bird,则由索引为0、1、2的图片帧组成。
在game.js中使用Asset类来下载完图片素材后,再调用initStage方法初始化舞台:
this.asset = new game.Asset(); this.asset.on('complete', function(e){ this.asset.off('complete'); this.initStage(); }.bind(this)); this.asset.load();
初始化舞台
由于我们的图片素材是高清图片,且背景大小为720x1280。因此,我们设定游戏舞台的大小为720x1280,并设置其x和y轴的缩放比例均为0.5,这样舞台实际的可见大小变为360x640。最后,我们把canvas画布添加到body中。
initStage: function(){ this.width = 720; this.height = 1280; this.scale = 0.5; var stage = this.stage = new Hilo.Stage({ width: this.width, height: this.height, scaleX: scale, scaleY: scale }); document.body.appendChild(stage.canvas); }
注意:舞台是一个各种图形、精灵动画等的总载体。所有用Hilo创建的可见的对象都必须添加到舞台或其子容器后,才会被渲染和显示出来。
舞台创建好后,我们需要一个定时器来不断更新和渲染舞台。这里可以使用Ticker类:
//设定舞台刷新频率为60fps
this.ticker = new Hilo.Ticker(60); //把舞台加入到tick队列 this.ticker.addTick(this.stage); //启动ticker this.ticker.start();
场景分析
如果你玩过原版的Flappy Bird,就知道此游戏的场景非常简单,大致可以划分以下几个部分:
背景 - 背景图和移动的地面是贯穿整个游戏,没有变化的。 准备场景 - 一个简单的游戏提示画面。游戏开始前和失败后重新开始都会进入此场景。 游戏场景 - 障碍物不断的从右往左移动,玩家控制小鸟的飞行。 结束场景 - 游戏失败后,显示得分以及相关按钮等。 接下来,我们就开始用Hilo来创建这4个场景。
游戏背景
由于背景是不变的,为了减少canvas的重复绘制,我们采用DOM+CSS来设置背景。先创建一个div,设置其CSS背景为游戏背景图片,再把它加入到舞台的canvas后面。
initBackground: function(){ var bgWidth = this.width * this.scale; var bgHeight = this.height * this.scale; document.body.insertBefore(Hilo.createElement('div', { id: 'bg', style: { position: 'absolute', background: 'url(images/bg.png) no-repeat', backgroundSize: bgWidth + 'px, ' + bgHeight + 'px', width: bgWidth + 'px', height: bgHeight + 'px' } }), this.stage.canvas); }
地面也是背景的一部分,处于画面最下端。地面我们使用可视对象Bitmap类。一般的,不需要使用精灵动画的普通图片对象都可使用此类。Bitmap类只要传入相应的图片image参数即可。此外,为了方便查找对象,一般我们都为可视对象取一个合适的id。
initBackground: function(){ /* all previous code here */ this.ground = new Hilo.Bitmap({ id: 'ground', image: this.asset.ground }); //放置地面在舞台的最底部 this.ground.y = this.height - this.ground.height; //循环移动地面 Hilo.Tween.to(this.ground, {x:-60}, {duration:300, loop:true}); }
地面不是静止的。它是从右到左的不断循环的移动着的。我们注意到地面的图片素材比背景要宽一些,而地面图片本身也是一些循环的斜四边形组成的。这样的特性,意味着如果我们把图片从当前位置移动到下一个斜四边形的某一位置时,舞台上的地面会看起来是没有移动过一样。我们找到当地面的x轴坐标为-60时,跟x轴为0时的地面的图形是没有差异的。这样,若地面在0到-60之间不断循环变化时,地面就循环移动起来了。
Hilo提供了缓动动画Tween类。它的使用也非常简单:
Hilo.Tween.to(obj, newProperties, params);
obj
- 要缓动的对象。newProperties
- 指定缓动后对象的属性改变后的新值。这里指定x为-60,也就是地面运动到x为-60的位置。params
- 指定缓动参数。这里指定了缓动时间time为300毫秒,loop表示缓动是不断循环的。
Tween类不会自动运行,也需要加入到tick队列才能运行:
this.ticker.addTick(Hilo.Tween);
准备场景
准备场景很简单,由一个Get Ready!的文字图片和TAP提示的图片组成。这里,我们创建一个ReadyScene的类,继承自容器类Container。它的实现只需要把getready和tap这2个Bitmap对象创建好并放置在适当的位置即可。而它们的图片素材image会传给ReadyScene的构造函数。
var ReadyScene = Hilo.Class.create({ Extends: Hilo.Container, constructor: function(properties){ ReadyScene.superclass.constructor.call(this, properties); this.init(properties); }, init: function(properties){ //准备Get Ready! var getready = new Hilo.Bitmap({ image: properties.image, rect: [0, 0, 508, 158] }); //开始提示tap var tap = new Hilo.Bitmap({ image: properties.image, rect: [0, 158, 286, 246] }); //确定getready和tap的位置 tap.x = this.width - tap.width >> 1; tap.y = this.height - tap.height + 40 >> 1; getready.x = this.width - getready.width >> 1; getready.y = tap.y - getready.height >> 0; this.addChild(tap, getready); } });
在游戏主文件game.js中,实例化ReadyScene并添加到舞台stage上。
this.readyScene = new game.ReadyScene({ width: this.width, height: this.height, image: this.asset.ready }).addTo(this.stage);
结束场景
结束场景跟准备场景相似。
var OverScene = ns.OverScene = Hilo.Class.create({ Extends: Hilo.Container, constructor: function(properties){ OverScene.superclass.constructor.call(this, properties); this.init(properties); }, init: function(properties){ //Game Over图片文字 var gameover = this.gameover = new Hilo.Bitmap({ id: 'gameover', image: properties.image, rect: [0, 298, 508, 158] }); //结束面板 var board = this.board = new Hilo.Bitmap({ id: 'board', image: properties.image, rect: [0, 0, 590, 298] }); //开始按钮 var startBtn = this.startBtn = new Hilo.Bitmap({ id: 'start', image: properties.image, rect: [590, 0, 290, 176] }); //等级按钮 var gradeBtn = this.gradeBtn = new Hilo.Bitmap({ id: 'grade', image: properties.image, rect: [590, 176, 290, 176] }); //玩家当前分数