1. 程式人生 > >「Unity3D」(3)事件系統和EventSystem詳細解讀

「Unity3D」(3)事件系統和EventSystem詳細解讀

Unity的事件系統提供了多種使用方式,又和物理碰撞結合在一起,所以同樣使用Unity事件處理,就能寫出各種各樣的風格。很多專案還會自己對事件在進行一次封裝,有的還會使用第三方外掛。無論是手勢外掛還是UI外掛,都是要建立在事件系統之上的,這些外掛都會各自針對事件進行封裝。所以,混亂,未知,衝突在所難免。

本文針對Unity2017的版本,對事件系統進行梳理和解讀,然後對EventSystem的使用和最佳實踐給出一套方案。

Unity事件處理的種類

1. 系統回撥OnMouse事件

首當其衝的就是MonoBehavior上的事件回撥,可以參看MonoBehaviour文件。這是一系列的OnMouse開頭的回撥函式。

OnMouseDown
OnMouseDrag
OnMouseEnter
OnMouseExit
OnMouseOver
OnMouseUp

這個處理方式有以下幾個特點:

  • MonoBehavior所在的GameObject需要有Collider碰撞元件,並且Physics.queriesHitTriggers設定為True,這個在Edit -> Physics Settings -> Physics or Physics2D中設定。

  • 或者MonoBehavior所在的GameObject存在GUIElement。

  • OnMouse處理函式可以是協程。

  • GameObject所有MonoBehavior實現OnMouse的函式都會呼叫。

  • Collider或GUIElement的層級順序,會遮擋事件的傳遞。

按照官方的解釋,這是GUI事件的一部分,參看EventFunctions。設計的初衷也是為了GUI服務的。參看ExecutionOrder最後的unity執行流程圖,會發現OnMouse事件是一個獨立的Input Event。

可以看到,OnMouse事件在,Physics事件之後,Update之前,記住這個順序,後面會用到。並且,這是引擎本身回撥的,就引擎使用而言可以看成是,訊息驅動。至於引擎的實現,可是輪詢也可以是訊息驅動。

2. 在Update中輪詢Input物件

public class ExampleClass : MonoBehaviour
{ 
    public
void Update() { if (Input.GetButtonDown("Fire1")) { Debug.Log(Input.mousePosition); } } }

這是官方的例子,Input擁有各種輸入裝置的資料資訊。每一幀不斷的檢測,檢視有沒有需要處理的輸入資訊,利用GameObject本身的層級順序來控制Update的呼叫順序,從而控制了Input的處理順序。

Input的資訊由引擎自己設定的,明顯Unity需要實現不同平臺的事件處理,然後對Input進行設定。另外有一個InputManager面板用來配置Input相關屬性的,在Edit -> Physics Settings -> Input中。

由前面的執行流程圖可知,OnMouse事件會在Update之前呼叫,當然我們也可以在OnMouse中使用Input,這樣就變成了訊息驅動,而不是輪詢了。但這樣的缺點是,事件必須由touch或pointer碰撞觸發,比如鍵盤或控制器按鈕的事件就沒有辦法捕獲了。

3. EventSystem

最常見的是在UGUI中,用來進行UI的事件處理和分發。但看其命名,就知道這並不是一個僅僅針對UI的事件系統。參看文件介紹,EventSystem,可以看到:

The Event System is a way of sending events to objects in the application based on input, be it keyboard, mouse, touch, or custom input. The Event System consists of a few components that work together to send events.

EventSystem基於Input,可以對鍵盤,滑鼠,觸控,以及自定義輸入進行處理。EventSystem本身是一個管理控制器,核心功能依賴InputModule和Raycaster模組。

Input Module

用來處理Input資料,管理事件狀態,和傳送事件給GameObject。

這是一個可替換模組,比如引擎自帶了,StandaloneInputModule和TouchInputModule,也可以自定義。

Raycaster

用來捕獲哪些GameObject需要執行事件處理。一共有3個種類。

  • Graphic Raycaster 用於UI元素就是繼承自Graphic的物件。所以button這樣的Selectable物件需要一個Target Graphic物件。
  • Physics 2D Raycaster 用於2D物理碰撞元素,依賴於Collider2D。
  • Physics Raycaster 用於3D物理碰撞元素,依賴於Collider。

通常,canvas只用了Graphic Raycaster,用來處理UI的事件。所以只要是繼承Graphic物件都會自動獲得EventSystem事件監聽。但官方文件有這樣的說明:

