UniRx 学习 —— 进阶

参考:

之前一篇博客 UniRx 学习——操作符 总结到最后变成了对 Rx 操作符的复习😂,所以这次有针对性的来学习一下 UniRx 这个框架(主要是官方文档)

网络操作

使用 ObservableWWW (其核心就是对 WWW 进行封装)进行异步网络操作:

  • 支持 GET/POST 方法
  • 支持订阅 WWWstringbyte[]AssetBundle格式的返回
  • 支持进度回调
  • 支持错误处理
  • 支持取消请求
  • 可搭配 Observable.WhenAll 进行并行异步操作

结合协程(Coroutines)

FromCoroutine

将协程转成 Observable

FromCoroutineVlaue

从协程转成 Observable,并发射 yield return 返回的值

FromMicroCoroutine

将微协程转成 Observable

ToYieldInstruction

Observable 转化成协程

StartAsCoroutine

AutoStart observable as coroutine

多线程

Observable.Start() 默认在异步线程运行

默认调度器(DefaultScheduler)

除了 Observable.Start()UniRx 默认基于时间的操作符都使用 Scheduler.MainThread

Scheduler.MainThreadTime.timeScale 的影响,若希望忽略时间缩放可使用 Scheduler.MainThreadIgnoreTimeScale

MonoBehaviour 触发器

UnityGameObject 可以通过添加 UniRx.Triggers 下的触发器组件(ObservableXxxTrigger)来处理事件,如:

1
2
3
4
cube.AddComponent<ObservableUpdateTrigger>()
.UpdateAsObservable()
.SampleFrame(30)
.Subscribe(x => Debug.Log("cube"), () => Debug.Log("destroy"));

UnityGameObject/Component 也可以直接通过扩展方法(***AsObservable)使用 UniRx.Triggers,如:

1
2
3
4
5
6
// All events can subscribe by ***AsObservable
this.OnMouseDownAsObservable()
.SelectMany(_ => this.UpdateAsObservable())
.TakeUntil(this.OnMouseUpAsObservable())
.Select(_ => Input.mousePosition)
.Subscribe(x => Debug.Log(x));

罗列一下 UniRx.Triggers 下支持的触发器(不够用可自定义):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
ObservableAnimatorTrigger    	// 动画:移动、反向运动
ObservableBeginDragTrigger // 开始拖动
ObservableCancelTrigger // 取消
ObservableCanvasGroupChangedTrigger
ObservableCollision2DTrigger // 2D 碰撞:进入、停留、退出
ObservableCollisionTrigger // 碰撞:进入、停留、退出
ObservableDeselectTrigger // 取消选中
ObservableDestroyTrigger // 破坏
ObservableDragTrigger // 拖动
ObservableDropTrigger // 释放
ObservableEnableTrigger // 激活
ObservableEndDragTrigger // 结束拖动
ObservableEventTrigger // 所有事件
ObservableFixedUpdateTrigger // FixedUpdate
ObservableInitializePotentialDragTrigger
ObservableJointTrigger // 物理关节断开
ObservableLateUpdateTrigger // LateUpdate
ObservableMouseTrigger // 鼠标:按下、拖动、进入、悬停、退出、提起
ObservableMoveTrigger // 移动
ObservableParticleTrigger // 粒子:碰撞、触发事件
ObservablePointerClickTrigger // 点击
ObservablePointerDownTrigger // 点击按下
ObservablePointerEnterTrigger // 点击进入
ObservablePointerExitTrigger // 点击结束
ObservablePointerUpTrigger // 点击提起
ObservableRectTransformTrigger // RectTransform:尺寸改变、移除
ObservableScrollTrigger // 滚动:
ObservableSelectTrigger // 选中
ObservableStateMachineTrigger // 状态机:进入、退出;状态:进入、退出、IK、更新
ObservableSubmitTrigger // 提交
ObservableTransformChangedTrigger // Transform:变化
ObservableTrigger2DTrigger // 触发器 2D:进入、停留、退出
ObservableTriggerBase // Base 类
ObservableTriggerExtensions // 扩展方法
ObservableTriggerTrigger // 触发器:进入、停留、退出
ObservableUpdateSelectedTrigger // 更新选中
ObservableUpdateTrigger // Update
ObservableVisibleTrigger // 可见性:可见、不可见

Observable 生命周期管理

使用 IDisposable.AddTo() 管理多个 IDisposable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// CompositeDisposable is similar with List<IDisposable>, manage multiple IDisposable
CompositeDisposable disposables = new CompositeDisposable(); // field

void Start()
{
Observable.EveryUpdate().Subscribe(x => Debug.Log(x)).AddTo(disposables);
}

void OnTriggerEnter(Collider other)
{
// .Clear() => Dispose is called for all inner disposables, and the list is cleared.
// .Dispose() => Dispose is called for all inner disposables, and Dispose is called immediately after additional Adds.
disposables.Clear();
}

