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 秒的时间,就搞定了一个实现起来比较麻烦的逻辑。
今天的内容就这些。