1、定位器的实现css
定位器的目的是实现对场景树中的节点精肯定位,获取对象实例,从而获取节点在界面中的位置、矩形大小等信息。 node
定位器:在Cocos2d(js)游戏引擎中用于精确描述场景树中的某一节点的字符串,其实现方式借鉴了css(层叠样式表)选择器设计思路,如下咱们将实现一个简单的从定位器字符串解析到节点定位的整个过程。c#
1.定位符规则数组
在Cocos2d中能够经过节点名字、节点tag值来表示一个节点,在js中还可使用对象的变量名好比:this[‘_button’]来获取节点对象。 一共有三种有效方式来表示一个node节点对象,因而这里对应三种定位符号,以下:函数
“/” :名字(name)定位符,例如: ‘a/b/c’ 、’dialogLayer/_closeButton’ post
“#”:tag(id)定位符,例如:’a#123’ 动画
“.”:变量名(var)定位符,例如:’a._okButton’ui
还有为了简化定位器字符串的长度,借鉴css中的子选择器this
“>”:子(child)定位符,例如:’a>c’spa
2.定位器解析
定位器字符串中只存在名字、tag、变量名、定位符,其中由定位符将名字、tag、变量名隔开。在js中最简单的就是使用String.split函数将其分开,但这里分隔符(/、#、. 、>)不止一个符号如何实现呢?以前我是本身写的一个遍历函数来解析,但感受有些丑陋。思考以后以为split不该该不支持多个分隔符,因而搜索了下,发现果然不出我所料splite还支持正则表达示的分隔规则,代码由n行变成1行,很是满意,愈来愈喜欢上了js。
1
2
3
|
> var locator =
"a/b.c#1"
> locator.split(/[.,
//,>,#]/g);
[
'a'
,
'b'
,
'c'
,
'1'
]
|
其实分隔符是用于修饰名字、tag、变量名的,一个定位符配合一个名字,因而设计一个简单的对象,以下:
1
|
{symbol: ‘/’, name:’a’}
|
代码以下:
1
2
3
4
5
6
7
|
//使用正则表达示分隔名字
var names = str.split(/[.,
//,>,#]/g);
var segments = names.map(function(name) {
var index = str.indexOf(name);
var symbol = str[index - 1] ||
'>'
;
return
{symbol: symbol, name: name.trim()};
});
|
segments中就是咱们须要的东西了,并且这里咱们为了编写方便或美观,在定位符与名字之间容许有空格,如:”a > b # 1”
还有一般第一段定位符一般为主界面下的某个子节点,我这里使用’>’为默认定位符。
3.定位函数实现细节
有了上面定位器字符串的解析输出,定位实际上是很容易的,由于cocos2d-js中已经提供了getChildByName、getWidgetByTag、seekWidgetByName、seekWidgetByTag,而对于变量定位符则更是简单,object[‘name’]便可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
/**
* 定位节点
* @param locator 定位器字符串
* @param cb 回调函数
* @returns null/node 返回值
*/
locateNode: function(locator, cb) {
//解析定位器字符串
var segments =
this
.parseLocatorString(locator);
if
(_.isEmpty(segments)) {
return
;
}
cc.
log
(
"定位器:"
+ locator);
var child,
node =
this
._target;
//this._target为检索起点节点
for
(var i = 0; i < segments.length; i++) {
var item = segments[i];
switch
(item.symbol) {
case
'/'
:
child = node.getChildByName(item.name);
break
;
case
'.'
:
child = node[item.name];
break
;
case
'>'
:
child = xl.UIHelper.seekNodeByName(node, item.name);
break
;
case
'#'
:
child = xl.UIHelper.seekNodeByTag(node, item.name);
break
;
}
if
(child) {
node = child
}
else
{
node = null;
break
;
}
}
if
(node) {
cb(node);
//定位节点成功,回调返回结果
this
._locatedNode = node;
}
else
{
//定位失败,等待0.1秒后重试。
this
.scheduleOnce(function () {
this
.locateNode(locator, cb);
}, 0.1);
}
return
node;
}
|
以上代码实现了在场景树中定位检索的过程,自认代码还算清晰明了,也很简单。在代码最后一段中,当定位失败后,会启动定时器再次检索节点,这是为了解决在引导任务切换时UI界面尚未建立出来而致使定位设计的解决方法。
2、手形提示动画与坐标转换
当咱们在场景树中定位到节点获取到节点对象后,就能够经过节点属性获取它的位置、大小、描点等信息,从而计算出节点在屏幕上的位置。
1.节点位置与世界位置
position: 咱们能够经过node.getPosition()、node.setPosition()来获取和设置节点在其父节点中的位置,也可使用属性node.x、node.y。这里须要注意的是一个节点的座标只是表示他在父节点位置,咱们在大多数时候,节点是层层包含的。咱们要获取一个节点在屏幕中的位置不能简单地使用x\y属性。
世界座标:在Cocos2d中全部节点都提示了从:局部座标到世界座标的相互转换,函数为 node.convertToNodeSpace 、node.convertToWorldSpace. 须要注意的是咱们要获取一个节点所在的世界座标位置,需使用其父节点计算子节点在世界中的位置。
2.获取定位节点在世界中的位置和矩形大小
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
/**
* 手形图标指向node节点
* @param node 节点对象
* @param cb 手指点击后的回调完成函数
*/
pointToNode: function(node, cb) {
this
.setTouchNode(null);
var pt = node.getParent().convertToWorldSpace(node.getPosition());
//设置手指图标,指定向pt位置
this
.setFinger(pt);
//经过node锚点计算,矩形大小
pt.x -= node.width * node.anchorX;
pt.y -= node.height * node.anchorY;
this
._touchRect = cc.rect(pt.x, pt.y, node.width, node.height);
//开启遮罩显示
this
.showMask();
//保存回调函数,node节点事件完成后执行
this
._callBack = cb;
},
|
3.手形提示动画
手形提示动画很是简单,使用action动做 cc.MoveTo便可完成,只不过在这里setFighter函数咱们有时传入一个point参数,有时可能传入的是一个point数组。当传入一个point数组时,但愿手形精灵按照数组中的point位置一个一个的依次移动。
3、定位区遮罩显示
咱们获取到节点对象,世界座标位置、矩形大小这些信息,生成一个矩形遮罩很是容易。遮罩显示主要使用cocos2d中的ClippingNode来实现,关于ClippingNode相关的技术、教程、文章已经有不少了,这里就不在详细说明,等我把代码整理好后会提供开打、显示遮罩的开关已方便使用。
4、非定位区触摸事件屏蔽
1.为引导层注册触摸事件
关于为Node节点注册触摸事件请参考:《在Cocos2d-JS中实现自动绑定Cocos Studio UI控件和事件(二)》
2.在引导层TouchBegan事件中屏蔽触摸操做
一般在引导过程当中是不容许进行其它操做的,须要屏蔽全部UI行为,只能执行当前引导步骤规定的动做。咱们经过以前的节点定位、座标转换、矩形区计算、遮罩显示一系列操做已经能够看到可操做区了。这里写图片描述的区域。
使用cc.node的onTouchBegan事件在返回true后将触摸事件吞食掉,从而屏蔽下层事件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
onTouchBegan: function(touch) {
//触摸矩形区不存在,直接吞食事件
if
(!
this
._touchRect) {
return
true
;
}
//获取触摸位置
var pt = touch.getLocation();
//检查触摸位置是否在可操做矩形区范围内
var ret = cc.rectContainsPoint(
this
._touchRect, pt);
if
(ret && !
this
._touchNode) {
//隐藏手形
this
._finger.setVisible(
false
);
//执行回调函数
this
._callBack();
}
//在可操做区,不对屏蔽下层事件
return
!ret;
},
|
5、定位区UI事件的检测
当引导层,将触摸事件放入下层游戏界面时,正常状况下会触发下层UI中的控件事件,从而进行真实的游戏步骤。这时咱们能够简单地认为当前引导任务被完成。
但在真实的项目中却有很多问题,有时用户并不会按咱们想象的操做进行游戏,在引导可操做区进行的不是点击而是滑动操做时就会很是的悲剧!由于这时引导上层已经检测到可操做已经发生触摸,将当前任务pass掉了,但下层UI事件并未执行,好比建立一个新界面,这时将致使引导进行不下去。
如何解决这个问题呢?如何检查下层UI已经真实进行了事件的触发?
目前我在本身的项目中没有特别好的办法,主要使用了sz.UILoader来管理事件并在控件的onTouchEnded时执行游戏逻辑,建立界面、界面切换等。 在sz.UILoader库中预留有勾子函数,用于拦截控件的事件。
1
2
3
4
5
6
|
sz.UILoader.prototype._onWidgetEvent = function(sender, type) {
if
(type === ccui.Widget.TOUCH_ENDED) {
//使用观察者模式,发送按钮点击事件
xl.postMessage(xl.Message.BUTTON_CLICKED, sender);
}
};
|
xl.postMessage封装了cc.NotificationCenter,用于向xl.Message.BUTTON_CLICKED事件观察者广播消息,参数为当前控件对象。
利于观察者模式检查UI事件被执行
将引导层对象注册为xl.Message.BUTTON_CLICKED事件的观察者,一但有控件的ccui.Widget.TOUCH_ENDED事件被触发,引导层都能知道,注册代码以下:
1
|
xl.addObserver(xl.Message.BUTTON_CLICKED,
this
,
this
.touchNodeClicked);
|
touchNodeClicked为引导层观察者响应函数:
1
2
3
4
5
6
7
8
9
10
11
|
touchNodeClicked: function(sender) {
if
(
this
._touchNode &&
(sender ===
this
._touchNode ||
sender.getName() ===
this
._touchNode.getName() )) {
this
.setTouchNode(null);
this
._touchRect = null;
this
._finger.setVisible(
false
);
//在此时才能执行任务回调函数,进行下一个任务的开始
this
._callBack();
}
},
|
到此关于UI定位、提示动画、事件屏蔽与检查的全部细节已经所有完成。
(未完待继)