標籤:
簡介http://blog.csdn.net/qinning199/article/details/41951517
遊戲開發中一個很重要的功能就是互動,如果沒有與使用者的互動,那麼遊戲將變成動畫,而處理使用者互動就需要使用事件監聽器了。
總概:
- 事件監聽器(cc.EventListener) 封裝使用者的事件處理邏輯
- 事件管理器(cc.eventManager) 系統管理使用者註冊的事件監聽器,根據觸發的事件類型分發給相應的事件監聽器
- 事件對象(cc.Event) 包含事件相關資訊的對象
如何使用呢? 首先需要建立一個事件監聽器,事件監聽器包含以下幾種類型:
- 觸摸事件監聽器 (cc.EventListenerTouch)
- 鍵盤事件監聽器 (cc.EventListenerKeyboard)
- 加速計事件監聽器 (cc.EventListenerAcceleration)
- 滑鼠事件監聽器 (cc.EventListenerMouse)
- 自訂事件監聽器 (cc.EventListenerCustom)
在監聽器中實現各種事件的處理邏輯,然後將監聽器加入到事件管理器中, 當事件觸發時,事件管理器會根據事件類型分發給相應的事件監聽器。下面以一個簡單的樣本來示範使用的方法。
使用方法
現在會在一個情境中添加三個按鈕(cc.Sprite),三個按鈕將會互相遮擋,並且都需要能夠監聽和處理觸摸事件,以下是具體實現
首先建立三個精靈,作為三個按鈕的顯示圖片
var sprite1 = new cc.Sprite("Images/CyanSquare.png"); sprite1.x = size.width/2 - 80; sprite1.y = size.height/2 + 80; this.addChild(sprite1, 10); var sprite2 = new cc.Sprite("Images/MagentaSquare.png"); sprite2.x = size.width/2; sprite2.y = size.height/2; this.addChild(sprite2, 20); var sprite3 = new cc.Sprite("Images/YellowSquare.png"); sprite3.x = 0; sprite3.y = 0; sprite2.addChild(sprite3, 1);
建立一個單點觸摸事件監聽器(事件類型:TOUCH_ONE_BY_ONE),並完成邏輯處理內容
// 建立一個事件監聽器 OneByOne 為單點觸摸 var listener1 = cc.EventListener.create({ event: cc.EventListener.TOUCH_ONE_BY_ONE, swallowTouches: true, // 設定是否吞沒事件,在 onTouchBegan 方法返回 true 時吞掉事件,不再向下傳遞。 onTouchBegan: function (touch, event) { //實現 onTouchBegan 事件處理回呼函數 var target = event.getCurrentTarget(); // 擷取事件所綁定的 target, 通常是cc.Node及其子類 // 擷取當前觸摸點相對於按鈕所在的座標 var locationInNode = target.convertToNodeSpace(touch.getLocation()); var s = target.getContentSize(); var rect = cc.rect(0, 0, s.width, s.height); if (cc.rectContainsPoint(rect, locationInNode)) { // 判斷觸摸點是否在按鈕範圍內 cc.log("sprite began... x = " + locationInNode.x + ", y = " + locationInNode.y); target.opacity = 180; return true; } return false; }, onTouchMoved: function (touch, event) { //實現onTouchMoved事件處理回呼函數, 觸摸移動時觸發 // 移動當前按鈕精靈的座標位置 var target = event.getCurrentTarget(); var delta = touch.getDelta(); //擷取事件數目據: delta target.x += delta.x; target.y += delta.y; }, onTouchEnded: function (touch, event) { // 實現onTouchEnded事件處理回呼函數 var target = event.getCurrentTarget(); cc.log("sprite onTouchesEnded.. "); target.setOpacity(255); if (target == sprite2) { sprite1.setLocalZOrder(100); // 重新設定 ZOrder,顯示的前後順序將會改變 } else if (target == sprite1) { sprite1.setLocalZOrder(0); } } });
引擎提供了cc.EventListener.create統一來建立各類型的事件監聽器,可以通過指定不同的 event
來設定想要建立的監聽器類型,如上例中的cc.EventListener.TOUCH_ONE_BY_ONE 為單點觸摸事件監聽器。
可選event
類型列表:
- cc.EventListener.TOUCH_ONE_BY_ONE (單點觸摸)
- cc.EventListener.TOUCH_ALL_AT_ONCE (多點觸摸)
- cc.EventListener.KEYBOARD (鍵盤)
- cc.EventListener.MOUSE (滑鼠)
- cc.EventListener.ACCELERATION (加速計)
- cc.EventListener.CUSTOM (自訂)
將事件監聽器添加到事件管理器中
// 添加監聽器到管理器 cc.eventManager.addListener(listener1, sprite1); cc.eventManager.addListener(listener1.clone(), sprite2); cc.eventManager.addListener(listener1.clone(), sprite3);
這裡的cc.eventManager 是一個單例對象,可直接拿來使用。通過調用 addListener
函數可以將listener加入到管理器中,需要注意的是第二個參數,如果傳入的是一個Node對象,則加入的是SceneGraphPriority(精靈以顯示優先順序) 類型的listener,如果是一個數實值型別的參數,則加入到的是FixedPriority 類型的listener。
注意: 這裡當我們想給不同的節點使用相同的事件監聽器時,需要使用 clone()
函數複製出一個新的監聽器,因為在使用 addListener
方法時,會對當前使用的事件監聽器添加一個登入的標記,這使得它不能夠被添加多次。另外,有一點非常重要,FixedPriority 類型的 listener添加完之後需要手動刪除,而SceneGraphPriority 類型的 listener是跟node綁定的,在node調用cleanup時會被移除。具體的樣本用法可以參考引擎內建的tests。
更快速的添加事件監聽器到管理器的方式
下面提交一種更快捷綁定事件到節點的方式, 不過這樣做就不會得到監聽器的引用,無法再對監聽器進行其他動作,適用於一些簡單的事件操作, 代碼如下:
cc.eventManager.addListener({ event: cc.EventListener.TOUCH_ALL_AT_ONCE, onTouchesMoved: function (touches, event) { var touch = touches[0]; var delta = touch.getDelta(); var node = event.getCurrentTarget().getChildByTag(TAG_TILE_MAP); var diff = cc.pAdd(delta, node.getPosition()); node.setPosition(diff); } }, this);
cc.eventManager的 addListener
的第一個參數也支援兩種類型的參數: cc.EventListener
類型對象和json格式的對象,如果是json格式對象,方法會根據傳入的event
屬性來建立對應的監聽器。
新的觸摸機制
以上的步驟相對於 2.x 版本觸摸機制實現,稍顯複雜了點。在老的版本中只需在節點中重載onTouchBegan
/onTouchesBegan
等方法, 處理對應的觸摸事件邏輯,然後調用cc.registerTargetedDelegate
或cc.registerStandardDelegate
將節點加入到觸摸事件分發器中就可以了,甚至有些已封裝的類只需要調用setTouchEnabled
, 就可以開啟觸摸事件,比如:cc.Layer
。
而新機制將事件處理邏輯獨立出來,封裝到一個 監聽器(listner) 中,使得不同對象可以使用同一份監聽器代碼(使用clone()
來達到目的)。另外,cc.eventManager加入了精靈以顯示優先順序 (SceneGraphPriority)排序的功能,以這種類型註冊的監聽器,事件管理器會根據螢幕顯示的情況來決定事件會最佳化分發給哪個事件監聽器。 而2.x要實現這樣的功能卻非常的麻煩,需要使用者自己通過調用setPriority
來管理節點的事件響應優先順序。
注意:與 SceneGraphPriority 所不同的是 FixedPriority 將會依據手動設定的 Priority
值來決定事件相應的優先順序,值越小優先順序越高, 後面章節中會作更具體的講解。
其它事件派發處理模組
除了觸摸事件響應之外,還可以使用相同的事件處理方式來處理其他事件。
鍵盤響應事件
除了可以監聽鍵盤按鍵,還可以是終端裝置的各個菜單鍵,都能使用同一個監聽器來進行處理。
//給statusLabel綁定鍵盤事件 cc.eventManager.addListener({ event: cc.EventListener.KEYBOARD, onKeyPressed: function(keyCode, event){ var label = event.getCurrentTarget(); //通過判斷keyCode來確定使用者按下了哪個鍵 label.setString("Key " + keyCode.toString() + " was pressed!"); }, onKeyReleased: function(keyCode, event){ var label = event.getCurrentTarget(); label.setString("Key " + keyCode.toString() + " was released!"); } }, statusLabel);
加速計事件
在使用加速計事件監聽器之前,需要先啟用此硬體裝置, 代碼如下:
cc.inputManager.setAccelerometerEnabled(true);
然後將相應的事件處理監聽器與sprite進行綁定就可以了,如下:
cc.eventManager.addListener({ event: cc.EventListener.ACCELERATION, callback: function(acc, event){ //這裡處理邏輯 } }, sprite);
滑鼠響應事件
對於PC和超級本,添加滑鼠事件的的處理,可以加強使用者的體驗,其處理邏輯與觸摸事件基本一樣,多了一些滑鼠特有的事件響應,如滾輪事件(onMouseScroll).
cc.eventManager.addListener({ event: cc.EventListener.MOUSE, onMouseMove: function(event){ var str = "MousePosition X: " + event.getLocationX() + " Y:" + event.getLocationY(); // do something... }, onMouseUp: function(event){ var str = "Mouse Up detected, Key: " + event.getButton(); // do something... }, onMouseDown: function(event){ var str = "Mouse Down detected, Key: " + event.getButton(); // do something... }, onMouseScroll: function(event){ var str = "Mouse Scroll detected, X: " + event.getLocationX() + " Y:" + event.getLocationY(); // do something... } },this);
注意: 由於在PC瀏覽器中,沒有觸摸事件,而此時強制要求使用者寫滑鼠事件的響應代碼,必然會讓開發人員多寫很多代碼,事實上觸摸響應的邏輯與滑鼠相差不大,所以引擎在檢測到不支援觸摸事件時,會讓滑鼠事件類比成觸摸事件進行分發,開發人員只需編寫觸摸事件監聽器就能完成大部分工作,而對於針對滑鼠操作而設計的遊戲,需要判斷使用者按下什麼鍵,響應滾輪等,這就需要開發人員編寫滑鼠事件監聽器了。
(開發人員反饋,滑鼠事件監聽器也需要有swallowTouches這個選項,我們將會有v3.1版本中加入這個項.)
自訂事件
以上是系統內建的事件類型,這些事件由系統內部自動觸發,如 觸控螢幕幕,鍵盤響應等,除此之外,還提供了一種 自訂事件,簡而言之,它不是由系統自動觸發,而是人為的幹涉,如下:
var _listener1 = cc.EventListener.create({ event: cc.EventListener.CUSTOM, eventName: "game_custom_event1", callback: function(event){ // 可以通過getUserData來設定需要傳輸的使用者自訂資料 statusLabel.setString("Custom event 1 received, " + event.getUserData() + " times"); } }); cc.eventManager.addListener(this._listener1, 1);
以上定義了一個 “自訂事件監聽器”,實現了一些邏輯, 並且添加到事件分發器。那麼以上邏輯是在什麼情況下響應呢?請看如下:
++this._item1Count; var event = new cc.EventCustom("game_custom_event1"); event.setUserData(this._item1Count.toString()); cc.eventManager.dispatchEvent(event);
建立了一個自訂事件(EventCustom
)對象 ,並且設定了其使用者自訂(UserData)資料,手動調用cc.eventManager.dispatchEvent(event);
將此事件分發出去,從而觸發之前監聽器中所實現的邏輯。
cc.eventManager加入自訂事件的處理,開發人員就可以很方便的使用該功能來實現觀察者模式。
移除事件監聽器
我們可以通過以下方法移除一個已經被添加了的監聽器。
cc.eventManager.removeListener(listener); //移除一個已添加的監聽器
也可以使用removeListeners
,移除註冊到cc.eventManager
中指定類型的所有監聽器,當然使用該函數時,傳入的參數如果是一個節點(cc.Node及其子類)對象, 事件管理器將移除與該對象相關的所有事件監聽器, 代碼如下:
cc.eventManager.removeListeners(cc.EventListener.TOUCH_ONE_BY_ONE); //移除所有單點觸摸事件監聽器 cc.eventManager.removeListeners(aSprite); //移除所有與aSprite相關的監聽器
事件管理器還提供了函數用來移除登入的所有監聽器。
cc.eventManager.removeAllListeners();
當使用 removeAllListeners
的時候,此節點的所有的監聽將被移除,推薦使用 指定刪除的方式。
_注意:調用removeAllListeners
之後 菜單(cc.Menu
) 也不能響應。因為它內部有一個觸摸事件監聽器,也會從事件管理器中刪除。
暫停/恢複 與情境相關(SceneGraph類型)的監聽器
開發過程中,我們經常會遇到這樣的情況:想要讓一個Layer中所有的Node對象的事件都停止回應。 在響應使用者事件後,又要恢複該Layer的所有事件響應。如: 使用者想要顯示一個強制回應對話方塊,顯示對話方塊後,禁止對話方塊後所有對象的事件響應。 在使用者關閉對話方塊後,又恢複這些對象的事件響應。
我們只需要暫停根node的事件,就可以讓根節點以及其子節點暫停事件響應。 代碼如下:
cc.eventManager.pauseTarget(aLayer, true); //讓aLayer對象暫停響應事件
而恢複對象的事件響應也非常簡單:
cc.eventManager.resumeTarget(aLayer, true); //讓aLayer對象恢複響應事件
注意: 第二個參數為選擇性參數,預設值為false, 表示是否遞迴調用子節點的暫停/恢複操作.
進階話題SceneGraphPriority類型與FixedPriority類型詳解
事件管理器將監聽器類型分為兩大類:SceneGraphPriority和FixedPriority, 下面將會詳細說明它們之間的區別, 並介紹FixedPriority的使用情境與使用方法。
SceneGraphPriority事件類型是指事件的響應優先順序與監聽器關聯對象在情境中顯示順序(zOrder)相關, 比如一個精靈對象在情境的顯示在最上層時,它對事件的響應優先順序最高。 這樣開發人員就不需要再像v2.x中那樣在情境對象的zOrder變化後,手動再調用setPriority
來改變相應的優先順序了,這些事將交由管理器來處理。
而 FixedPriority 事件類型則是相對於 SceneGraphPriority 來定義的,不需要與情境顯示順序相關的事件監聽器 也就是最佳化級固定的(fixed),就可以註冊成FixedPriority類型事件。 我們的SceneGraphPriority定義的系統優先順序是0, 在添加監聽器(addListener
)時, 如果第二個參數設定為負數時,該監聽器就會擁有比所有情境相關監聽器都高的優先順序, 而如果是正數,則反之。
那麼什麼情況下使用FixedPriority類型的監聽器呢? 比如,一個冒險類的遊戲中,遊戲主角應該要最先響應觸摸事件,而UI介面的按鈕往往會安排在介面的最上層。但是,如果主角移動到了按鈕的後面,這時點擊遊戲主角,如果遊戲主角註冊的是SceneGraphPriority類型監聽器,響應的將會是按鈕事件。而如果註冊成FixedPriority類型,並把它的優先順序設定為負數,將會響應遊戲主角的事件。
有開發人員反饋想保持他們在v2.x的響應優先順序管理機制,因為他們有特殊的需求,那麼這部分開發人員也可以使用FixedPriority來管理,cc.eventManager
也提供了一個setPriority
函數來管理優先順序。
UI控制項的事件處理詳解
Cocos提供一套UI控制項,許多開發人員對於控制項的事件響應,特別是對於容器類控制項(如:ccui.PageView, ccui.ScrollView)的事件響應有些疑惑。這裡將詳細說明控制項的事件處理流程。
首先來看一下ccui.Widget
的事件實現, 所有的控制項的事件監聽器都是單點觸摸事件,並且會吞食事件,註冊代碼如下:
this._touchListener = cc.EventListener.create({ event: cc.EventListener.TOUCH_ONE_BY_ONE, swallowTouches: true, onTouchBegan: this.onTouchBegan.bind(this), onTouchMoved: this.onTouchMoved.bind(this), onTouchEnded: this.onTouchEnded.bind(this) }); cc.eventManager.addListener(this._touchListener, this);
然後看一下它的各事件響應函數,會發現每個函數都會有類似這樣的語句: if (widgetParent) widgetParent.interceptTouchEvent(ccui.Widget.TOUCH_XXXXX, this, touch);
這句的意思是,在控制項處理完自己的觸摸事件之後,都會向父節點(widgetParent)發送事件響應通知。那麼,interceptTouchEvent的實現是什麼呢? 代碼如下:
interceptTouchEvent: function(eventType, sender, touch){ var widgetParent = this.getWidgetParent(); if (widgetParent) widgetParent.interceptTouchEvent(eventType,sender,touch); }
對於像ccui.Button, ccui.ImageView這樣的控制項,它只是簡單的向父類發送事件通知就行了,而對於像ccui.PageView這樣的容器類控制項,會對這些通知做出響應,代碼如下:
interceptTouchEvent: function (eventType, sender, touch) { var touchPoint = touch.getLocation(); switch (eventType) { case ccui.Widget.TOUCH_BEGAN: this._touchBeganPosition.x = touchPoint.x; this._touchBeganPosition.y = touchPoint.y; break; case ccui.Widget.TOUCH_MOVED: this._touchMovePosition.x = touchPoint.x; this._touchMovePosition.y = touchPoint.y; var offset = 0; offset = Math.abs(sender.getTouchBeganPosition().x - touchPoint.x); if (offset > this._childFocusCancelOffset) { sender.setFocused(false); this._handleMoveLogic(touch); } break; case ccui.Widget.TOUCH_ENDED: case ccui.Widget.TOUCH_CANCELED: this._touchEndPosition.x = touchPoint.x; this._touchEndPosition.y = touchPoint.y; this._handleReleaseLogic(touch); break; } }
這樣的處理,就能實現在按鈕上滑動時,也能讓其父節點的PageView觸摸事件。不然,如果不採用這種機制,當一個PageView中填滿了子控制項時,PageView將無法響應觸摸事件。
屬性與方法列表cc.Event (事件類別)
屬性/方法
類型
參數說明
用法說明
getType
Number
no
返回事件類型,包含:TOUCH, KEYBOARD, ACCELERATION, MOUSE, CUSTOM
stopPropagation
void
no
停止當前事件的冒泡
isStopped
Boolean
no
事件是否已停止
getCurrentTarget
cc.Node
no
返回事件相關的Node對象, 如果事件未與cc.Node對象關聯,則返回null
cc.EventCustom (自訂事件)
cc.EventCustom
繼承自 cc.Event
屬性/方法
類型
參數說明
用法說明
setUserData
void
data: 要設定的自訂資料
設定使用者自訂資料
getUserData
*
no
返回使用者佈建的自訂資料
getEventName
String
no
返回自訂事件名稱
cc.EventMouse (滑鼠事件)
cc.EventMouse
繼承自 cc.Event
屬性/方法
類型
參數說明
用法說明
setScrollData
void
scrollX, scrollY
設定滾輪資料
getScrollX
Number
no
返回x軸滾輪資料
getScrollY
Number
no
返回y軸滾輪資料
setLocation
void
x, y
設定滑鼠游標位置
getLocation
cc.Point
no
擷取滑鼠游標位置
getLocationInView
cc.Point
no
返回滑鼠游標在螢幕上的位置
getDelta
cc.Point
no
擷取當前游標與上一游標的位移量
setButton
void
button
設定滑鼠按鍵
getButton
Number
no
擷取滑鼠按鍵
cc.EventTouch ()
cc.EventTouch
繼承自 cc.Event
屬性/方法
類型
參數說明
用法說明
getEventCode
Number
no
擷取觸摸事件類型代碼: BEGAN, MOVED, ENDED, CANCELLED
getTouches
Array
no
擷取觸摸事件中所有點資訊
cc.EventListener (事件監聽器)
屬性/方法
類型
參數說明
用法說明
checkAvailable
boolean
no
檢測監聽器是否有效
clone
cc.EventListener
no
複製一個監聽器,其子類會重寫本函數
create
cc.EventListener
json object
通過json對象建立事件監聽器
cc.EventListener.create
函數參數列表:
建立EventListenerTouchOneByOne對象:
event: cc.EventListener.TOUCH_ONE_BY_ONE
選擇性參數:
- swallowTouches, boolean, 是否吞下該touch點
- onTouchBegan, function, TouchBegan 事件回調
- onTouchMoved, function, TouchMoved 事件回調
- onTouchEnded, function, TouchEnded 事件回調
- onTouchCancelled, function, TouchCancelled 事件回調
建立EventListenerTouchAllAtOnce對象:
event: cc.EventListener.TOUCH_ALL_AT_ONCE
選擇性參數:
- onTouchesBegan, function, TouchesBegan 事件回調
- onTouchesMoved, function, TouchesMoved 事件回調
- onTouchesEnded, function, TouchesEnded 事件回調
- onTouchesCancelled, function, TouchesCancelled 事件回調
建立EventListenerKeyboard對象:
event: cc.EventListener.KEYBOARD
選擇性參數:
- onKeyPressed, function, KeyPressed (鍵按下) 事件回調
- onKeyReleased, function, keyRelease (鍵放開) 事件回調
建立EventListenerMouse對象:
event: cc.EventListener.MOUSE
選擇性參數:
- onMouseDown, function, MouseDown 事件回調
- onMouseUp, function, MouseUp 事件回調
- onMouseMove, function, MouseMove 事件回調
- onMouseScroll, function, MouseScroll 事件回調
建立EventListenerAcceleration對象:
event: cc.EventListener.ACCELERATION
選擇性參數:
- callback, function, Acclerometer 事件回調
建立EventListenerCustom對象:
event: cc.EventListener.CUSTOM
選擇性參數:
- callback, function, 自訂事件回調
cc.eventManager
屬性/方法
類型
參數說明
用法說明
pauseTarget
void
node, recursive(是否遞迴調用子類)
暫停傳入的node相關的所有監聽器的事件響應
resumeTarget
void
node, recursive
恢複傳入的node相關的所有監聽器的事件響應
addListener
void
json對象或cc.EventListener, node對象或最佳化值
向事件管理器添加一個監聽器
addCustomListener
void
eventName, callback
向事件管理器添加一個自訂事件監聽器
removeListener
void
listener
移除一個事件監聽器
removeListeners
void
listenerType
cc.Node, recursive
removeCustomListeners
void
customEventName
移除同一事件名的自訂事件監聽器
removeAllListeners
void
no
移除所有事件監聽器
setPriority
void
listener, fixedPriority
設定FixedPriority類型監聽器的優先集
setEnabled
void
enabled
是否允許分發事件
isEnabled
boolean
no
檢測事件管理器是否分發事件
dispatchEvent
void
event
分發事件
dispatchCustomEvent
void
eventName, optionalUserData
分發自訂事件
轉載請註明
【cocos2d-js官方文檔】十七、事件分發機制