If you have a 2d / 3d Raycaster configured in your scene it is easily possible to have non UI elements receive messages from the Input Module. Simply attach a script that implements one of the event interfaces.

也就是說,場景如果添加了2d / 3d Raycaster的射線檢測,那麼EventSystem也會檢測相應的物理元素。(後面會詳細介紹這種混合的使用模式)

這是EventSystem預設支援的事件處理回撥,當然也可以自定義,就需要擴充套件自己的Input Module來實現。這裡需要強調幾點:

  • IMoveHandler,ISubmitHandler 這樣的回撥事件可以接受鍵盤輸入,可以在InputManager面板裡配置自定義的值,不然就會使用預設值。

  • 鍵盤事件需要Selectable物件,比如button就是繼承自Selectable。所以當button被選中的時候,就會響應鍵盤事件,比如回車和上下左右方向鍵,還有空格鍵。這時候,在button所在GameObject繫結一個實現了ISubmitHandler或IMoveHandler介面的指令碼,也會同時觸發。

  • 另外,如果我想使用Collider來觸發這個鍵盤事件,就需要使用一個Selectable物件。Collider與Selectable放在一起,並且掛載一個實現了實現了ISubmitHandler或IMoveHandler介面的指令碼,當Collider被選中的時候,就可以觸發鍵盤事件了。

  • 最後,系統提供了一個EventTrigger元件。這僅僅是針對SupportedEvents的視覺化封裝。在面板上拖放配置就用EventTrigger,用程式碼繫結就用實現介面的方法。這就像UnityEvent和C# event的關係。

這是EventSystem的訊息傳遞系統,UGUI就是使用了這個機制來發送事件訊息的。文件寫的比較清楚,我們可以自定義自己訊息傳遞。值得注意的有兩句話:

The new UI system uses a messaging system designed to replace SendMessage. The messaging system is generic and designed for use not just by the UI system but also by general game code.

這個訊息系統是用來替換SendMessage,實際專案估計也很少會用SendMessage,因為效率不高。另外,這是一個通用的訊息系統,不僅僅是針對UI的,而是通用的機制。

不過,我仍然覺得這種搜尋GameObject查詢介面型別呼叫的方式,沒有Action直接訂閱呼叫來的高效。

EventSystem 與 射線檢測的衝突問題

如果EventSystem僅僅用來處理UI事件的時候,就會與我們自己手動的射線檢測產生衝突,Physics.Raycast(ray, out hit),原因是顯而易見的,因為PhysicsGraphic只會過濾Graphic物件並且有自己的Raycast呼叫。我們自己手動的Raycast就會穿透過去。

那為什麼我們需要自己呼叫Raycast呢 ?其原因在於,我們使用了Collider碰撞檢測,UI系統並不會處理。這時候,我們就需要使用EventSystem的IsPointerOverGameObject()方法來判斷,有沒有選中了UI元素。具體的解決方案參看我的上一篇文章。

但現在我們知道EventSystem也是可以處理Physics元素的,那麼我們就可以放棄手動Raycast,轉而讓EventSystem統一處理。

EventSystem混合處理Physics

首先,我們看一個官方文件的說明 Raycasters

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.

當多個Raycaster被使用的時候,結果會按照元素之間的距離排序,然後事件就會按照這個順序被傳遞。

第一步

在相機上新增Physics2DRaycaster,我這裡只需要對Physics2D檢測,如果是3D就用Physics3DRaycaster。Physics Raycaster 依賴一個相機,如果沒有會自動新增。我掛載在相機上,射線檢測就會依賴這個相機。

這裡我用在GameCamera上面,當然也可以放在UICamera上面,Physics Raycaster掛載在哪個相機上面,射線就依賴這個相機的Culling Mask。

另外需要注意的是,Physics Raycaster所在的相機層級,也就是Depth,會影響到事件傳遞的順序。比如,UI Camera層級高於Game Camera,就會永遠先出發UI上的事件。同樣,OnMouse事件會預設依賴Main Camera的層級。

第二步

給需要碰撞檢測的GameObject,新增Collider和EventSystem的事件處理回撥介面。注意GameObject的Layer也要與Camera和Raycaster一致,才能正確被檢測到。

事件介面實現指令碼(圖中的Test)需要Collider,事件才能正確回撥,並且GameObject和相機的距離決定了Collider的層級,也就是事件阻擋關係。

第三步

這樣一來,EventSystem的SupportEvents的介面全部被應用到了Physics上面。也就不再需要自己手動去呼叫射線去檢測Physics碰撞了。那麼,還隱含著一個事情就是,EventSystem的IsPointerOverGameObject()就無法在判斷對UI的點選了。因為現在點選到Physics也會讓這個函式返回True。

