上一章我们介绍了开发中会用到的辅助工具,并创建了 GameScene 场景,接下来这章我们将继续 GameScene 的传(bai)奇(bi)。不过在开始编写 GameScene 场景的代码之前,我们还是先来明确一下游戏的功能和实现方法。这样可以帮我们更好的理解并设计逻辑。下面是总结出的结论:
- 在 GameScene 场景中,我们将创建一个飞行的娃娃角色,这个角色是游戏的唯一主角。游戏初始状态下,这个角色有满满的生命值,但随着时间的推移,生命值会不断的减少。这里生命值我们可以以进度条的形式来展示它的多少和增减。
- 游戏 GameScene 场景有不止一层的滚动背景,每层的背景滚动速率不一,这样可以产生立体的滚动效果。娃娃位置不动,让背景不停的滚动,可以产生娃娃在向前飞行的视觉感受。
- 在滚动过程中屏幕上会时不时的出现一些障碍物和可以增加生命值的奖励品,当娃娃碰到障碍物时,生命值减少;碰到奖励品时,生命值增加。
- 当触碰屏幕时,娃娃将向上升至一定的高度;当不触碰屏幕时,它将向下掉落。掉落到地板时会扣除相应的生命值。所以玩家必须在按与不按之间保持一定的平衡,一方面躲避障碍物并避免触碰地板,另一方面争取多吃一点奖励品,这样才能让娃娃顺利地到达终点。
- 本场景我们将用物理引擎来模拟整个飞行世界,这样游戏主角的飞行问题(漂浮状态),以及它与障碍物/奖励品之间的碰撞检测就很容易实现了。
GameScene 的背景
为了产生立体的滚动视觉效果,我们决定用四层不同的背景来实现这一效果(怕累死的,可以偷懒)。当然,这里层的概念不同于引擎中的层(cc.Layer),它只是一个普通的副词而已。
- 首先,最底层是一张固定不动的布幕背景;
- 其次,我将在布幕背景的上层添加一层远景背景,该背景将会以较慢的速度向左滚动;
- 然后,在远景背景的上层我们又会添加一层近景背景,该背景将会以较快的速度向左滚动;
- 最后,在最上层,我们将放置一层以更快速度向左滚动的背景,游戏的障碍物和奖励品都会在该层背景上。所以这层背景我们将用 TliedMap 编辑器来制作,也就是用 TMX 文件来创建。
乱入一个知识点:游戏中元素的层级关系由 z-order 属性来决定,我们可以通过设置元素的z-order值来控制元素之间的渲染顺序。默认情况下,所有元素的 z-order 为0,所以当游戏元素没有指定z-order值时,游戏中的元素将按添加顺序来显示。故此,我们在添加游戏背景或其他元素时,应该要注意下它们的添加顺序或 z-order 值,不要出现遮挡的现象。
创建BackgroundLayer背景层
为了让代码结构更加清晰,接下来我们将为 GameScene 场景创建一个背景层,然后把以上的四层背景图都添加到该层上。
所以,我们在 src/app 目录下新建了一个layers 文件夹,然后再新建了一个 BackgroundLayer.lua 文件,并把它保存到 src/app/layers 目录下。
以下是创建空白 BackgroundLayer 层的代码:
1
2
3
4
5
6
7
8
|
BackgroundLayer =
class
(
"BackgroundLayer"
,function()
return
display.newLayer()
end)
function BackgroundLayer:ctor()
end
return
BackgroundLayer
|
display.newLayer()方法能创建并返回一个 cc.Layer 层对象。
注意:因为我们将在其他(GameScene.lua)文件中调用 BackgroundLayer 类,所以在创建 BackgroundLayer 类时,我们并没有像创建 GameScene 一样把它定义为 local 局部型的类。
把背景层添加到GameScene场景
BackgroundLayer.lua 文件是个单独的模块文件,如果我们想要引用它,那我们需要把它加载到项目中来。
通常,载入文件到 Quick 项目可以使用以下的两种方式:
- 通过require()方法,该方法会搜索指定的目录,并加载文件。
- 通过import()方法,该方法用于处理require同目录下其他模块,在模块名前加.。
它们的详细用法参见API。
这里,我们载入 BackgroundLayer 模块用 require 方法。在 MyApp.lua 中加入如下的函数:
1
|
require(
"app.layers.BackgroundLayer"
)
|
这样就把我们定义的 BackgroundLayer 类引入到了quick项目中,之后,我们就可以在任何地方引用这个 BackgroundLayer 模块了。
接下来我们来把 BackgroundLayer 层加入到 GameScene 场景中。
1
2
3
4
5
|
function GameScene:ctor()
self.backgroundLayer = BackgroundLayer.
new
()
:addTo(self)
end
|
这里调用BackgroundLayer.new()
方法实例化了一个 BackgroundLayer 对象,并把它加入到场景。这样 GameScene 场景中就有一层空白的 BackgroundLayer 层了。
框架整好以后,下面我们来向层容器里面塞东西。
添加背景
布幕背景
回到 BackgroundLayer 文件,下面我们添加如下的一段函数来为 BackgroundLayer 层添加最底层的布幕背景,这里添加固定不动的布幕背景就如添加普通精灵。
1
2
3
4
5
6
|
function BackgroundLayer:createBackgrounds()
-- 创建布幕背景
local bg = display.newSprite(
"image/bj1.jpg"
)
:pos(display.cx, display.cy)
:addTo(self, -4)
end
|
addTo方法中可以指定游戏元素的 z-order 值,本教程中,我们把布幕背景的z-order设置为-4,确保它位于场景的最下层(当然,这要确保该层中不能有比布幕背景的 z-order 值还小的元素)。
循环滚动的远/近景背景
远景背景和近景背景有着共同的特征,它们都会以一定的速度向左循环移动。所以这里我们以远景背景为例,说明下它们的实现过程。
首先,我们添加两张首尾可以拼接的图片来表示滚动的远景背景图。
在BackgroundLayer:createBackgrounds()
方法中加入如下的代码来添加远景背景图:
1
2
3
4
5
6
7
8
9
10
|
-- 创建远景背景
local bg1 = display.newSprite(
"image/b2.png"
)
:align(display.BOTTOM_LEFT, display.left , display.bottom + 10)
:addTo(self, -3)
local bg2 = display.newSprite(
"image/b2.png"
)
:align(display.BOTTOM_LEFT, display.left + bg1:getContentSize().width, display.bottom + 10)
:addTo(self, -3)
table.insert(self.distanceBg, bg1) -- 把创建的bg1插入到了 self.distanceBg 中
table.insert(self.distanceBg, bg2) -- 把创建的bg2插入到了 self.distanceBg 中
|
其中 self.distanceBg 是一个 table 类型的值,它的定义我们放在 ctor 函数中。
1
|
self.distanceBg = {}
|
实现滚动背景,需要做的就是不断的改变背景图片贴图横坐标,并且不断的刷新位置。所以我们定义了一个滚动背景的函数scrollBackgrounds()。
1
2
3
4
5
6
7
8
9
10
11
12
|
function BackgroundLayer:scrollBackgrounds(dt)
if
self.distanceBg[2]:getPositionX() <= 0 then
self.distanceBg[1]:setPositionX(0)
end
local x1 = self.distanceBg[1]:getPositionX() - 50*dt -- 50*dt 相当于速度
local x2 = x1 + self.distanceBg[1]:getContentSize().width
self.distanceBg[1]:setPositionX(x1)
self.distanceBg[2]:setPositionX(x2)
end
|
以上的这段函数的作用就是让 self.distanceBg[1]
和 self.distanceBg[2]
的 X 坐标都向左移动 50 * dt (dt是时间间隔,两帧之间的时间间隔)个单位,self.distanceBg[2]
紧接在 self.distanceBg[1]
后面。
在此之后,需要添加不断执行 scrollBackgrounds() 函数的方法,以确保远景背景不断的向左移动。使用过 Cocos2d-x 的童鞋应该知道,Cocos2d-x 中可以通过重载 update 函数在每帧刷新的时候执行自己需要的一些操作。在 Quick 框架中,我们把这种事件叫做帧事件,意思是每帧刷新时都会执行的事件。
帧事件在游戏中经常用来更新游戏中的数据。下面我们将在 ctor() 函数中加入这种帧事件,用以更新背景图的坐标。代码如下:
1
2
3
4
5
6
7
8
9
10
11
|
function BackgroundLayer:ctor()
self.distanceBg = {}
self.nearbyBg = {}
self.tiledMapBg = {}
self:createBackgrounds()
self:addNodeEventListener(cc.NODE_ENTER_FRAME_EVENT, handler(self, self.scrollBackgrounds))
self:scheduleUpdate()
end
|
其中,addNodeEventListener 方法用于注册帧事件,scheduleUpdate 方法则启用了帧事件,只有调用了 scheduleUpdate 后,帧事件才会被触发。
此时我们再用同样的方法添加近景背景(让它每次移动的距离大一些),运行游戏时,屏幕上就会出现滚循环移动的背景了。
注:以上截图不是循环的。
最上层的TMX背景
背景层中还有一个重要的滚动项,那就是容纳了障碍物和奖励品的 TMX 类型的背景。
前面章节中我们已经提到过,TiledMap 编辑器能把单个的图块拼接成一幅完整的地图,而它的最终产物就是 TMX 文件。这里我们也不要把它想的有多复杂,其实说白了,渲染出来就是一张破图而已。和 png,jpg的图片精灵外形无明显差异。
所以它滚动的原理和远/近景背景滚动的原理差不多,只不过,它不循环。我们可以用一个以上的 TMX 文件来实现滚动,当最后一个 TMX 文件刚好显示完的时候游戏就结束。这里考虑到后续的碰撞检测,所以我们只用一个 TMX 文件实现滚动。
载入 TMX 文件的代码如下,添加的位置依旧在 createBackgrounds 方法中。
1
2
3
|
self.map = cc.TMXTiledMap:create(
"image/map.tmx"
)
:align(display.BOTTOM_LEFT, display.left, display.bottom)
:addTo(self, -1)
|
让地图文件滚动的代码如下:
1
2
3
4
5
6
|
if
self.map:getPositionX() <= display.width - self.map:getContentSize().width then
self:unscheduleUpdate() -- 禁用帧事件,停止整个背景层滚动
end
local x5 = self.map:getPositionX() - 130*dt
self.map:setPositionX(x5)
|
好了,本周的教程就算完成了,这里我们的 tmx 文件是在下暂时随便创建的一个,下一章我们会详细地讲解如何制作 tmx 文件。