一、EventSystem對(duì)象的說明
當(dāng)我們?cè)趫?chǎng)景中創(chuàng)建任一UI對(duì)象后,Hierarchy面板中都可以看到系統(tǒng)自動(dòng)創(chuàng)建了對(duì)象EventSystem,可以看到該對(duì)象下有三個(gè)組件:EventSystem、StandaloneInputModule、TouchInputModule,后面兩個(gè)組件都繼承自BaseInputModule。
EventSystem組件主要負(fù)責(zé)處理輸入、射線投射以及發(fā)送事件。一個(gè)場(chǎng)景中只能有一個(gè)EventSystem組件,并且需要BaseInputModule類型組件的協(xié)助才能工作。EventSystem在一開始的時(shí)候會(huì)把自己所屬對(duì)象下的BaseInputModule類型組件加到一個(gè)內(nèi)部列表,并且在每個(gè)Update周期通過接口UpdateModules接口調(diào)用這些基本輸入模塊的UpdateModule接口,然后BaseInputModule會(huì)在UpdateModule接口中將自己的狀態(tài)修改成'Updated',之后BaseInputModule的Process接口才會(huì)被調(diào)用。
BaseInputModule是一個(gè)基類模塊,負(fù)責(zé)發(fā)送輸入事件(點(diǎn)擊、拖拽、選中等)到具體對(duì)象。EventSystem下的所有輸入模塊都必須繼承自BaseInputModule組件。StandaloneInputModule和TouchInputModule組件是系統(tǒng)提供的標(biāo)準(zhǔn)輸入模塊和觸摸輸入模塊,我們可以通過繼承BaseInputModule實(shí)現(xiàn)自己的輸入模塊。
除了以上兩個(gè)組件,還有一個(gè)很重要的組件通過EventSystem對(duì)象我們看不到,它是BaseRaycaster組件。BaseRaycaster也是一個(gè)基類,前面說的輸入模塊要檢測(cè)到鼠標(biāo)事件必須有射線投射組件才能確定目標(biāo)對(duì)象。系統(tǒng)實(shí)現(xiàn)的射線投射類組件有PhysicsRaycaster, Physics2DRaycaster, GraphicRaycaster。這個(gè)模塊也是可以自己繼承BaseRaycaster實(shí)現(xiàn)個(gè)性化定制。
總的來說,EventSystem負(fù)責(zé)管理,BaseInputModule負(fù)責(zé)輸入,BaseRaycaster負(fù)責(zé)確定目標(biāo)對(duì)象,目標(biāo)對(duì)象負(fù)責(zé)接收事件并處理,然后一個(gè)完整的事件系統(tǒng)就有了。
另外,其實(shí)這些說明官方都有提供,這里也就是把英文譯成了中文,并整理下,加上自己的理解,有問題的地方請(qǐng)各路神仙多多指教。
官方文檔在這里: http://docs.unity3d.com/ScriptReference/EventSystems.EventSystem.html
二、UGUI中的事件系統(tǒng)
根據(jù)第一節(jié)中的說明,EventSystem和BaseInputModule是粘在一個(gè)對(duì)象上的,這兩個(gè)模塊在EventSystem對(duì)象上可以直接看到。那么,BaseRaycaster模塊呢。。。
其實(shí)射線檢測(cè),肯定是從攝像機(jī)發(fā)起的,那么BaseRaycaster模塊也一定和攝像機(jī)關(guān)系一定不簡(jiǎn)單。
對(duì)于UI模塊,在Canvas對(duì)象下我們可以看到GraphicRaycaster組件。如果Canvas的渲染模式是SceenSpace-Overlay,那么我們是看不到Camera組件的。所以應(yīng)該是GraphicRaycaster會(huì)對(duì)UI不同的渲染模式做特殊處理。
因?yàn)橛蠫raphicRaycaster組件的原因,Canvas上的所有UI對(duì)象,都可以接受輸入模塊發(fā)出的事件,具體事件的處理在第四節(jié)說明。
三、場(chǎng)景對(duì)象中使用事件系統(tǒng)
場(chǎng)景中的非UI對(duì)象,如果想要接收輸入模塊的事件,一樣的道理,也需要給攝像機(jī)掛上一個(gè)射線檢測(cè)組件。PhysicsRaycaster, Physics2Draycaster這兩個(gè)組件分別是用于3D和2D的場(chǎng)景。當(dāng)然,還需要場(chǎng)景的對(duì)象掛了collider射線才檢測(cè)的到。
其實(shí)官方對(duì)射線檢測(cè)也是做了說明的,如果不詳讀手冊(cè)是不會(huì)發(fā)現(xiàn)的,這里是傳送門:
http://docs.unity3d.com/Manual/Raycasters.html
如果場(chǎng)景中只有一個(gè)射線檢測(cè)源:When a Raycaster is present and enabled in the scene it will be used by the EventSystem whenever a query is issued from an InputModule.
如果場(chǎng)景中有多個(gè)射線檢測(cè)源:If multiple Raycasters are used then they will all have casting happen against them and the results will be sorted based on distance to the elements.
四、響應(yīng)事件
1、輸入模塊可以檢測(cè)到的事件
StandaloneInputModule和TouchInputModule兩個(gè)組件會(huì)檢測(cè)一些輸入操作,以事件的方式(message系統(tǒng))通知目標(biāo)對(duì)象,那么這兩個(gè)組件支持的事件主要有以下:
IPointerEnterHandler - OnPointerEnter - Called when a pointer enters the object
IPointerExitHandler - OnPointerExit - Called when a pointer exits the object
IPointerDownHandler - OnPointerDown - Called when a pointer is pressed on the object
IPointerUpHandler - OnPointerUp - Called when a pointer is released (called on the original the pressed object)
IPointerClickHandler - OnPointerClick - Called when a pointer is pressed and released on the same object
IInitializePotentialDragHandler - OnInitializePotentialDrag - Called when a drag target is found, can be used to initialise values
IBeginDragHandler - OnBeginDrag - Called on the drag object when dragging is about to begin
IDragHandler - OnDrag - Called on the drag object when a drag is happening
IEndDragHandler - OnEndDrag - Called on the drag object when a drag finishes
IDropHandler - OnDrop - Called on the object where a drag finishes
IScrollHandler - OnScroll - Called when a mouse wheel scrolls
IUpdateSelectedHandler - OnUpdateSelected - Called on the selected object each tick
ISelectHandler - OnSelect - Called when the object becomes the selected object
IDeselectHandler - OnDeselect - Called on the selected object becomes deselected
IMoveHandler - OnMove - Called when a move event occurs (left, right, up, down, ect)
ISubmitHandler - OnSubmit - Called when the submit button is pressed
ICancelHandler - OnCancel - Called when the cancel button is pressed
只要目標(biāo)對(duì)象的mono腳本實(shí)現(xiàn)了以上接口,那么輸入模塊會(huì)將檢測(cè)到的事件通過這些接口通知給目標(biāo)對(duì)象。
參考:http://docs.unity3d.com/Manual/SupportedEvents.html
如果你自定義了自己的輸入模塊,那么以上這些事件肯定是不能用的了。
2、接收輸入事件的方式
1)、自行繼承接口實(shí)現(xiàn)監(jiān)聽
在mono腳本中繼承輸入模塊提供的事件接口,如下圖。接口的定義方式也可以查下官方手冊(cè),http://docs.unity3d.com/ScriptReference/EventSystems.IBeginDragHandler.html 這邊有每一個(gè)接口的定義方式,放心大膽地點(diǎn)進(jìn)去。另外,添加ObjChooseEvent組件的對(duì)象,一定要有Collider哦。
using UnityEngine;
using UnityEngine.EventSystems;
public class Player3DShow : MonoBehaviour,IDragHandler,IPointerUpHandler {
public void OnDrag(PointerEventData data){
Vector2 deltaPos = data.delta;
}
public void OnPointerUp (PointerEventData eventData)
{
Vector2 deltaPos = data.delta;
}
}
2)、 通過EventTrigger組件監(jiān)聽事件
這是一個(gè)官方組件。在需要監(jiān)聽事件的對(duì)象上,掛上這個(gè)組件,然后在Inspector面板展開配置,你會(huì)看到這個(gè)組件提供了所有輸入模塊支持的事件類型的監(jiān)聽,如下圖。
這種方式的優(yōu)點(diǎn)是,當(dāng)你選中一個(gè)你要監(jiān)聽的類型,你可以為這個(gè)事件類型添加多個(gè)監(jiān)聽接口,統(tǒng)一管理,可以清楚的知道到底哪些地方響應(yīng)了這個(gè)事件呢。如果是繼承Interface的方式,它將會(huì)分散在N個(gè)腳本里,一旦出現(xiàn)問題,那查起來一定會(huì)很酸爽。
但是這種通過配置的方式,一旦項(xiàng)目多人協(xié)作,項(xiàng)目的復(fù)雜度起來,這種拖來拽去的配置終究是會(huì)有很多問題的,比如某個(gè)組件刪除,比如響應(yīng)接口改了個(gè)名字~~都會(huì)導(dǎo)致配置丟失,而問題又不能及時(shí)發(fā)現(xiàn)。又或者程序的監(jiān)聽接口因?yàn)槟承l件而不同。所以也許你會(huì)需要第三種方式。
3)、動(dòng)態(tài)添加EventTrigger組件或者修改組件
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
public class ScriptControl : MonoBehaviour {
// Use this for initialization
void Start ()
{
var trigger = transform.gameObject.GetComponent<EventTrigger>();
if (trigger == null)
trigger = transform.gameObject.AddComponent<EventTrigger>();
// 實(shí)例化delegates
trigger.triggers = new List<EventTrigger.Entry>();
// 定義需要綁定的事件類型。并設(shè)置回調(diào)函數(shù)
EventTrigger.Entry entry = new EventTrigger.Entry();
// 設(shè)置 事件類型
entry.eventID = EventTriggerType.PointerClick;
// 設(shè)置回調(diào)函數(shù)
entry.callback = new EventTrigger.TriggerEvent();
UnityAction<BaseEventData> callback = new UnityAction<BaseEventData>(OnScriptControll);
entry.callback.AddListener(callback);
// 添加事件觸發(fā)記錄到GameObject的事件觸發(fā)組件
trigger.delegates.Add(entry);
}
public void OnScriptControll(BaseEventData arg0)
{
Debug.Log("Test Click");
}
}
五、EventSystem組件提供的一些有意思的接口
其實(shí)文檔都有http://docs.unity3d.com/ScriptReference/EventSystems.EventSystem.html 只是也許你沒有注意。
點(diǎn)擊EventSystem對(duì)象,你可以看到運(yùn)行時(shí)候的一些詳細(xì)數(shù)據(jù):
變量:
firstSelectedGameObject:這個(gè)值可以在面板設(shè)置,如果你需要游戲在啟動(dòng)的時(shí)候自動(dòng)選中某個(gè)對(duì)象,需要鼠標(biāo)的那一下點(diǎn)擊。
currentSelectedGameObject:當(dāng)前選中的對(duì)象,你可以通過這個(gè)值判斷當(dāng)前是否鼠標(biāo)點(diǎn)擊在對(duì)象上,因?yàn)橐苍S你有拖動(dòng)攝像機(jī)的功能,但是你又不喜歡點(diǎn)擊某些對(duì)象的時(shí)候這個(gè)功能又被響應(yīng),所以通過這個(gè)變量判斷是一個(gè)很好的辦法。
接口:
IsPointerOverGameObject:當(dāng)前鼠標(biāo)是否在事件系統(tǒng)可以檢測(cè)的對(duì)象上。
SetSelectedGameObject:這個(gè)接口也許你會(huì)忽略,但是它很棒。因?yàn)槟泓c(diǎn)擊場(chǎng)景對(duì)象的時(shí)候,如果不調(diào)用這個(gè)接口,你的對(duì)象是收不到OnSelect事件的,currentSelectedGameObject的值也不會(huì)被設(shè)置的,必須在點(diǎn)擊事件里調(diào)用這個(gè)接口設(shè)置選中對(duì)象!
Ex:
public void OnPointerClick(PointerEventData eventData)
{
print ("OnPointerClick...");
currEvent.SetSelectedGameObject(gameObject);
}
不用在場(chǎng)景里找EventSystem對(duì)象,EventSystem組件有一個(gè)current靜態(tài)變量,它就是你要的對(duì)象,直接EventSystem.current即可使用。
六、點(diǎn)擊事件的特殊實(shí)現(xiàn)方式,使用Button控件
針對(duì)Click事件還存在一種特殊實(shí)現(xiàn)方式,在UGUI系統(tǒng)中,官方提供了一個(gè)Button控件,Button封裝了一個(gè)Onlick事件,操作方式類似于方式二,
使用Button我們可以實(shí)現(xiàn)動(dòng)態(tài)的變更鼠標(biāo)綁定的點(diǎn)擊事件。代碼如下:
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class BtnControl : MonoBehaviour {
// Use this for initialization
void Start ()
{
var button = transform.gameObject.GetComponent<Button>();
if (button != null)
{
button.onClick.RemoveAllListeners();
button.onClick.AddListener(TestClick);
}
}
public void TestClick()
{
Debug.Log("Test Click. This is Type 4");
}
// Update is called once per frame
void Update () {
}
}