EventSystem與OnMouse的區別

  • OnMouse 會先於 EventSystem 觸發。因為EventSystem的原始碼顯示,其在Update中去輪詢檢測處理Input的輸入。而OnMouse事件先於Update呼叫。

  • OnMouse指令碼需要在同一個GameObject上掛載Collider才能檢測。EventSystem的指令碼會根據子節點的Collider來觸發(平行節點不行)。

  • Rigidbody有個特點,會把子節點所有的Collider統一檢測和處理。也就是說,OnMouse指令碼與RigidBody在一起就可以檢測所有的子節點Collider,而不再需要同級的Collider。而EventSystem的指令碼則不依賴於Rigidbody,都可以檢測子節點的Collider。

  • OnMouse依賴於Tag為MainCamera相機的Culling Mask來過濾射線。EventSystem則是依賴掛載Physics Raycaster的相機。

另外,當在有Collider的子節點都掛載OnMouse或EventSystem事件的時候,只會觸發一次事件。但在同一個GameObject上掛載多個指令碼,就會觸發多次。

訊息輪詢 VS 訊息驅動

奇怪的是Unity好像比較推薦訊息輪詢的方式,就是在Update裡面每一幀去檢測Input的變化,來處理事件。從引擎的實現方式來看,完全可以採用訊息驅動,來暴露API。因為不同的平臺肯定都會提供,事件的回撥函式。平臺自身的事件有些是啟動執行緒輪詢的,有些是從底層作業系統拿到的事件回撥。當然,訊息驅動往往回調函式會在獨立的執行緒裡,不在渲染執行緒就無法呼叫渲染的API。

不過Unity引擎完全可以提供一組事件的回撥,就像OnMouse事件一樣。但Input的設計就已經是基於輪詢的事件查詢機制了。我們可以看到在EventSystem的原始碼實現裡,也是在Update裡去輪詢Input Module的狀態。

protected virtual void Update()
{
    // ...
    TickModules();

    // ....
    if (!changedModule && m_CurrentInputModule != null)
        m_CurrentInputModule.Process();
}

輪詢需要每一幀都去檢測判斷Input的狀態,如果這樣的檢測散落在程式碼的各處是非常不好的。難道Unity的本意就是實現一個輪詢的外掛,在用訊息驅動去分發事件 ?於是EventSystem就出現了。

總結

EventSystem的設計和功能,就能夠統一所有的事件處理。其提供的事件回撥介面也很豐富,基本可以滿足各種需求。基於這些介面手勢檢測也很容易實現。也會受益於未來Unity的優化和改進。

「用起來」

相關推薦

Unity3D(3)事件系統EventSystem詳細解讀

Unity的事件系統提供了多種使用方式,又和物理碰撞結合在一起,所以同樣使用Unity事件處理,就能寫出各種各樣的風格。很多專案還會自己對事件在進行一次封裝,有的還會使用第三方外掛。無論是手勢外掛還是UI外掛,都是要建立在事件系統之上的,這些外掛都會各自針對事件

LibreOJ2097 - CQOI2015任務查詢系統

