Cocos2d-x 原本的触摸机制存在一些限制,在使用中需要开发者做不少额外的处理。所以 Quick-Cocos2d-x 提出了自己的一套触摸机制。本文详细介绍了这套新机制的用法。
在 Cocos2d-x 里,整个游戏的画面是由一系列的 Scene, Node, Sprite, Layer 等对象构成的。而所有这些对象都是从 Node 这个类继承而来。我们可以将 Node 称为显示节点
。
一个游戏画面就是许多显示节点构成的一棵树:
1
2
3
4
5
6
7
8
9
10
11
12
|
/|\
| 显示层级
|
| [Node] [Node] [Node]
| | | |
| +---+---+ |
| | |
| [Node] [Node]
| | |
| +-----+-----+
| |
| [Node]
|
在这棵树里,Node 所处的垂直位置就是它们的显示层级
。越往上的 Node,其显示层级就越高。从画面表现上来说,下面的 Node 是背景,上面的 Node 是建筑,那么建筑就会挡住一部分背景。
在 Cocos2d-x 里,只有 Layer 对象才能接受触摸事件。而 Layer 总是响应整个屏幕范围内的触摸,这就要求开发者在拿到触摸事件后,再做进一步的处理。
例如有一个需求是在玩家触摸屏幕上的方块时,人物角色做一个动作。那么使用 Layer 接受到触摸事件后,开发者需要自行判断触摸位置是否在方块之内。当屏幕上有很多东西需要响应玩家交互时,程序结构就开始变得复杂了。
所以 Quick-Cocos2d-x 允许开发者将任何一个 Node 设置为接受触摸事件。并且触摸事件一开始只会出现在这个 Node 的触摸区域
内。
所谓触摸区域
,就是一个 Node 及其所有子 Node 显示内容占据的屏幕空间。要注意的是这个屏幕空间包含了图片的透明部分。下图中,节点 A 是一个 Sprite 对象,它的触摸区域就是图片大小;而节点 B 是一个 Node 对象,其中包含了三个 Sprite 对象,那么节点 B 的触摸区域就是三个 Sprite 对象触摸区域的合集。
为了简化实现,触摸区域
都是一个矩形,所以节点 B 的触摸区域
实际上是一个“包含三个 Sprite 对象触摸区域合集的矩形”,可以参考上图中的红色边框线。
下面列出触摸事件的用法示例,更详细的示例请参考 samples/touch
示例。
local layer = display.newLayer() self:addChild(layer) --1、开启触摸事件 layer:setTouchEnabled(true) --2、设置触摸模式 (默认是单点触摸) --layer:setTouchMode(cc.TOUCH_MODE_ONE_BY_ONE) --3、注册触摸事件 layer:addNodeEventListener(cc.NODE_TOUCH_EVENT, function(event) -- event.name 是触摸事件的状态:began, moved, ended, cancelled -- event.x, event.y 是触摸点当前位置 -- event.prevX, event.prevY 是触摸点之前的位置 local x,y,prevX,prevY = event.x, event.y, event.prevX, event.prevY if event.name == "began" then print("touch Began") return true elseif event.name == "moved" then print("touchMoved") elseif event.name == "ended" then print("touchEnded") elseif event.name == "cancelled" then print("touchCancelled") end end)
触摸事件的 event.name
指示了事件的状态:
began
状态时,如果要继续接收该触摸事件的状态变化,事件处理函数必须返回 true
。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
-- 允许 node 接受触摸事件
node:setTouchEnabled(
true
)
-- 设置触摸模式
node:setTouchMode(cc.TOUCH_MODE_ALL_AT_ONCE) -- 多点
-- node:setTouchMode(cc.TOUCH_MODE_ONE_BY_ONE) -- 单点(默认模式)
-- 注册触摸事件
node:addNodeEventListener(cc.NODE_TOUCH_EVENT, function(event)
-- event.name 是触摸事件的状态:began, moved, ended, cancelled
-- 多点触摸增加了 added 和 removed 状态
-- event.points 包含所有触摸点
-- 按照 events.point[id] = {x = ?, y = ?} 的结构组织
for
id, point in pairs(event.points)
do
printf
(
"event [%s] %s = %0.2f, %0.2f"
,
event.name, id, point.x, point.y)
end
if
event.name ==
"began"
then
return
true
end
end)
|
在多点触摸时,事件状态的含义有所区别:
began
状态时,event.points
中可能仍然只有一个触摸点的数据,其他触摸点数据会通过 added
状态提供。added
状态。此时 event.points
中包含新加入的触摸点数据。removed
状态。此时 event.points
中包含删除的触摸点数据。ended
状态。此时 event.points
中包含删除的触摸点数据。event.points
中只包含有变化的触摸点数据。在新版触摸机制中,还需要主要的一个就是触摸吞噬:
1
|
setTouchSwallowEnabled(
true
)
|
它的作用就是是否继续传递触摸消息,在绘制节点的时候,越是在屏幕上方,就是zOrder越大,越优先接收到触摸事件,如果设置吞噬,那么在它下方的节点都不会接收到触摸消息了。默认如果不设置则quick自动设置为true。
默认情况下,Node 在响应触摸后(在 began
状态返回 true
表示要响应触摸),就会阻止事件继续传递给 Node 的父对象(更下层的 Node),这称为触摸事件吞噬
。
如果要改变这个行为,可以用:
true
。如果设置为 false
,则 Node 响应触摸事件后,仍然会将事件继续传递给父对象。对于一个 Node,随时可以启用或禁用其触摸事件:
false
。但即便禁用了 Node 的触摸事件,也只能阻止这个 Node 响应触摸,而不能阻止这个 Node 的子 Node 响应触摸。
假设有一个对话框(Node),我们需要禁止对话框中的所有 Node 响应触摸。那么需要禁止对话框 Node 捕获事件:
1
|
dialog:setTouchCaptureEnabled(
false
)
|
true
。当设置为 false
时,该 Node 及其所有子 Node 都无法得到触摸事件。 总结而言,setTouchEnabled()
只针对当前 Node,而 setTouchCaptureEnabled()
同时影响当前 Node 及其所有子 Node。
quick 中触摸事件分为三个阶段:capturing(捕获)、targeting(触发)、bubbling(冒泡)。
当用户的一根手指触摸到屏幕时,将产生一个触摸事件:
显示层级
最高,并且其触摸区域
包含触摸位置的那个 Node。这个 Node 被称为 TargetNode(目标 Node)。isTouchCaptureEnabled()
结果,如果返回 false
,则重复 1
cc.NODE_TOUCH_CAPTURE_EVENT
事件的返回结果。任何一个 Node 返回 false
都会阻止事件在 TargetNode 上触发。并从步骤 1 开始查找其他符合条件的 Node。capturing
。targeting
。false
,表示 TargetNode 不响应该事件,并从步骤 1 开始查找其他符合条件的 Node。TargetNode:isTouchSwallowEnabled()
的返回值。如果是 true
,则取消 bubbling
阶段。false
或者事件被吞噬。这个阶段称为 bubbling
。 利用事件的三个阶段,我们可以注册 capturing
阶段的触摸事件处理函数:
1
2
3
4
5
6
7
|
-- 在 capturing 阶段就捕获事件
node:addNodeEventListener(cc.NODE_TOUCH_CAPTURE_EVENT, function(event)
if
event.name ==
"began"
then
-- 在 began 状态返回
false
,将阻止事件
return
false
end
end)
|
关于触摸机制的灵活运用,可以参考 cc.ui
中的各个 UI 控件,以及 samples/touch
示例。
addNodeEventListener()
返回的注册 id。false
。cc.TOUCH_MODE_ONE_BY_ONE
。true
。true
。