利用UniRx滙集事件
July 02, 2017
Unity Asset Store(UAS)裡有上萬個付費、免費的外掛,可說是不管什麼樣的開發內容,都有知名的外掛可使用,加速開發。雖然不可否認的,多數知名且好用的外掛都需要付費才能取得,但在免費的領域中,仍是有很優秀的外掛可幫助開發,這裡要介紹的就是UniRx。從用程式碼來撰寫遊戲邏輯的角度上來看,此外掛雖然常被低估,但它無疑是免費外掛中必備的前三名。
此外掛其實是將.Net裡的Rx(Reactive Extension)函式庫重新撰寫,將原本只能跑在.Net 4.x版的Rx放置於Unity中.Net 3.5的環境,並針對Unity特有的生態,增加了在Unity中和Trigger、UI等接合。也就是因為有這些特有的接合,就算是在Unity 2017中可以用.Net 4.6的框架,也不會直接用原始的Rx 函式庫。作者將之命名為UniRx可說是非常的貼切。
Rx是一個聽起來簡單但用起來有一定複雜性的概念。在它的概念中只有一個很重要的Interface,也就是IObservable需要被理解。光看這個名稱不會有太多的想法,但它和IEnumerable是相輔相成的。
這裡針對IEnumerable的概念最簡單的說明,IEnumerable的使用概念是pull,可視為拿出來。比說說要拿取某個Collection裡的資料時,不論Array也好、List也好或是Tree也好,IEnumerable配合了IEnumerator用了Iterator Pattern,將拿取這動作抽像化。不管不同的型別中怎麼去實作,但拿取出某個值(物件)的想法被抽像化後成了一個共同的Interface。
IObservable概念剛好是相反的,概念上是push,可視為放進去。在它的概念中會註冊一個聆聽者,當某值(事件)產生時,此聆聽者會被知會。此概念用了Observer Pattern,將放入的動作抽像化。不論註冊者為何,當某個關注的值(事件)發生時會被告知。
更詳細的概念可從官網中了解,而此官網裡的資訊雖然是針對通用Rx概念做解說,但UniRx在開發時也儘可能的遵循其概念和命名,故也可以當做UniRx的文件做為使用上的依據。
從原理上去看它不困難,但實際要怎麼應用才是開發者最在意的。以目前UniRx提供的功能來看,主要有二個直接和開發遊戲非常相關聯的部份。其一是滙集事件(event)進行處理。而另一個則是將資料和UI做綁定,達成MVVM(Model-View-ViewModel)的設計。此篇會先進行滙集事件的介紹,待進入放入UI時才會再對資料綁定到UI的內容做更詳細的說明。
從UniRx的GitHub文件裡,可以看到一個簡單的範例。這個範例介紹如何用UniRx的方式拿取雙擊(Double Click)的事件。
在Rx的概念下,每個事件都可滙集成像是一條河流般的事件流(Event Stream),它是川流不息的,若沒有特別的去關閉它,則會源源不絕的產出事件。而範例中的雙擊,在其定義下則為一定時間內若是有超過一次以上的點擊事件,則可視為雙擊。一但IOsbervable開始運作了,此處沒有特別關閉的寫法,要到該物件結結束時,才會被移除。
而EveryUpdate是UniRx針對Unity環境中的特化,它於每一次Update時會觸發,而這裡用Where來做過濾,因為每個Update會有很多事件發生,此處只拿取滑鼠按鍵的事件。
從這個程式碼片段可以了解到UniRx拿來做Input事件的滙集是天性,若是將之用到Snake的遊戲上,它可以當成Input事件的集合器,滙集事件後直接反應給代表Snake的概念層。利用此想法,將它置入到MonoBehaviour元件裡,並加入一個可以實際改變Snake方向的功能撰寫,讓Input可確實的操控Sanke。
如同預期的,當鍵盤的AWSD按下去時,會有相對應的Input事件產生,並改變Snake的方向性。但這條Snake現階段只能改變方向,不能真正的移動,所以接下來再藉由UniRx滙整事件來達成。
利用F#特有的Discriminated Union(DU)進行事件型別的定義,將改變方向和依時間而做出的變更定義成同一事件,而後藉由Rx的Merge事件功能,將二個相同型別的Event Stream整合成單一事件流,並於接收端進行型別的比對,如果是改變方向事件,則進行改變方向,如果是依時間改變事件,則原之前定義好的移動函式處理。
這裡可以看到UniRx所帶來的方便性,滙整事件後統一進行操作可以省下無謂的程式碼,搭配F#特有的DU讓事件的不需要額外的定義。雖然此階段的程式碼反應出的Snake有一些移動上和寫法上的不妥,但在Unity的Editor Console,已確實的可以看到Snake狀態上有跟著規則做改變。接下來要處理的則是如何在遊戲畫面中看到,而不僅僅只是Console中文字的敍述。
可提出但傾向放於元件端 在繼承MonoBehaviour型別的元件裡,那二個stream其實是可以抽離出來而不用放在元件中,因為以目前的寫法,它也不是該型別的method,而是獨立的function。但在多數的開發下,不論是時間或是輸入控制,都有可能是使用現有的外掛,而多數的外掛寫法是依賴元件拿取值並產生行為,故這樣的寫法在和這些外掛做接合時較為容易。這也反應出元件的使用像是殼層,為了接合而產生的。