i++ using eof rip read 時間復雜度 ble 優先 str Portal Description 給出\(n(n\leq10^5)\)個任務,和總時間範圍\(m(m\leq10^5)\)。每個任務有開始/結束時間\(s_i,e_i(1\leq s_i \

深入淺出 java集合CollectionMap

本系列文章主要對java集合的框架進行一個深入淺出的介紹,使大家對java集合有個深入的理解。 本篇文章主要具體介紹了Collection介面,Map介面以及Collection介面的三個子介面Set,List,Queue。 什麼是集合 Java集合類存放於 jav

【LOJ】#6433. PKUSC2018最大字首

題解 神仙的狀壓啊QAQ 設一個\(f[S]\)表示數字的集合為\(S\)時\(sum[S]\)為字首最大值的方案數 \(g[S]\)表示數字集合為\(S\)時所有字首和都小於等於0的方案數 答案就是\(sum_{S} sum[S] * f[S] * g[2^{N} - 1 - S]\) 求\(f\)

PKUSC2018最大字首 [DP?]

「PKUSC2018」最大字首和 Tags: DP 狀壓 「PKUSC2018」最大字首和 題意 求對於a[]的所有排列的的最大字首和的和對998244353取模的值。 分析 其實就是一個計數問題?和概率期望沒有什麼關係。 然後考慮狀壓,然後某個狀態表示的是選

PKUSC2018最大字首

狀壓好題啊。 一眼看出時間複雜度 \(O(n2^n)\),然後開始想正解。 然後設 \(dp_i\) 為字首狀態為 \(i\) 時的方案數,所以這時候 \(sum_i\) 一定是單峰的。 可以推匯出: \[(1\leq j<i)\ sum_i\geq sum_j\Rightarrow sum_i

Unity3D(9)自定義編輯器選單擴充套件總結

新增選單 [MenuItem("Tools/MyOption")] private static void MyOption() { // 自定義選單Tools } [MenuItem("Tools/Sub/MyOption")] priva

.jshintrc檔案各項詳細解讀

{ // // 強制選項 // // When set to true, these options will make JSHint produce more warnings about your code. /** * 是否阻止位運算子的使用

提升不止一點點,Dubbo 3.0 預覽版詳細解讀,還愣著幹啥啊?進來啊

Dubbo 自 2011 年 10 月 27 日開源後,已被許多非阿里系的公司使用,其中既有當當網、網易考拉等網際網路公司,也不乏

提升不止一點點,Dubbo 3.0 預覽版詳細解讀

Dubbo 自 2011 年 10 月 27 日開源後,已被許多非阿里系的公司使用,其中既有當當網、網易考拉等網際網路公司,也不乏

補課進行時:設計模式(3)——做菜一樣簡單的模版方法模式

![](https://cdn.geekdigging.com/DesignPatterns/java_design_pattern.jpg) ## 1. 前文彙總 [「補課」進行時:設計模式系列](https://www.geekdigging.com/category/%e8%ae%be%e8%ae%

C#內置的事件機制Unity3D姻緣

mono 需要 listen 存在 logs sharp strong 關聯 有一種 最近因為項目,也因為一些其他事情而導致學習的停止,抽個空來記錄下C#內置的事件在Unity3D中的使用。 我需要讓一個物體對鼠標懸停做出事件的響應的情況下,我們通常會創建一個繼承Mon

《數據庫系統概論》 -- 3.1SQL概論DDL

pat 內部 rdbms primary 如果 針對 存儲過程 直接 dep 3.1 SQL特點 綜合統一 集數據定義語言、數據操縱語言、數據控制語言的功能於一體,語言風格統一,可以獨立完成數據庫生命周期中的全部活動

用jQuery來綁定事件3種方法區別

委托 images 不同的 doc mage his ges utf htm 1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF

joyoi1957 Poetize5VaniCl2捉迷藏

覆蓋 include names ostream eof scan 最大 return inline 最小路徑可重點覆蓋。先傳遞閉包,然後拆點,\(n-\)最大匹配,看算法競賽進階指南。 #include <iostream> #include <cstr

無向圖的割頂橋,無向圖的雙連通分量入門詳解及模板 -----轉載

dbr break nts word 否則 mark push gravity 無向連通圖 https://blog.csdn.net/stillxjy/article/details/70176689 割頂和橋:對於無向圖G,如果刪除某個節點u後,連通分量數目

樹莓派3 之 啟動 系統配置

sshd err date int pic imageview ras per c4c 在上一篇文章中系統已經燒寫到SD卡中了,接下來我們來啟動,啟動系統。為了方便操作,需要一個HDMI的顯示器,USB鼠標和鍵盤。啟動直接將SD卡放入樹莓派,通電就啟動了。不需要特殊配置就啟

【題解】洛谷6月月賽 —— 數學約數個數

分解 pri clas left pac 這樣的 DC 兩個 探測   看德國戰墨西哥去了結果發現比賽只剩下30分鐘……當然之後又思考這題挺久也還是不會做。看了一下題解,覺得這個做法挺厲害的,在這裏記錄一下:   原式實際上就是:(\(K +=

LuoguP2420 讓我們異或吧(樹上前綴

pro main () radi 運算 總結 str logs using P2420 讓我們異或吧-洛谷 題目描述 異或是一種神奇的運算,大部分人把它總結成不進位加法. 在生活中…xor運算也很常見。比如,對於一個問題的回答,是為1,否為0.那麽:

把虛擬教練帶回家,EuMotus想用AI實現高精度運動反饋系統

https://36kr.com/p/5089139.html 無需穿戴裝置,只需一個紅外攝像頭和+已安裝好EuMotus專利軟體的手提電腦 由政府主導的高達2200億美金的健身與運動支出,15%的健身俱樂部年增長率,46%的中國年輕人正在使用健身器材與健身軟體。這是美國IBIS此前釋出的關於中國體