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定位、提示動畫、事件屏蔽與檢查的全部細節已經所有完成。
(未完待繼)