1.UniRx 簡介
UniRx 是一個 Unity3D 的程式設計框架。它專注於解決時間上非同步的邏輯,使得非同步邏輯的實現更加簡潔和優雅。
簡潔優雅如何體現?
比如,實現一個“只處理第一次滑鼠點選事件”這個功能,使用 UniRx 實現如下:
Observable.EveryUpdate()
.Where(_ => Input.GetMouseButtonUp(0))
.First()
.Subscribe(_ => { // do something });
程式碼做的事情很簡單:
- 開啟一個 Update 的事件監聽。
- 每次 Update 事件被呼叫時,進行滑鼠是否抬起的判斷。
- 如果判斷通過,則進行計數,並且只獲取第一次點選事件。
- 訂閱/處理事件。
如果在 Unity 中,使用傳統的方式實現如上功能,首先要建立一個成員變數來記錄點選次數/是否點選過,然後在指令碼中建立一個 Update 方法來監聽滑鼠抬起的事件。
如果在 Update 方法中,除了實現滑鼠事件監聽這個功能之外,還要實現其他的功能。那麼 Update 裡就會充斥著大量的狀態判斷等邏輯。程式碼非常不容易閱讀。
而 UniRx 提供了一種程式設計思維,使得平時一些比較難以實現的非同步邏輯(比如以上這種),使用 UniRx 輕鬆搞定,並且不失程式碼的可讀性。
當然 UniRx 的強大不僅僅如此。
它還可以:
- 優雅實現 MVP(MVC)架構模式。
- 對 UGUI/Unity API 提供了增強,很多需要寫大量程式碼的 UI 邏輯,使用 UniRx 優雅實現。
- 輕鬆完成非常複雜的非同步任務處理。
- ......
最最重要的是,它可以提高我們的編碼效率,同時還給我們的大腦提供一個強有力的程式設計模型。而 UniRx 本身的原始碼非常值得研究學習,就連大名鼎鼎的 uFrame 框架,在 1.6 版本之後,使用 UniRx 進行了大幅重構,其事件/資料繫結層使用 UniRx 強力驅動。
筆者的 QFramework 也同樣引入了 UniRx,有一大批的框架使用者都是因為支援了 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 的能力。
專欄內容:
- 實踐並講解開發中最常用的 UniRx API。
- 對 UniRx 進行一個全方面的簡介。
- 在每個階段結束後就會結合所學的知識進行專案實踐。
- UniRx 操作符大全。
- UniRx 原始碼閱讀。
- UniRx 背後的設計原理及設計模式學習。
- LINQ、Coroutine 底層原理剖析。
- BindingsRx、uFrame 原始碼閱讀。
OK,讓我們從下一篇開始,感受一下 UniRx 的魅力吧。
知識地圖
2.定時功能實現
在 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 秒的時間,就搞定了一個實現起來比較麻煩的邏輯。
今天的內容就這些。
知識地圖
更多內容
- QFramework 地址:https://github.com/liangxiegame/QFramework
- QQ 交流群:623597263
- 涼鞋的主頁:https://liangxiegame.com/zhuanlan
- 關注公眾號:liangxiegame 獲取第一時間更新通知及更多的免費內容。