【正文】
前面我们基本上已经将bbframework开发的必备工作都已经做好了,今天我们就来往我们那乌漆麻黑的场景里面添加点东西,让游戏慢慢的像个游戏。首先我们来看下一个空的Layer(层)所应该有的东西:
--[[!-- 场景层类,定义层相关操作方法及逻辑实现。 - 定义场景层功能方法。 ]] ---------------------- -- 类 ---------------------- local M = classLayerTouch("Main") ---------------------- -- 公共参数 ---------------------- -- [常量] -- .. -- [操作变量] -- .. ---------------------- -- 构造方法 ---------------------- --[[-- 构造方法,定义视图实例初始化逻辑 ### Parameters: - table **params** 参数集合 ### Return: - object 对象实例 ]] function M:ctor(params) -- [超类调用] M.super.ctor(self, params) end ---------------------- -- 结点渲染 ---------------------- --[[-- 视图渲染,处理视图结点加载、事件绑定等相关操作 ]] function M:onRender() M.super.onRender(self) end ---------------------- -- 结点析构 ---------------------- --[[-- 视图析构,处理视图结点卸载、事件解除绑定等相关操作 ]] function M:onDestructor() -- [超类调用] M.super.onDestructor(self) end return M
注意我们的局部变量“M”是调用了“classLayerTouch()”这个全局函数创建的,它接收一个字符串类型的参数作为层的名称,然后返回一个CCLayer层对象。新建一个空的节点,它至少需要有3个最基本的函数,分别是ctor(构造函数)、onRender(渲染器)和onDestructor(析构函数)。构造函数和析构函数,但凡学过编程的人都应该会清楚,构造函数用于创建一个对象,析构函数在对象销毁时会被调用。除此之外,游戏内部的视图渲染、节点加载我们都习惯放在onRender也就是渲染器里面去做。这三个函数是bbframework框架的模板方法,Scene(场景)、Layer(层)、Sprite(精灵)都拥有这三个函数,且是我们必须实现的。Lua是一门弱类型的编程语言,但是我们的bbframework自己有一套我们必须遵循的规范,bbframework在搭建时用到了模板方法这种设计模式,所以一些必要的模板方法我们必须重写,哪怕你没有任何自己的操作。
除此之外,Lua不像C#、Java之类的语言,构造函数会自动调用基类(父类、超类)的构造函数。Lua本身是没有类的概念的,它的类是我们模拟出来的,所以我们应当在这三个模板函数里面去手动调用其父类的相应函数。Lua是脚本语言,它所有的对象都可以看成是第一类值,也就是跟整数(int)、浮点数(float)、字符串(string)等一样,都是可以保存在一个变量里面的。其父类就保存在对象的“super”成员里面。所有我们在构造函数里面调用超类的构造函数时就可以使用“M.super.ctor(self, params)”的方式来进行调用,onRender和onDestructor也是类似的道理。
现在我们先往这个MainLayer层里面放一个节点,然后指定贴图为“node.png”,位置设置在屏幕的中央,层级为10,并将其保存在局部变量nodeObj里面。因为节点精灵显示跟渲染相关,所以我们在onRender的超类调用后面添加一行代码:
---------------------- -- 结点渲染 ---------------------- --[[-- 视图渲染,处理视图结点加载、事件绑定等相关操作 ]] function M:onRender() M.super.onRender(self) local nodeObj = D.img("node.png"):pc():to(self, 10) end
bbframework显示一张图片我们可以简单的调用“D.img()”函数来实现,其参数是要显示的图片路径,因为我的图片是放置在x4文件夹的根目录下,所以我直接写“node.png”就可以了,这样我们就可以得到一个简单的精灵。紧接着我们通过“:pc()”函数来将精灵的位置设置到屏幕的中央,“p”代表Position,位置的意思,“c”表示Center,中间的意思。确定位置后,我们再调用“:to()”函数将这个精灵添加到“self”对象上,因为当前对象是MainLayer的实例,“self”类似于C#、Java和C++的“this”关键字,所以self表示的就是当前的Layer。“to()”函数的第二个参数是设置精灵的层级(z值),默认为“0”。完了之后将这个节点赋值给局部变量“nodeObj”,这种多个函数调用的方式我们将其称为“链式调用”。
至于如何让框架默认直接读取x4的资源,我们可以在“game.lua”文件里面强制复写定义。其代码如下:
---------------------- -- 强制覆写定义 ---------------------- -- FIXME: 在此处可以覆盖定义config.lua中声明的变量,因为在进行打包时config.lua文件锁定不受增量包影响 -- 设置驱动模型 DEVICE_MODEL = "x4" -- 设置语言 device.language = "zh"
这个,你从载下来的框架的“main/bbframework/scripts/game.lua”文件里面就可以看到,DEVICE_MODEL这个全局常量就是用来指定强制读取x2或者x4资源的标识位,因为ios要根据设备分辨率自动判断,所以发布时我们一般将这行代码注释,但是开发中我们是强制开启x4的。然后安卓版发布时我们强制为x2,其实在测试阶段测试人员就会要求对其作出相应的操作。device.language这个变量是保存当前语言环境的变量,开发中我们强制让其等于“zh”(中文简体),发布时不管是ios还是android都会注释掉,让bbframework自动获取设备的语言环境。
这时候我们用quick-x-player运行我们的项目,你会看到如图所示的画面:
一个很大的logo图片显示在屏幕的正中间,但是我们同时还会注意到屏幕左下角有三个数字,这个三个数值从上往下依次表示:当前程序调用的渲染次数、当前渲染所耗的时间(秒)和当前游戏的帧率(1秒钟程序刷新了多少次),也就是我们通常游戏开发里面所说的“FPS”值。关于这个数值的开关在“main/bbframework/scripts/config.lua”文件里头:
---------------------- -- 载入游戏配置 ---------------------- -- [调试配置] -- 显示FPS DEBUG_FPS = true -- 显示内存 DEBUG_MEM = false -- 调试等级[DEBUG] (0:禁用调试信息, 1:少量调试信息, 2:详细调试信息) DEBUG = 1 -- 日志等级[LOG] (0:e, 1:e|i, 2:e|i|w, 3: e|i|w|d, 4:e|d) LOG_LEVEL = 1
我们将DEBUG_FPS值设置成true,模拟器左下角就可以开启FPS显示,相反的设置成false就隐藏了。当DEBUG_MEM的值为true时,模拟器的控制台就会每隔几秒钟打印一次程序当前所消耗的内存:
然后,我们将调试等级也就是DEBUG设置成“0”的时候,控制台将不会有调试日志输出,在产品发布之前,我们都会将其设置为0。LOG_LEVEL表示的是日志等级,同样,控制台只会输出相应权限等级的日志,其最大值是“5”,发布时设置为0。DEBUG_FPS和DEBUG_MEM发布时设置为“false”,这样可以减少程序运行时的消耗。
介绍完那三个数值,我们回到我们的游戏内部,这时我们会发现我们的这个logo貌似太大了点。很好,我们的bbframework给节点(可以算是游戏内部所以对象的基类)提供了缩放的函数“:scale()”,它接收的参数是缩放比例,取值范围为非负数,是一个浮点数,“1.0”表示按原图大小显示(原尺寸),“0”表示无穷小,“2.0”表示原图的2倍大小,以此类推。我们先将其缩放到0.5倍大小。
local nodeObj = D.img("node.png"):scale(0.5):pc():to(self, 10)
我们按下“F5”刷新模拟器,就会发现精灵的长宽都变成原来的一半了。但是有人要说了,那要是只将精灵的水平方向缩放一半怎么办?不用担心,bbframework肯定是会考虑到这些东西的。要快,就肯定要方便;要方便就肯定要随意,要随意就肯定要封装很多的东西给我们使用。bbframework给节点提供了“:scaleX()”和“:scaleY()”两个函数分别用于水平和竖直方向的缩放,其参数和“scale()”函数一致。同时,bbframework兼容“setScale()”、“setScaleX()”和“setScaleY()”三个函数,用法和效果同框架封装的一致(我们的bbframework基本完全兼容quick-cocos2d-x的API)。
说完缩放,我们再来说下位置,我们肯定不会只将位置设置在屏幕的中央,框架除了“:pc()”函数之外还提供了“:p()”函数来设置节点的位置。我们将我们的节点设置到屏幕的原点,也就是(0, 0)的位置上:
local nodeObj = D.img("node.png"):scaleX(0.5):p(0 ,0):to(self, 10)
重新刷新模拟器,我们会发现节点显示在了屏幕的左下角位置上。除了调用节点的“p()”函数之外,我们还可以调用“setPosition()”函数来设置位置。但是“p()”这个函数比较随意,我们可以通过传递x和y两个参数来设置之外,我们还可以直接传一个坐标点变量进去,代码如下:
local pointObj = ccp(0, 0) local nodeObj = D.img("node.png"):scale(0.5):p(pointObj):to(self, 10)
“ccp()”函数是个全局的函数,其接收x和y两个参数,返回一个CCPoint类型的对象,我们将其作为p()函数的参数,刷新模拟器后发现精灵的位置还是成功设置在了原点上。但是我们发现我们的精灵只显示了右上角的部分,其他的都在屏幕的外面,这是因为精灵锚点(作用点)的关系。bbframework默认精灵的锚点在精灵的中间位置,也就是(0.5, 0.5)的位置,锚点的取值是一个CCPoint类型,(0, 0)表示左下角,(1, 1)表示右上角,以此类推。我们如果要让我们的坐标作用在精灵的左下角,我们只需要将其锚点设置在精灵的左下角即可:
local nodeObj = D.img("node.png"):scale(0.5):p(ccp(0, 0)):anchor(ccp(0, 0)):to(self, 10)
再次修改我们的代码,我把那个pointObj变量去掉,直接将ccp(0, 0)写在了p()函数的参数位置,然后调用了“:anchor()”函数来设置精灵的锚点。刷新模拟器我们就成功的将精灵的左下角与屏幕原点重合了。
大家还可以试着自己把锚点坐标设置成负数或者自己喜欢的值,然后运行起来看看效果,这样能帮助你更加了解精灵的这个属性。
除了调用p()函数,我们也可以在精灵创建时调用“D.imgc()”来创建,其效果和“D.img():pc()”是一样的,参数同样只要传一张图片的路径进去就可以了。除此之外,bbframework还提供了“D.imgp()”函数,其参数有三个,第一个是精灵的文理贴图路径,第二个和第三个分别是精灵坐标的x和y值,其效果类似于“D.img():p()”。这个大家可以自己试着修改看看效果,我这里就不再阐述了。
介绍完这个,我们再来说说精灵的层级,也就是“z值”。我们先在屏幕上创建两个精灵:
local nodeObj1 = D.imgp("node.png", 480, 320):scale(0.5):to(self, 10) local nodeObj2 = D.imgc("node.png"):to(self)
我们的bbframework是以960*640分辨率为基准开发的,所以坐标(480, 320)正好是屏幕的中间位置。通过以上两行代码我们会发现两个精灵在屏幕中央重叠摆放了。因为我们对nodeObj1进行了缩放,所以比较小的那个是nodeObj,但是我们渲染是先创建的先渲染,后创建的后渲染,后渲染的会覆盖在先渲染的上面。可是由于我们给第一个精灵设置了层级为“10”,而第二个节点不设置(默认为0),所以第一个节点对象反而显示在了第二个节点的前面。我们可以通过调用框架的“:z()”或者“:zorder()”函数来改变精灵的层级:
local nodeObj1 = D.imgp("node.png", 480, 320):scale(0.5):to(self, 10) local nodeObj2 = D.imgc("node.png"):to(self) nodeObj1:zorder(-1)
或者:
local nodeObj1 = D.imgp("node.png", 480, 320):scale(0.5):to(self, 10) local nodeObj2 = D.imgc("node.png"):to(self) nodeObj1:z(-1)
重新刷新模拟器,你会发现小个的那个不见了,其实是我们把第一个节点的层级设置成了“-1”,它被大个的精灵给挡住了。不相信的我们可以将第二个精灵的位置设置到小个精灵的边上,让它们有一部分重合。代码如下:
local nodeObj1 = D.imgp("node.png", 480, 320):scale(0.5):to(self, 10) local nodeObj2 = D.imgc("node.png"):to(self):bindTouchLocate() nodeObj1:z(-1)
我们重新刷新了模拟器,但是你会发现怎么感觉一点变化都没有。其实不然,你是在用鼠标点中那个大个的精灵,然后将其往旁边拖动一下,你会发现,你把它拖走了。而它下面的那个小个的精灵出现在了模拟器上。
这是因为我们在创建nodeObj2时给第二个精灵节点调用了“:bindTouchLocate()”函数,这个函数可以开启我们的界面辅助功能,所有调用了该函数的节都可以被我们随意的拖动。当我们将其任意的拖动到某个位置然后松开鼠标的时候,我们会在模拟器的控制台看到居然打印出了这个节点精灵的当前坐标,这对于我们进行游戏场景的界面布局起到了决定性的作用,大大提高了我们的开发速度。
好了,今天就先到这里为止了。看在我还没吃晚饭,饿着肚子写了这篇博客快3个小时以及今天又是情人节的份上,饶过我吧。这年头清明节都能过成情人节,更何况今天,我这单身狗都不敢上街找饭馆吃饭了,忍到现在实在饿,我们后会有期!【下一次我们来讲解下自定义精灵以及bbframework的触控绑定和一些触控相关的全局标签的使用。】
【脚注】
宝宝巴士-快乐童年!