随风逐叶 随风逐叶
首页
  • Quick Reference (opens new window)
  • EgretEngine开发者文档 (opens new window)
  • TinaX框架
  • SSH教程
  • VSCode插件开发
关于
  • 分类
  • 标签
  • 归档

rontian

从事游戏开发10多年的老菜鸟一枚!
首页
  • Quick Reference (opens new window)
  • EgretEngine开发者文档 (opens new window)
  • TinaX框架
  • SSH教程
  • VSCode插件开发
关于
  • 分类
  • 标签
  • 归档
  • 框架简介
  • TinaX.Core
  • 基于TinaX创建一个扩展库
  • TinaX.VFS
  • TinaX.UIKit
  • TinaX.I18N
  • TinaX.Lua
  • XLua

  • Google.Protobuf
  • Lua-Protobuf
  • 一些优秀的第三方库

    • CatLib

    • UniRx

      • 简介
      • 基础教程
      • 官方入门文档
      • UniRx入门系列一
      • UniRx入门系列二
      • UniRx入门系列三
        • UniRx入门系列四
        • UniRx入门系列五
      • UniTask

    目录

    UniRx入门系列三

    # UniRx入门系列三

    # 上节回顾


    上一篇文章中,我们介绍了OnNext、OnError、OnCompleted和IDisposable的用途,还介绍了如何管理流的生命周期,下面我将介绍一下流的来源。

    # 什么是流的来源(消息的发布者)


    UniRX中的流由以下三个部分组成:

    • 发布消息的源(如Subject)
    • 传递消息的操作符(Where、Select等等)
    • 消息的订阅(Subscribe) 刚开始接触UniRx的人,大多会有些疑惑,流是如何创建的。接下来我们将讨论如何创建一个流。

    # 可能作为流源的一些东西


    可以使用UniRx提供的流源,也可以自己实现自己需要的流源。如果你想使用UniRx提供的流源,那么你可以通过以下几种方式来创建。


    • 使用Subject系列
    • 使用ReactiveProperty
    • UniRx提供的方法
    • 使用UniRx.Trigger系列
    • 使用UniRx提供的协程
    • 使用UniRx转换后的UGUI事件 我们逐一解释一下。

    # Subject系列


    Subject 在之前已经出现过很多次了。但他是使用这个Subject系列的基础。如果你想创建一个你自己的流,并发布一些消息,你可以继续使用Subject。对应的,Subject有一些衍生的用法,它们有各自的用法;最好是依据不同的用途来选择合适的用法,下面做一个简单的说明:

    • Subject 最基础的一项,OnNext执行后,发布对应的值。
    • BehaviorSubject 缓存最后发布的值,执行到Subscribe时,发布当前值,也可以设置初始值
    • 缓存之前发布过的所有的值,当Subscribe时,将缓存的值汇总并发布

    AsyncSubjecr 在没有执行OnNext的情况下,向内部缓存值;并在执行OnCompleted时,只发布最后一个OnNext的值。AsyncSubject和Future和Promise一样。如果你想在结果出来的时候获取它,你可以使用异步的方式来处理它。

    # ReacriveProperty系列


    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; 
    }
    
    1
    2
    3
    4
    5
    6
    7

    输出如下:

    20
    30
    
    1
    2

    另外,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));
    	}
    }
    
    1
    2
    3
    4
    5
    6
    7
    8

    # ReactiveCollection


    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");
    	}
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17

    输出如下:

    Add [0] = Apple
    Add [1] = Baseball
    Add [2] = Cherry
    Remove [0] = Apple
    
    1
    2
    3
    4

    # ReactiveDictionary<T1,T2>


    这是Dictionary的Reactive版,他的行为几乎和ReactiveCollection一样,参考如上。

    # UniRx的工厂方法


    UniRX为构建流源提供了一系列的工厂方法。这时,我们可以很容易的创建一些复杂的流,仅仅通过Subject时无法实现的。如果你在Unity中使用UniRx,你可能不会很频繁的使用到UniRx提供的工厂方法,但是在某些地方,它是必须的。由于这一类方法较多,我们提取几个使用比较频繁的介绍一下。

    # Observable.Create

    Obseervable.Create是一个静态方法,你可以自由的创建一个发布值的流。例如,在一些程序中,将处理调用规则的细节隐藏在这个方法中,使用这个方法,只在流中检索结果值。Observable.Create接收一个参数Func<IObserver, IDisposable> subscribe,返回IObservable,使用如下:

    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));
    	}
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    执行输出如下:

    Start
    0
    10
    20
    30
    40
    50
    60
    70
    80
    90
    100
    Finished
    Disposable
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    # Observable.Start

    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));
    	}
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    注意,Observable.Start时在另外一个线程上执行,然后直接在该线程中执行Subscribe,所以,在非线程安全的Unity中会出现问题。所以我们需要将消息从当前线程传递到主线程中,请使用ObserveOnMainThread操作符。使用这个操作符之后,他将转换到Unity的主线程上运行。

    # Observable.Timer/TimeFrame


    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);
    	}
    
    1
    2
    3
    4
    5
    6
    7
    8

    如果使用Timer TimerFrame定期执行的话,一定要记得Dispose,如果你已经不需要定期执行的这项操作了,但是你依然没有将其Dispose,就会导致内存泄漏或者NullReferenceException.

    # UniRx.Trigger系列


    要使用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);
    	}
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    使用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();
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22

    # UGUI事件转换

    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();
       }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17

    # Observable.EvenryUpdate

    Observable.EveryUpdate 以 Time.deltatime 的时间间隔更新流。之前说 UniRx.Triggers和UpdateAsObservabel 的行为与 GameObject 相关联,当对象被 Destroy 时发布 OnCompleted。但 Observable.EvenryUpdate 的行为却不与 GameObject 的行为相关联,当 GameObject 对象被销毁时, Observable.EveryUpdate 并不会停止,除非你手动释放。

    # ObserveEveryValueChanged

    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);
        }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17

    总结:


    使用多种方法创建流

    • Subject 使用Subject的一系列方法
    • ReactiveProperty 使用ReactiveProperty系列
    • 使用UniRx.Trigger 转化的Unity回调方法
    • 使用UniRx转化Unity协程
    • 使用UniRx转化的UGUI事件

    个人,开发中会经常的UniRx.Trigger、ReactiveProperty、UGUI变换,把这些概念理解透彻,会大大提升开发效率。

    上次更新: 2023/10/17, 14:09:52 访问次数: 0
    UniRx入门系列二
    UniRx入门系列四

    ← UniRx入门系列二 UniRx入门系列四→

    最近更新
    01
    一些Shell常用的功能写法整理
    10-20
    02
    删除git仓库submodule的步骤
    10-20
    03
    django基本命令
    10-16
    更多文章>
    Copyright © 2017-2025 随风逐叶
    沪ICP备18008791号-1 | 沪公网安备31011502401077号

    网站访问总次数: 0次
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式