UniRx 初体验
链式编程
所谓链式编程——使用. . . 像是链条一样进行编程。
:bookmark: 下面是一个延迟调用的示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class UniRxTest : MonoBehaviour { private void Start () { Debug.Log("开始时间:" +Time.realtimeSinceStartup); Observable.Timer(TimeSpan.FromSeconds(2 )).Subscribe((x) => { Debug.Log("现在时间:" + Time.realtimeSinceStartup); }).AddTo(this ); Observable.EveryUpdate().Where(_ => Input.GetKeyDown(KeyCode.A)).Subscribe(_ => { Debug.Log("点击了" ); }).AddTo(this ); } }
Ui数据绑定
原版本不支持绑定TextMeshPro
UI数据绑定 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 39 40 41 42 43 using System;using UniRx;using UnityEngine;using UnityEngine.UI;public class RxBingData : MonoBehaviour { public Text titleTex; public Button clBtn; public IModel model; private void Awake () { model = new GameData(); model.Name.SubscribeToText(titleTex); clBtn.OnClickAsObservable().Subscribe(_ => { model.Name.Value = "以分行诉分行诉客户方" ; }); Observable.EveryUpdate().Where(_ => Input.GetKeyDown(KeyCode.A)).Subscribe(_ => { model.Name.Value = "1111" ; }).AddTo(this ); } } public interface IModel { StringReactiveProperty Name { get ; } } public class GameData : IModel { public StringReactiveProperty Name { get ; } = new StringReactiveProperty("校长" ); }
绑定TextMeshPro
通过扩展方法增加支持
1 2 3 4 5 6 7 8 9 10 11 12 public static class UnityUiExtensions { public static IDisposable SubscribeToText (this IObservable<string > source, TextMeshProUGUI text ) { return source.SubscribeWithState(text, (x, t) => t.text = x); } public static IDisposable SubscribeToText <T >(this IObservable<T> source, TextMeshProUGUI text ) { return source.SubscribeWithState(text, (x, t) => t.text = x.ToString()); } }
绑定ScripTableObject
在此我开始思考是否可以绑定ScripTableObject?:thought_balloon:
结果是:YES
Asset设定 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 [CreateAssetMenu ] public class GameSetting : ScriptableObject ,IAudioSetting { public StringReactiveProperty ProfileName => profileName; public FloatReactiveProperty BgmVolume => bgmVolume; public StringReactiveProperty profileName; public FloatReactiveProperty bgmVolume; } public interface IAudioSetting { StringReactiveProperty ProfileName { get ; } FloatReactiveProperty BgmVolume { get ; } }
bing数据 1 2 3 4 5 6 7 8 9 10 11 12 13 gameSetting.profileName.SubscribeToText(title); gameSetting.bgmVolume.SubscribeToText(titleTex); clBtn.OnClickAsObservable().Subscribe(_ => { gameSetting.profileName.Value = "clicked Data" ; }); Observable.EveryUpdate().Where(_ => Input.GetKeyDown(KeyCode.A)).Subscribe(_ => { gameSetting.profileName.Value = "keyDown A" ; }).AddTo(this );
数据实时绑定
:label:通过这种方式很快捷方便的就实现了数据的绑定,同时没有污染数据接口
Subject 和OnNext
++Subject既可以作为观察者也可以作为被观察对象++
1 2 3 4 5 6 7 8 9 private void Start (){ var subject = new Subject<string >(); subject.Subscribe(m => print($"接收到:{m} " )); subject.OnNext("你好" ); }
Subject实现了ISubject接口,其中ISubject接口声明如下:
1 2 3 4 5 6 7 public interface ISubject <TSource , TResult > : IObserver <TSource >, IObservable <TResult >{ } public interface ISubject <T > : ISubject <T , T >, IObserver <T >, IObservable <T >{ }
在其中实现了IObserver接口,这是一个微软官方的观察者模式的实现接口,可参考参考资料2
IObserver 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 namespace System { public interface IObserver <in T > { void OnNext (T value ) ; void OnError (Exception error ) ; void OnCompleted () ; } }
事件的过滤和筛选 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 private void Start (){ var subject = new Subject<string >(); subject.Subscribe(m => print($"接收到:{m} " )); subject.Subscribe(m => print("处理方式二|" + m)); subject.Where(x=>x=="你好" ).Subscribe(m => print($"接收到特殊:{m} " )); subject.OnNext("你好" ); subject.OnNext("1" ); subject.OnNext("2" ); subject.OnNext("3" ); }
subject.Where(x=>x=="你好").Subscribe(m => print($"接收到特殊:{m}"));使用Where操作符对输入参数进行了过滤,让其只处理特定的输入信息 :satisfied: 。
:book:PS: 可以定义自定义的过滤运算符来指定过滤操作。
UniRX 流 调用OnError、OnCompleted将会终止流,并销毁流对象。(后续监听被终止)
1 2 3 4 5 6 7 8 9 10 11 12 var subject = new Subject<string >();subject.Subscribe(m => print($"接收到:{m} " )); subject.OnNext("你好" ); subject.OnNext("1" ); subject.OnCompleted(); subject.OnNext("2" ); subject.OnNext("3" );
空参数
需要使用空参数的情况可以如下操作:
1 2 3 4 var sub = new Subject<Unit>();sub.Subscribe(_ => print("触发事件" )); sub.OnNext(Unit.Default);
:bookmark:对于不再使用的流要及时OnCompleted获得Dispose来释放内存以免出现空引用或内存泄漏。
:bookmark:可以使用AddTo()将流的生命周期绑定到指定对象。对象被销毁,流也会被销毁。
流的来源
官方提供了创建流的几种方式:
Subject
ReactiveProperty
ReactiveCollection
ReactiveDictionary
UniRx提供的方法
UniRx.Trigger
UniRx的协程
UniRx转换后的UGUI事件
ReactiveCollection ReactiveCollection与ReactiveProperty类似,++它是内置了一个通知状态变化功能的List++,ReactiveCollection可以像List一样使用,更棒的是,ReactiveCollection可以用来对List状态的变化进行Subscribe,所支持的状态变化订阅如下:
添加元素时
删除元素时
集合数量变化时
集合元素变化时
元素移动时
清除集合时
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 var list = new ReactiveCollection<string >(){"a" ,"b" ,"c" };list.ObserveAdd().Subscribe(x => { print($"新增元素:{x} " ); }); list.ObserveRemove().Subscribe(x => { print($"删除元素:{x} " ); }); list.ObserveMove().Subscribe(x => { print($"移动元素:{x} " ); }); list.ObserveReset().Subscribe(_ => { print($"重制" ); }); list.Add("AA" ); list.RemoveAt(0 ); list.Clear();
UniRx 的工厂方法 UniRx.Trigger系列 1 2 3 4 5 6 7 8 9 10 11 private void Start (){ this .OnTriggerEnterAsObservable().Subscribe(x => { print($"{x.gameObject.name} 进入触发区域" ); }); this .OnTriggerExitAsObservable().Subscribe(x => { print($"{x.gameObject.name} 离开触发区域" ); }); }
在Player中的应用 1 2 3 4 5 6 7 8 9 10 11 private void Start (){ this .UpdateAsObservable().Where(_ => Input.GetKeyDown(KeyCode.A)).ThrottleFirst(TimeSpan.FromSeconds(2f )) .Subscribe(_ => Attack()); } public void Attack (){ print("Attack" ); }
使用Unirx设计PlayerController 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 39 40 41 42 43 44 45 46 47 48 49 50 using UnityEngine;using UniRx.Triggers;using UniRx;public class TestUniRX : MonoBehaviour { private CharacterController characterController; private BoolReactiveProperty isJumping = new BoolReactiveProperty(); void Start () { characterController = GetComponent<CharacterController>(); this .UpdateAsObservable() .Where(_ => !isJumping.Value) .Select(_ => new Vector3(Input.GetAxis("Horizontal" ), 0 , Input.GetAxis("Vertical" ))) .Where(x => x.magnitude > 0.1f ) .Subscribe(x => Move(x.normalized)); this .UpdateAsObservable() .Where(_ => Input.GetKeyDown(KeyCode.Space) && !isJumping.Value && characterController.isGrounded) .Subscribe(_ => { Jump(); isJumping.Value = true ; }); characterController .ObserveEveryValueChanged(x => x.isGrounded) .Where(x => x && isJumping.Value) .Subscribe(_ => isJumping.Value = false ) .AddTo(gameObject); isJumping.Where(x => !x) .Subscribe(_ => PlaySoundEffect()); } private void PlaySoundEffect () { Debug.Log("播放音效" ); } private void Jump () { Debug.Log("Jump" ); } private void Move (Vector3 normalized ) { Debug.Log("Move" ); } }
针对协程流
UniRx 提供一种微协程的协程实现方式,性能比原生协程更佳。
串联协程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 void Start (){ Observable.FromCoroutine(CoroutineA) .SelectMany(CoroutineB) .Subscribe(_=>Debug.Log("CoroutineA 和CoroutineB 执行完成" )); } IEnumerator CoroutineA () { Debug.Log("CoroutineA 开始" ); yield return new WaitForSeconds (3 ) ; Debug.Log("CoroutineA 完成" ); } IEnumerator CoroutineB () { Debug.Log("CoroutineB 开始" ); yield return new WaitForSeconds (10 ) ; Debug.Log("CoroutineB 完成" ); }
并联协程汇总再处理 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 void Start () { Observable.WhenAll( Observable.FromCoroutine<string >(o => CoroutineA(o)), Observable.FromCoroutine<string >(o => CoroutineB(o)) ).Subscribe(xs => { foreach (var item in xs) { Debug.Log("result:" + item); } }); } IEnumerator CoroutineA (IObserver<string > observer ) { Debug.Log("CoroutineA 开始" ); yield return new WaitForSeconds (3 ) ; observer.OnNext("协程A 执行完成" ); Debug.Log("A 3秒等待结束" ); observer.OnCompleted(); } IEnumerator CoroutineB (IObserver<string > observer ) { Debug.Log("CoroutineB 开始" ); yield return new WaitForSeconds (1 ) ; observer.OnNext("协程B 执行完成" ); Debug.Log("B 1秒等待结束" ); observer.OnCompleted(); }
其他使用 延迟和定时 1 2 3 4 5 Observable.Timer(TimeSpan.FromSeconds(1 )).Subscribe((x) => { print("执行" ); }).AddTo(this ); Observable.Interval(TimeSpan.FromSeconds(1 )).Subscribe(_ => { print("11" ); }).AddTo(this );
个人总结
unirx提供了一种响应式的流编程模式,我觉得UniRx最强之处是以下几点:
万事万物皆可以为流
提供一种微协程方式,线程回溯
观察者模式——数据绑定和监听(可用于UI)
装饰者模式——对操作和流进行灵活裁剪控制(Linq)
++:bookmark: UniRx是非常优秀的Unity框架!在之后我会在项目中尝试去使用它。++
参考资料
UniRx入门系列一 · 开发文档集合 (ronpad.com)
IObserver 接口 (System) | Microsoft Learn