【正文】
前面我们介绍了如何在bbframework项目中创建我们自己的模块,也在场景里面添加了精灵节点,但是讲到编程就少不了要说到事件。因为我们是做手机游戏,而现在的手机又普遍都是大屏的智能触控手机,所以我们游戏涉及最多的当属触控操作了。今天我们便来简单介绍下bbframework的触控,帮助我们实现游戏的交互操作。
接着上一次的内容,我们在Layer层上面放置了两个节点,代码如下:
---------------------- -- 结点渲染 ---------------------- --[[-- 视图渲染,处理视图结点加载、事件绑定等相关操作 ]] function M:onRender() M.super.onRender(self) local nodeObj1 = D.img("node.png"):p(480, 320):to(self) local nodeObj2 = D.img("node.png"):p(350, 300):scale(0.5):to(self) end
我们打开模拟器,看到的效果如下:
两个图标nodeObj1和nodeObj2重叠在一起,其中较大的那个是nodeObj1。那我们现在想要说点击大的图片,大的图消失,点击小的图片,大的又出现,这个我们该如何实现呢?bbframework为我们提供了“:bindTouch()”函数来实现节点的触控绑定。然后重写节点的“:onTouchBegan(x, y, touches)”、onTouchMoved(x, y, touches)”和onTouchEnded(x, y, touches)”这三个函数来处理触控事件。onTouchBegan、onTouchMoved和onTouchEnded分别会在节点被点击下去、移动和松开时触发。但是,想要触发onTouchMoved和onTouchEnded还要在对应的onTouchBegan里面返回“true”才行,不返回或者返回“false”触控的onTouchMoved和onTouchEnded都不会被触发。这三个函数的前两个参数x和y表示的就是当前触控位置(触控点)的横纵坐标。最后一个参数touches是一个表,保存的是当前的触控信息,目前表中前两个成员保存的分别是触控的x和y,第三个成员是当前的触控点索引,因为电脑上用鼠标都是单点,所以那个值会等于“1”。
---------------------- -- 结点渲染 ---------------------- --[[-- 视图渲染,处理视图结点加载、事件绑定等相关操作 ]] function M:onRender() M.super.onRender(self) local nodeObj1 = D.img("node.png"):p(480, 320):to(self) local nodeObj2 = D.img("node.png"):p(350, 300):scale(0.5):to(self) nodeObj1:bindTouch() function nodeObj1:onTouchBegan(x, y, touches) print("ondeObj1 onTouchBegan!!!") return true end function nodeObj1:onTouchMoved(x, y, touches) print("ondeObj1 onTouchMoved!!!") end function nodeObj1:onTouchEnded(x, y, touches) print("ondeObj1 onTouchEnded!!!") end end
如上,我们给大的精灵绑定了触控,然后刷新模拟器,点击大个的精灵,发现控制台打印了相应函数里面的print()输出的内容,这就说明我们绑定触控成功了。同样的我们也给nodeObj2也绑定了触控。
---------------------- -- 结点渲染 ---------------------- --[[-- 视图渲染,处理视图结点加载、事件绑定等相关操作 ]] function M:onRender() M.super.onRender(self) local nodeObj1 = D.img("node.png"):p(480, 320):to(self) local nodeObj2 = D.img("node.png"):p(350, 300):scale(0.5):to(self) nodeObj1:bindTouch() function nodeObj1:onTouchBegan(x, y, touches) print("ondeObj1 onTouchBegan!!!") return true end function nodeObj1:onTouchMoved(x, y, touches) print("ondeObj1 onTouchMoved!!!") end function nodeObj1:onTouchEnded(x, y, touches) print("ondeObj1 onTouchEnded!!!") end nodeObj2:bindTouch() function nodeObj2:onTouchBegan(x, y, touches) print("------------------nodeObj2 onTouchBegan!!!") return true end function nodeObj2:onTouchMoved(x, y, touches) print("------------------nodeObj2 onTouchMoved!!!") end function nodeObj2:onTouchEnded(x, y, touches) print("------------------nodeObj2 onTouchEnded!!!") end end
刷新模拟器分别点击nodeObj1和nodeObj2,可以看到如下控制台打印:
但是我们发现当我们点击在nodeObj1和nodeObj2的重叠部分时,模拟器也只会输出nodeObj2的触控打印,这说明我们只触发了nodeObj2的触控事件,而nodeObj1的触控事件被nodeObj2吞噬了,触控根本没有分发到nodeObj1上面。那么我们要如何实现两个精灵的触控事件都被触发呢?bbframework框架提供了一些定义好的标签给我们使用,我们只要在onTouchBegan函数里面返回“SIGN_TOUCH_BEGAN_NO_SWALLOWS”就可以实现触控穿透下去,而返回“SIGN_TOUCH_BEGAN_SWALLOWS”触控便被当前对象吞噬了,其他精灵便无法获得触控响应。于是我们修改nodeObj2的onTouchBegan代码如下:
function nodeObj2:onTouchBegan(x, y, touches) print("------------------nodeObj2 onTouchBegan!!!") return SIGN_TOUCH_BEGAN_NO_SWALLOWS end
然后重新刷新模拟器,你就发现触控穿透了。
修改nodeObj2的onTouchBegan代码如下,触控便又开始吞噬了。
function nodeObj2:onTouchBegan(x, y, touches) print("------------------nodeObj2 onTouchBegan!!!") return SIGN_TOUCH_BEGAN_SWALLOWS end
节点的onTouchMoved函数只有在函数onTouchBegan里面return true或者SIGN_TOUCH_BEGAN_NO_SWALLOWS还有SIGN_TOUCH_BEGAN_SWALLOWS时并且发生移动才会触发。这时候我们会发现只要我们在onTouchBegan里面返回的不是“false”,那么不管onTouchMoved有没有被调用,onTouchEnded一定会被调用。这是因为当我们松开节点时,触控松开的事件肯定被触发了。但是我们有时会出现某个精灵在移动过程中被销毁,比如移动香蕉皮精灵到垃圾桶上就自动移除香蕉皮,这个时候触控还是被香蕉皮这个精灵所持有,当我们松开触控手指的时候,程序也会调用onTouchEnded函数,但是发现对象不存在了,要调用的onTouchEnded函数是一个空值。它调用不到了,控制台也输出了错误日志。那么这个时候我们更希望在移动香蕉皮到垃圾桶位置,然后删除完香蕉皮之后触控就此结束,不要再给我进onTouchEnded了,其实你也进不去了,因为没有哈。这个时候我们就用到了另外一个触控标签“SIGN_TOUCH_MOVED_STOP”,我们在onTouchMoved函数里面返回这个标签值,触控就不会进入onTouchEnded了。
function nodeObj2:onTouchMoved(x, y, touches) print("------------------nodeObj2 onTouchMoved!!!") self:p(x, y) if x > 700 then self:remove() return SIGN_TOUCH_MOVED_STOP end end
假设屏幕上横坐标大于700的位置都是垃圾桶的范围,我们的nodeObj2就是香蕉皮,于是我们修改nodeObj2的onTouchMoved函数如上,刷新模拟器后我们往右边拖动nodeObj2到横坐标大于700的位置,我们会发现nodeObj2消失了,然后控制台的打印也在onTouchMoved里面就结束了,就算我们松开触控也不会进入onTouchEnded。
看上面的代码,我们会发现我们是调用了节点的“:remove()”函数来移除一个节点对象的,就像上面博文开头说的,我们有时候只是想隐藏某个精灵,然后后面又要显示它该怎么办,bbframework让我们一样可以通过“show()”和“hide()”两个函数来实现显示和隐藏功能。
function nodeObj1:onTouchBegan(x, y, touches) self:hide() return true end function nodeObj1:onTouchMoved(x, y, touches) end function nodeObj1:onTouchEnded(x, y, touches) end nodeObj2:bindTouch() function nodeObj2:onTouchBegan(x, y, touches) nodeObj1:show() return SIGN_TOUCH_BEGAN_SWALLOWS end function nodeObj2:onTouchMoved(x, y, touches) end function nodeObj2:onTouchEnded(x, y, touches) end
如上,我们在nodeObj1的onTouchBegan里面隐藏掉自身(也就是nodeObj1),然后在nodeObj2的onTouchBegan里面显示nodeObj1,这样我们就实现看博文开头所举的那个例子。
知道怎么绑定触控之后,我们来看下触控是如何分发的。首先,如上图所示,假设我们场景里面加载了一个MainLayer层,层上面又放置了一个叫“node”的节点和一个ButtonLayer层的实例对象。在node里面又有两个子节点分别是:node1和node2,node1又有一个叫做node11的子节点。然后ButtonLayer上面也放置了nodeA和nodeB两个节点。这样构成了如上所示的一棵树。
当我们点击到node11的时候,bbframework会从根节点,也就是MainLayer的实例对象开始遍历这颗节点树。当MainLayer有绑定触控的时候,bbframework就会遍历其下的所有子节点。当它发现节点node有绑定触控的时候就会遍历node的子节点node1,以此类推。但是如果节点node没有绑定触控,那么就算node的所有子节点以及所有的子孙节点都绑定了触控,那也没有办法触发触控事件。因为触控根本没有分发到被点击的节点上。因为Layer的触控是单独处理的,没个Layer也只管理其下的节点的触控,所有当触控遍历到ButtonLayer实例对象时,触控就将ButtonLayer以及ButtonLayer下的所有后代节点的触控都交给ButtonLayer来管理了。当MainLayer所有的后代节点都有绑定触控的时候,由于我们点击的是node11这个节点,所有触控就会从node11开始触发。触控事件触发的节点顺序依次是:node11->mainLayer。如果node2和node11位置重叠,而且node1也和node11位置重叠,并且node11允许触控穿透的话,那么触控就会传递到node1和node2上面,其触控分发的顺序就是:node11->node1->node2->node->mainLayer。这样我们就发现不管被触控的节点是不是允许触控穿透传递,该节点所在的那个层的触控都会被触发,这是因为Layer的触控事件是独立分发的,和节点不同。
同样值得注意的是,我们前面所说的“SIGN_TOUCH_BEGAN_SWALLOWS”和“SIGN_TOUCH_BEGAN_NO_SWALLOWS”还有“SIGN_TOUCH_MOVED_STOP”这三个标签也只对节点有用,对于Layer,我们一般只在层的onTouchBegan里面返回“true”或者“false”。那么层要如何实现穿透和不穿透呢?我们一般在Layer的构造函数的超类调用前面添加“params.swallowsTouches = false”来实现,这样,同一个场景里面的多个层就会实现触控穿透,否则现在的bbframework框架默认触控只会传递到层级最高的那个绑定了触控的层上面,层级比较低的层就算绑定了触控也无法接收到触控。
---------------------- -- 构造方法 ---------------------- --[[-- 构造方法,定义视图实例初始化逻辑 ### Parameters: - table **params** 参数集合 ### Return: - object 对象实例 ]] function M:ctor(params) params.swallowsTouches = false -- [超类调用] M.super.ctor(self, params) end
注意,我们现在的层都是通过“local M = classLayerTouch("层名称")”这个函数来创建的,如果是通过“classLayer("层名称")”或者UUI.lua文件里面的“loadLayer(params)”来创建的话我们需要通过调用层的“:bindTouch()”函数来手动给层绑定触控事件,否则层上面的节点精灵也将无法点击。
我们创建自定义精灵节点一般会在模块的“node”文件夹下新建文件,大体上和Layer是一样的,只是创建的时候调用的不是classLayer()和classLayerTouch()而是调用“classSprite()”和“classSpriteTouch()”这两个函数。这两个函数一样也是接收一个字符串类型的参数作为精灵的类名,但是在创建层的时候,如“MainLayer.lua”时,我们的类名写的是“Main”,然后bbframework框架会自动添加“Layer”后缀,但是精灵与之不同,假设我们要创建一个名为“Dog”的类,我们一般将文件保存为“Dog.lua”,然后代码里面调用时也写全类名“local M = classSpriteTouch("Dog")”。
和创建Layer类似的创建了我们的精灵类,然后我们想要在Layer里面创建这个类,那么我们只要调用require()函数将类引入,比如我的Dog.lua文件在“scripts/app/test/node”文件夹里面,那我只要在Layer里面写如下代码即可创建一个精灵:
---------------------- -- 结点渲染 ---------------------- --[[-- 视图渲染,处理视图结点加载、事件绑定等相关操作 ]] function M:onRender() M.super.onRender(self) local Dog = require("app.test.node.Dog") local dog = Dog.new({ imagePath = "node.png" }):pc():to(self) end
我们先通过require()函数引入文件,类似于C#的using或者C++的include。然后通过得到的类对象来调用“.new()”函数,注意,这里的new()函数是通过“.”来调用的。它接受一个表来充当参数,最后这个参数会被传递到Dog类的构造函数那边的参数上。
--[[!-- 相关操作方法及逻辑实现。 ]] ---------------------- -- 类 ---------------------- local M = classSpriteTouch("Dog") ---------------------- -- 公共参数 ---------------------- -- [常量] -- .. -- [操作变量] -- .. ---------------------- -- 构造方法 ---------------------- --[[-- 构造方法,定义视图实例初始化逻辑 ### Parameters: - table **params** 参数集合 ### Return: - object 对象实例 ]] function M:ctor(params) -- [超类调用] M.super.ctor(self, params) -- 获取传递进来的贴图文件路径 self.imageFile = params.imagePath end ---------------------- -- 结点渲染 ---------------------- --[[-- 视图渲染,处理视图结点加载、事件绑定等相关操作 ]] function M:onRender() M.super.onRender(self) -- 显示贴图 self:display(self.imageFile) end ---------------------- -- 结点析构 ---------------------- --[[-- 视图析构,处理视图结点卸载、事件解除绑定等相关操作 ]] function M:onDestructor() -- [超类调用] M.super.onDestructor(self) end ---------------------- -- 触控 ---------------------- function M:onTouchBegan(x, y, touches) return SIGN_TOUCH_BEGAN_SWALLOWS end function M:onTouchMoved(x, y, touches) -- body end function M:onTouchEnded(x, y, touches) -- body end return M
如上所示,就是我们的Dog.lua文件的内容。我在构造函数里面通过“self.imageFile = params.imagePath”这句代码获得前面new()函数传递的“imagePath”这个参数的值。然后在渲染器(onRender)函数里面通过节点的“:display()”函数来显示贴图。display()函数接受一个字符串类型的参数,这个参数是要显示的图片资源文件的路径。同样的,由于我们的Dog类是精灵节点,所以它的触控支持我们的触控标签,所以我在Dog的onTouchBegan函数里面返回的是“SIGN_TOUCH_BEGAN_SWALLOWS”,用来吞噬触控。
晚上也写了2个多小时了,今天就先到此为止了,如果对于触控还不是很了解的同志可以去SVN的Babybus-lua里面去找下框架1.3版本的触控视频教程来看,那是AC录制的,讲的应该会更准确些。
然后下一次我们来说下bbframework的动作和动画该怎么实现,到时候我就简单的写两个例子,说明下动作的“:at()”和节点的“:runAction()”这两个函数,然后再说下怎么停止指定动作和停止所有动作,最后列举说明下“UAction.lua”里面常用的动作,动作就算过了哈。
【脚注】
宝宝巴士-快乐童年!