如果希望在 GameObject 销毁时,能自动释放订阅,可使用 AddTo(GameObject/Component)

1
2
3
4
void Start()
{
Observable.IntervalFrame(30).Subscribe(x => Debug.Log(x)).AddTo(this);
}

其他可用控制方法:TakeWhile, TakeUntil,TakeUntilDestroy and TakeUntilDisable

1
2
Observable.IntervalFrame(30).TakeUntilDisable(this)
.Subscribe(x => Debug.Log(x), () => Debug.Log("completed!"));

小心使用 Repeat 操作符,避免无限循环订阅,可适当使用 RepeatSafeRepeatUntilDestroy(gameObject/component)RepeatUntilDisable(gameObject/component)

所有类实例都提供了 ObserveEveryValueChanged() 方法,可以在每帧都检查值的变化

转 Unity Callbacks 为 IObservables

流式日志

UniRx.Diagnostics.Logger

调试

需要使用 UniRx.Diagnostics

应用生命周期钩子

  • Observable.EveryApplicationPause()
  • Observable.EveryApplicationFocus()
  • Observable.OnceApplicationQuit()

基于帧数时间的操作符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
EveryUpdate
EveryFixedUpdate
EveryEndOfFrame
EveryGameObjectUpdate
EveryLateUpdate
ObserveOnMainThread
NextFrame
IntervalFrame
TimerFrame
DelayFrame
SampleFrame
ThrottleFrame
ThrottleFirstFrame
TimeoutFrame
DelayFrameSubscription
FrameInterval
FrameTimeInterval
BatchFrame

Every* 方法的执行顺序:

1
2
3
4
EveryGameObjectUpdate(in MainThreadDispatcher's Execution Order) ->
EveryUpdate ->
EveryLateUpdate ->
EveryEndOfFrame

微协程

微协程是内存高效、快速的协程 worker

可见另一篇博客的分析:UniRx —— MicroCoroutine(微协程)

uGUI 集成

使用 UnityEvent.AsObservable 可以方便地处理 Unity 事件

响应式属性 & 响应式集合

非序列化/非编辑器可见

  • ReactiveCollection
  • ReactiveDictionary
  • ReactiveProperty

编辑器可见

  • IntReactiveProperty
  • LongReactiveProperty
  • ByteReactiveProperty
  • FloatReactiveProperty
  • DoubleReactiveProperty
  • StringReactiveProperty
  • BoolReactiveProperty
  • Vector2ReactiveProperty
  • Vector3ReactiveProperty
  • Vector4ReactiveProperty
  • ColorReactiveProperty
  • RectReactiveProperty
  • AnimationCurveReactiveProperty
  • BoundsReactiveProperty
  • QuaternionReactiveProperty

针对多行和范围的响应式属性

可用 MultilineReactivePropertyAttributeRangeReactivePropertyAttribute 来替代 MultilineRange

自定义编辑器可见的响应式属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public enum Fruit
{
Apple, Grape
}

[Serializable]
public class FruitReactiveProperty : ReactiveProperty<Fruit>
{
public FruitReactiveProperty()
{
}

public FruitReactiveProperty(Fruit initialValue)
:base(initialValue)
{
}
}

[UnityEditor.CustomPropertyDrawer(typeof(FruitReactiveProperty))]
[UnityEditor.CustomPropertyDrawer(typeof(YourSpecializedReactiveProperty2))] // and others...
public class ExtendInspectorDisplayDrawer : InspectorDisplayDrawer
{
}

ReadOnlyReactiveProperty

如果一个 ReactiveProperty 的值只会通过一个流更新,可使用 ReadOnlyReactiveProperty

Model-View-(Reactive)Presenter 模式

暂不研究

响应式命令 & 异步响应式命令

ReactiveCommand 抽象了按钮的 interactable 属性

(PS:对象命名很难与实际用法联系在一起,先不研究)

消息代理 & 异步消息代理

MessageBroker:

1
2
3
4
5
// Subscribe message on global-scope.
MessageBroker.Default.Receive<TestArgs>().Subscribe(x => Debug.Log(x));

// Publish message
MessageBroker.Default.Publish(new TestArgs { Value = 1000 });

AsyncMessageBroker(可以处理异步的 Publish 调用):

1
2
3
4
5
6
7
8
9
AsyncMessageBroker.Default.Subscribe<TestArgs>(x =>
{
// show after 3 seconds.
return Observable.Timer(TimeSpan.FromSeconds(3)).ForEachAsync(_ => { Debug.Log(x); });
});

AsyncMessageBroker.Default
.PublishAsync(new TestArgs {Value = 3000})
.Subscribe(_ => Debug.Log("called all subscriber completed"));

UniRx.Toolkit

对象池 ObjectPoolAsyncObjectPool

UniRx.Async

UniRx.Async is the custom async/await support utilities.