Unity之UniRX

UniRX好好用,我在这里做一个记录。

一、Stream流的思想

使用UniRx,就要使用响应式编程(Reactive Programming)的思想。那么响应式编程的思想是什么呢。可以类比面向对象的编程(OOP)——万物皆对象,响应式编程的思想是——万物皆可流。即什么东西都可以当做流来思考。具体可以参考https://zhuanlan.zhihu.com/p/27678951  这篇文章,里面讲了一下流的概念。

我举个例子。现在有一条以时间为横轴的坐标轴。我们鼠标的点击事件就记录在这个坐标轴上。我鼠标在时刻a,点了一下正键,那么就在坐标的时刻a处,记录一下。假如是我规定这个轴有一个长度,1个小时。那么通过这个轴,我就可以知道一个小时里,我鼠标的点击情况。 对于这样的一个以时间为载体,里面载有不同的鼠标点击信息的这样一个东西,我将其称为流。随你怎么称呼,数据流也好,鼠标点击的事件流也好。(全是我自己的想法,仅作为启发,如果觉得不对,还请包涵)

当然,对于这样的一条鼠标点击事件流,我可以做很多事情。通常对于流的处理,我们可以截取流的一部分重新生成新的流(比如,截取里面的1分钟,或者截取里面的100毫秒),可以过滤这个流里面的一部分信息生成新的流(比如,我将原流里面的鼠标反键点击数据去掉,生成的新流里面只包含鼠标的正键点击的数据),又比如我可以对流进行监听(比如,我对流里面每一个点击事件进行判断,当我第一次点击正键的时候,我会在控制台打印出“第一次点击鼠标”字样),还可以将一个数据流与另一个数据流合并(比如将鼠标的点击事件流与键盘的点击事件流合并,这样我就可以对新的流进行联合处理,比如按下空格键的同时点击鼠标正键就会播放一段音乐。),当然可以取消 一个流(dispose)。

 

二、UniRX的肤浅讲解

        private void Start()
		{
			Observable.EveryUpdate().Subscribe(_ => Debug.Log("每帧都会触发哦"));
		}

Observable.EveryUpdate()表示将每一帧事件作为一个事件流返回。

Subscribe(_ => Debug.Log("每帧都会触发哦"))  ,就是订阅一个事件,绑定一个委托的意思。它的触发条件是在每一帧触发一次。对于内部的实现细节,是每一帧事件出现的时候就是调用OnNext(),然后就会调用Subscribe绑定的委托。

上面代码的意思就是将游戏里面每一帧事件,连成一个数据流,然后对于这个数据流,每一个事件发生(也就每一帧发生)的时候,都会调用Debug.Log("每帧都会触发哦") 这个。

万物皆可流。原本我们需要在Update调用的方法,现在只要在Start里面,将每一帧转化成事件流,然后给每一个事件发生的时候添加一个委托,那么就可以在每帧都调用这个委托了。

 

        private void Start()
		{
			//Observable.EveryUpdate().Subscribe(_ => Debug.Log("每帧都会触发哦"));

			this.UpdateAsObservable().Subscribe(_ => Debug.Log("每帧都会触发哦"));
		}

这个就是只给当前物体绑定这个委托。当前物体每个update都会调用委托。我注释掉的代码相当于是一个全局的。

 

this.UpdateAsObservable().First(_ => Input.GetMouseButtonDown(0))
				.Subscribe(_ => Debug.Log("第一次触发 鼠标点击事件,触发之后这个流的生命周期就完了"));

this.UpdateAsObservable() :获取this对象的每帧事件流。

First(_ => Input.GetMouseButtonDown(0)):监听一个事件流,当括号内的条件第一次满足的时候调用绑定的委托,然后结束这个流。

			Observable.EveryUpdate().First(_ => Input.GetMouseButtonDown(0))
				.Subscribe(_ => Debug.Log("第一次触发 鼠标点击事件,触发之后这个流的生命周期就完了")).AddTo(this);

这一段代码与上一段代码的功能完全一样。

 

现在应该对于UniRX和流的概念有一些了解了。那么我就加快节奏了。其中这篇文章作为我的一个学习记录,主要是记录一些方法的使用。

Observable.Timer(TimeSpan.FromSeconds(3)).Subscribe(_ => Debug.Log("三秒呢"));

Timer:就是一个定时器,到达时间后就会触发绑定的委托,然后结束当前流的生命。

Observable.Timer(TimeSpan.FromSeconds(3)).Do(_ => Debug.Log("第一个三秒")).Delay(TimeSpan.FromSeconds(3)).Do(_ => Debug.Log("又三秒"))
				.Subscribe(_ => Debug.Log("又三秒"));

Do:就是立即执行传入的委托。

Delay:就是延迟一段时间,然后执行之后的事情。

这个例子很重要。它的结果是  注意看时间。自己体会

Timer用于创建一个延时的流,在等待相应的时间后会发出一次OnNext。

 

而Delay则是流调用的一个方法,将发出的OnNext延时一个时间。

 

这边的Do方法,就是获取到OnNext后,马上执行Do中的内容,并将OnNext传递下去。

 

接下来的几个例子是https://blog.csdn.net/u012632851/article/details/80153149  的,我偷偷拿过来用一下

IEnumerator CorA()
{
    yield return new WaitForSeconds(1f);
    Debug.Log("A");
}
 
IEnumerator CorB()
{
    yield return new WaitForSeconds(1f);
    Debug.Log("B");
}
 
