上一篇文章中,我们介绍了OnNext、OnError、OnCompleted和IDisposable的用途,还介绍了如何管理流的生命周期,下面我将介绍一下流的来源。
UniRX中的流由以下三个部分组成:
可以使用UniRx提供的流源,也可以自己实现自己需要的流源。如果你想使用UniRx提供的流源,那么你可以通过以下几种方式来创建。
Subject 在之前已经出现过很多次了。但他是使用这个Subject系列的基础。如果你想创建一个你自己的流,并发布一些消息,你可以继续使用Subject。对应的,Subject有一些衍生的用法,它们有各自的用法;最好是依据不同的用途来选择合适的用法,下面做一个简单的说明:
AsyncSubjecr 在没有执行OnNext的情况下,向内部缓存值;并在执行OnCompleted时,只发布最后一个OnNext的值。AsyncSubject和Future和Promise一样。如果你想在结果出来的时候获取它,你可以使用异步的方式来处理它。
ReactivePropert
是为一个普通变量添加一些Subject的功能(具体的实现过程也是这样的),我们可以像定义变量那样来定义和使用它,如下:
void Start(){
var rp = new ReactiveProperty<int>(10);
rp.Value=20;
var currentVlue=rp.Value;
rp.Subscribe(x=>Debug.Log(x));
rp.Value=30;
}
输出如下:
20
30
另外,ReactiveProperty可以像Unity中的publish变量一样,显示在Inspector面板中;当然,要这样做的话,你应该使用为不同类型定义的Reactiveproperty而不是使用ReactiveProperty的泛型版本。另外,我们将在下一节中详解ReactiveProperty在mv®p模式中的价值,所以,务必提前掌握好ReactiveProperty。
public class TestReactiveProperty : MonoBehaviour {
private IntReactiveProperty playerHealyh=new IntReactiveProperty(100);
void Start () {
playerHealyh.Subscribe(x=>Debug.Log(x));
}
}
ReactiveCollection与ReactiveProperty类似,它是内置了一个通知状态变化功能的List,ReactiveCollection可以像List一样使用,更棒的是,ReactiveCollection可以用来对List状态的变化进行Subscribe,所支持的状态变化订阅如下:
void Start () {
var collection = new ReactiveCollection<string> ();
collection.ObserveAdd ()
.Subscribe (x => {
Debug.Log (string.Format ("Add {0}={1}", x.Index, x.Value));
});
collection.ObserveRemove ()
.Subscribe (x => {
Debug.Log (string.Format ("Remove {0}={1}", x.Index, x.Value));
});
collection.Add ("Apple");
collection.Add ("Baseball");
collection.Add ("Cherry");
collection.Remove ("Apple");
}
输出如下:
Add [0] = Apple
Add [1] = Baseball
Add [2] = Cherry
Remove [0] = Apple
这是Dictionary的Reactive版,他的行为几乎和ReactiveCollection一样,参考如上。
UniRX为构建流源提供了一系列的工厂方法。这时,我们可以很容易的创建一些复杂的流,仅仅通过Subject时无法实现的。如果你在Unity中使用UniRx,你可能不会很频繁的使用到UniRx提供的工厂方法,但是在某些地方,它是必须的。由于这一类方法较多,我们提取几个使用比较频繁的介绍一下。
Obseervable.Create是一个静态方法,你可以自由的创建一个发布值的流。例如,在一些程序中,将处理调用规则的细节隐藏在这个方法中,使用这个方法,只在流中检索结果值。Observable.Create接收一个参数Func
void Start () {
Observable.Create<int> (observer => {
Debug.Log ("Start");
for (int i = 0; i < 100; i += 10) {
observer.OnNext (i);
}
Debug.Log ("Finished");
observer.OnCompleted ();
return Disposable.Create (() => {
Debug.Log ("Dispose");
});
}).Subscribe (x => Debug.Log (x));
}
执行输出如下:
Start
0
10
20
30
40
50
60
70
80
90
100
Finished
Disposable
Observable.Start是一个工厂方法,在不同的线程上运行给定的块,并且只发布一个结果值。你可以使用这个方法异步执行一些操作,然后在你希望获得结果通知时使用它们。
void Start () {
Observable.Start (() => {
var req = (HttpWebRequest) WebRequest.Create ("https://www.baidu.com");
var res = (HttpWebResponse) req.GetResponse ();
using (var reader = new StreamReader (res.GetResponseStream ())) {
return reader.ReadToEnd ();
}
}).ObserveOnMainThread ()
.Subscribe (x => Debug.Log (x));
}
注意,Observable.Start时在另外一个线程上执行,然后直接在该线程中执行Subscribe,所以,在非线程安全的Unity中会出现问题。所以我们需要将消息从当前线程传递到主线程中,请使用ObserveOnMainThread操作符。使用这个操作符之后,他将转换到Unity的主线程上运行。
Observable.Timer是在一定时间后发布消息的一个工厂方法。如果使用真实时间,请使用Observable.Timer方法;如果使用Unity的帧数制定,阿么使用TimeFrame方法。Timer和TimeFrame的行为根据你指定参数的不同而不同。如果你只指定一个参数,那么,你会以一个OneShot动作结束;如果你指定了两个参数。你会定期发布信息(间隔时间);你还可以通过指定调度器来指定其运行在指定的线程上。另外,类似,还存在Observable.Interval/IntervalFrame方法。
void Start () {
Observable.Timer (TimeSpan.FromSeconds (5))
.Subscribe (_ => Debug.Log ("流失了5秒"));
Observable.Timer (TimeSpan.FromSeconds (5), TimeSpan.FromSeconds (1))
.Subscribe (_ => Debug.Log ("5秒之后,每间隔1妙发布一次"))
.AddTo (gameObject);
}
如果使用Timer TimerFrame定期执行的话,一定要记得Dispose,如果你已经不需要定期执行的这项操作了,但是你依然没有将其Dispose,就会导致内存泄漏或者NullReferenceException.
要使用UniRx.Trigger,先导入using UniRx.Trigger。其将Unity的回调函数转化为UniRx中的IObservable,可以用操作UniRx的方式来操作Unity回调函数。因为Triggers数量众多,这里不做过多介绍,请参考官方文档。由于Unity提供的大多数回调函数都可以被当做流来获取,而且当GameObject被发布时,流会自动发布,所以这里不同担心流的生命周期。
void Start () {
var isForceEnabled = true;
var rigidBody = GetComponent<Rigidbody> ();
this.FixedUpdateAsObservable ()
.Where (_ => isForceEnabled)
.Subscribe (_ => rigidBody.AddForce (Vector3.up));
this.OnTriggerEnterAsObservable ()
.Where (x => x.gameObject.tag == "WarpZone")
.Subscribe (_ => isForceEnabled = true);
this.OnTriggerExitAsObservable ()
.Where (x => x.gameObject.tag == "WarpZone")
.Subscribe (_ => isForceEnabled = false);
}
使用Triggers将Unity的回调函数变成了一个流,那么把所有的事件处理都汇总到Awake/Start中就成了可能,下一节中我们再详述。
事实上,Unity中的协程和UniRx的可转化型时比较好的,UniRx提供了一些方法,使得IObservable和Unity协程的转化变得相当容易。如果你想从Unity协程转化为IObservable,你可以利用Observable.Fromecoroutine来实现。某些情况下,与其通过操作链中复杂的操作符来构建复杂的流,不如使用协程来实现,这种方式实现更简单、容易;下一节中,我们会详细的介绍UniRx和Unity中协程的结合,所以,只在这里做简单的介绍。
public class Timer : MonoBehaviour {
public bool IsPaused { get; private set; }
void Start () {
Observable.FromCoroutine<int>
(observer => GameTimerCoroutine (observer, 60))
.Subscribe (t => Debug.Log (t));
}
private IEnumerator GameTimerCoroutine(IObserver<int> observer, int initialCount)
{
var current=initialCount;
while(current>0){
if (!IsPaused){
observer.OnNext(current--);
}
yield return new WaitForSeconds(1);
}
observer.OnNext(0);
observer.OnCompleted();
}
}
UniRx为UGUI提供了独特的实现,结合之前说过的ReactiveProprtty,可以非常便捷的描述View(视图)和Model(模型)之间的关系。本节暂时不介绍mv®p模式,只介绍UGUI事件到UniRx的转换,如下所示:
void Start()
{
var button=GetComponent<Button>();
button.OnClickAsObservable()
.Subscribe(x=>Debug.Log("点击了当前按钮"));
var inputField=GetComponent<InputField>();
inputField.OnValueChangedAsObservable().Subscribe(x=>Debug.Log(x));
inputField.OnEndEditAsObservable().Subscribe(x=>Debug.Log(x));
var slider=GetComponent<Slider>();
slider.OnValueChangedAsObservable().Subscribe(x=>Debug.Log(x));
inputField.onValueChanged.AsObservable().Subscribe();
inputField.OnValueChangedAsObservable();
inputField.onValueChanged.AsObservable();
}
Observable.EveryUpdate 以 Time.deltatime 的时间间隔更新流。之前说 UniRx.Triggers和UpdateAsObservabel 的行为与 GameObject 相关联,当对象被 Destroy 时发布 OnCompleted。但 Observable.EvenryUpdate 的行为却不与 GameObject 的行为相关联,当 GameObject 对象被销毁时, Observable.EveryUpdate 并不会停止,除非你手动释放。
ObserveEveryValueChanged 在流源中是一个比较特殊的存在,它被定义为类(class)的扩展方法。通过使用这个方法,你可以在每一帧中监控任何对象的参数,并创建一个变化发生时的通知流。
void Start()
{
var characterController = GetComponent<CharacterController>();
characterController
.ObserveEveryValueChanged(character => character.isGrounded)
.Where(x => x)
.Subscribe(_ => Debug.Log("落地"))
.AddTo(this.gameObject);
Observable.EveryUpdate()
.Select(_ => characterController.isGrounded)
.DistinctUntilChanged()
.Where(x => x)
.Subscribe(_ => Debug.Log("落地"))
.AddTo(this.gameObject);
}
总结:
使用多种方法创建流
个人,开发中会经常的UniRx.Trigger、ReactiveProperty、UGUI变换,把这些概念理解透彻,会大大提升开发效率。
更多内容,欢迎关注: