UniRx 是一個 Unity3D 的編程框架。它專一於解決時間上異步的邏輯,使得異步邏輯的實現更加簡潔和優雅。git
好比,實現一個「只處理第一次鼠標點擊事件」這個功能,使用 UniRx 實現以下:github
Observable.EveryUpdate() .Where(_ => Input.GetMouseButtonUp(0)) .First() .Subscribe(_ => { // do something });
代碼作的事情很簡單:編程
若是在 Unity 中,使用傳統的方式實現如上功能,首先要建立一個成員變量來記錄點擊次數/是否點擊過,而後在腳本中建立一個 Update 方法來監聽鼠標擡起的事件。設計模式
若是在 Update 方法中,除了實現鼠標事件監聽這個功能以外,還要實現其餘的功能。那麼 Update 裏就會充斥着大量的狀態判斷等邏輯。代碼很是不容易閱讀。網絡
而 UniRx 提供了一種編程思惟,使得平時一些比較難以實現的異步邏輯(好比以上這種),使用 UniRx 輕鬆搞定,而且不失代碼的可讀性。架構
固然 UniRx 的強大不只僅如此。框架
它還能夠:異步
最最重要的是,它能夠提升咱們的編碼效率,同時還給咱們的大腦提供一個強有力的編程模型。而 UniRx 自己的源碼很是值得研究學習,就連大名鼎鼎的 uFrame 框架,在 1.6 版本以後,使用 UniRx 進行了大幅重構,其事件/數據綁定層使用 UniRx 強力驅動。學習
筆者的 QFramework 也一樣引入了 UniRx,有一大批的框架用戶都是由於支持了 UniRx 慕名而來。動畫
UniRx 就是 Unity 版本的 Reactive Extensions,Reactive Extensions 中文意思是:響應式擴展,響應式指的是觀察者和定時器,擴展指的是 LINQ 的操做符。Reactive Extensions 以擅長處理時間上異步的邏輯、以及極簡的 API 風格而聞名。
咱們都知道,遊戲不少的系統都是在時間上異步的,因此 Unity 開發者所須要實現的異步邏輯是很是多的。這也是爲何 Unity 官方在引擎層實現了 Coroutine(協程)這樣的概念。
在遊戲中,像動畫的播放、聲音的播放、網絡請求、資源加載/卸載、Tween 動畫、場景過渡等都是在時間上異步的邏輯。甚至是遊戲循環(Every Update、OnCollisionEnter 等)、傳感器數據(Kinect、Leap Motion、VR Input 等)都是時間上異步的邏輯(事件)。
當咱們在項目中實現以上的邏輯的時候,每每使用的方式是用大量的回調實現,最終隨着項目的擴張會致使傳說中的」回調地獄」。
相對較好的方法則是使用消息/事件進行實現,結果致使「消息滿天飛」,致使代碼很是難以閱讀。
以上的任務使用 Coroutine 也是很是不錯的,可是 Coroutine 在 Unity 使用的時候,須要定義一個方法。寫起來是很是面向過程的。當邏輯稍微複雜一點,就很容易形成 Coroutine 嵌套 Coroutine,代碼是很是不容易閱讀的(強耦合)。
而 UniRx 的出現恰好解決了這個問題,它介於回調和事件之間。
它有事件的概念,只不過它的事件是像流水同樣流過來,而咱們要作的則是簡單地對這些事件進行組織、變換、過濾、合併就能夠獲得咱們想要的結果了。
它也用掉了回調,只不過它的回調是在事件通過組織以後,只須要調用一次就能夠進行事件處理了。
它的原理和 Coroutine (迭代器模式)、LINQ 很是類似,可是比 Coroutine 強大得多。
UniRx 將時間上異步的事件轉化爲響應式的事件序列,經過 LINQ操做能夠很簡單地組合起來。
爲何要用 UniRx? 答案就是遊戲自己有大量的在時間上異步的邏輯,而 UniRx 剛好擅長處理這類邏輯,使用 UniRx 能夠節省咱們的時間,同時讓代碼更簡潔易讀。
Rx 只是一套標準,其餘的語言也有實現,若是在 Unity 中熟悉了這套標準,那麼在將來,你們在作別的語言的項目的時候,很容易得到 Rx 的能力。
OK,讓咱們從下一篇開始,感覺一下 UniRx 的魅力吧。
在 Unity 開發中,延時是咱們常常要實現的功能,這個功能對於有點經驗的開發者來講不難。
最多見的實現方式以下:
using UnityEngine; public class CommonDelayExample : MonoBehaviour { private float mStartTime; void Start() { mStartTime = Time.time; } void Update() { if (Time.time - mStartTime > 5) { DoSomething(); // 避免再次執行 mStartTime = float.MaxValue; } } void DoSomething() { Debug.Log("DoSomething"); } }
這是不少初學者剛入門時候的實現方式,而初學者們隨着深刻學習後來接觸到了 Coroutine(協程),使用 Coroutine 實現定時功能會更容易,並且也是更好的選擇,實現以下:
using System; using System.Collections; using UnityEngine; public class CoroutineDelayExample : MonoBehaviour { void Start() { StartCoroutine(Timer(5, DoSomething)); } IEnumerator Timer(float seconds, Action callback) { yield return new WaitForSeconds(seconds); callback(); } void DoSomething() { Debug.Log("DoSomething"); } }
協程已經把代碼精簡了不少,不過接下來有更厲害的,使用 UniRx。
代碼以下:
Observable.Timer(TimeSpan.FromSeconds(5)).Subscribe(_ => { /* do something */ });
固然以上代碼是沒有和 MonoBehaviour 進行生命週期綁定的,也就是說當 MonoBehaviour 銷燬了以後,這個定時邏輯可能還在執行,這樣就會有形成空指針異常的風險。
要解決也很簡單,代碼以下:
Observable.Timer(TimeSpan.FromSeconds(5)) .Subscribe(_ => { /* do something */ }) .AddTo(this);
只要加上一個 AddTo(this) 就能夠了。
這樣,當 this(MonoBehaviour) Destroy 的時候,這個延時邏輯也會銷燬掉,從而避免形成空指針異常。
三行代碼,寫下來大約 20 秒的時間,就搞定了一個實現起來比較麻煩的邏輯。
今天的內容就這些。