void Start ()
{
	var StreamA = Observable.FromCoroutine(CorA);
	var StreamB = Observable.FromCoroutine(CorB);
	StreamA.SelectMany(StreamB)
        .Subscribe(_=>Debug.Log("Hello"));
}
--------------------- 
作者:暗光之痕 
来源:CSDN 
原文:https://blog.csdn.net/u012632851/article/details/80153149 
版权声明:本文为博主原创文章,转载请附上博文链接!

SelectMany():顺序执行,当全部执行完了才开始后面的代码

这个就是流的顺序执行结果是

等待1秒——打印A——等待1秒——打印B——打印Hello

 

void Start ()
{
	var StreamA = Observable.FromCoroutine(CorA);
	var StreamB = Observable.FromCoroutine(CorB);
	StreamA.Concat(StreamB)
        .Subscribe(_=>Debug.Log("Hello"));
}
--------------------- 
作者:暗光之痕 
来源:CSDN 
原文:https://blog.csdn.net/u012632851/article/details/80153149 
版权声明:本文为博主原创文章,转载请附上博文链接!

Contact:同样是顺序执行A和B,但是

结果是:打印A -> 打印Hello -> 打印B -> 打印Hello

为什么会出现这样的情况?

 

首先我稍微提一下Rx的内部实现细节:内部有两种方法OnNext和OnComplete(暂时不考虑OnError),OnComplete调用的时候就是整个流生命周期完结的时候,而OnNext是往下进行通知。比方说一段流SubScribe打印Hello,流中每次执行OnNext就会打印一次Hello。也就是平时说的事件触发就会调用OnNext。

 

知道了OnNext的作用后,StreamA和StreamB执行完协程后都会发出一次OnNext,所以Concat连接后OnNext继续向下传递,所以会打印两次Hello。

 

而SelectMany会屏蔽之前所有的OnNext,直到最后一个协程执行完毕,才会继续传递OnNext,所以最终只有一次。当然SelectMany是最常用的。

 

 

IEnumerator CorA()
{
    yield return new WaitForSeconds(2f);
    Debug.Log("A");
}
 
IEnumerator CorB()
{
    yield return new WaitForSeconds(1f);
    Debug.Log("B");
}
 
void Start ()
{
	var StreamA = Observable.FromCoroutine(CorA);
	var StreamB = Observable.FromCoroutine(CorB);
    Observable.WhenAll(StreamA, StreamB)
        .Subscribe(_=>Debug.Log("Hello"));
}
--------------------- 
作者:暗光之痕 
来源:CSDN 
原文:https://blog.csdn.net/u012632851/article/details/80153149 
版权声明:本文为博主原创文章,转载请附上博文链接!

WhenAll:等待所有的流都执行完毕后,再开始后面的代码

结果:等一秒 -> 打印B -> 再等一秒 -> 打印A -> 打印Hello

 

Observable.ReturnUnit()
    .Delay(TimeSpan.FromSeconds(1f))
    .Do(_ => Debug.Log("A"))
    .Delay(TimeSpan.FromSeconds(1f))
    .Do(_ => Debug.Log("B"))
    .Subscribe(_ => Debug.Log("Hello"));
--------------------- 
作者:暗光之痕 
来源:CSDN 
原文:https://blog.csdn.net/u012632851/article/details/80153149 
版权声明:本文为博主原创文章,转载请附上博文链接!

不管是在什么语言工具中,一个空的概念是很重要的。而空流则是Rx中空的概念,所有的流都是基于这个概念而来的。

 

实际的来说Observable.ReturnUnit()就能产生一个空流,它的作用就是直接产生一个OnNext向后执行。

结果:打印B -> 打印A -> 打印Hello

 

这和我们第一个例子的结果是一样的,大家可以对比来看看。

 

它的主要作用就是在一些需要流操作的地方,将普通的方法转换为流,算是流的一个基石。

 

var clickStream = Observable.EveryUpdate().Where(_ => Input.GetMouseButtonDown(0));
clickStream.Buffer(clickStream.Throttle(TimeSpan.FromSeconds(1))).Where(x => x.Count == 2)
				.Subscribe(_ => Debug.Log("鼠标双击"));

这个是一个非常好的例子,会这个了,就说明流的概念搞懂了。

首先Observable.EveryUpdate()获取每帧的流。

然后Where(_ => Input.GetMouseButtonDown(0)),就是过滤每帧流,括号内是过滤的条件,我设置的是当鼠标正键点击为条件。

clickStream.Throttle(TimeSpan.FromSeconds(1)) :就是获取一段时间的流,这里是将流截取成1秒1秒的,然后每次获取一个1秒的流。

clickStream.Buffer():就是将一段流里面的数据转换成集合List,也就是说,如果我在1秒的流里面点击了3次,那么这个集合List的count就是3。

Where(x => x.Count == 2):就是上面说的一样,如果我鼠标1秒内点击了两次,那么就将这个流过滤出来。

Subscribe(_ => Debug.Log("鼠标双击")):最后将每一次过滤出来的流绑定这个委托

 

感觉我也有点一知半解,欢迎多多交流~

参考的资料:

https://blog.csdn.net/u012632851/article/details/80153149

https://blog.csdn.net/chinarcsdn/article/details/84984959#font_faceSegoe_UI_Black_size6_Thread_font__font_colorFF1493_face_size5__font_416

https://blog.csdn.net/zhenghongzhi6/article/details/79229585

https://github.com/neuecc/UniRx

https://zhuanlan.zhihu.com/p/27678951

  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值