[Unity3D]UniRx介绍以及使用
国庆回来,发现有一段时间没写东西,今天继续更新一个比较好用的东西.UniRx:
送上AssetStore链接:
UniRx - Reactive Extensions for Unity | Integration | Unity Asset Store
这是一个unity官方商店里一个免费的插件,看完本片文章,如果感兴趣的可以去下载学习学习.
什么是UniRx:一个响应式框架(链式编程),Unity编程框架,专注于解决时间上的异步.
UniRx简单示例先入个门:
public class UniRxExample : MonoBehaviour
{
private void Awake()
{
Debug.Log("开始时间:" + Time.realtimeSinceStartup);
Observable.Timer(TimeSpan.FromSeconds(2))
.Subscribe(_ => {
Debug.Log("延时2秒"+Time.realtimeSinceStartup);
}).AddTo(this);
}
}
直接看代码可能看不懂,简单解释一下,这是一个平时我们经常使用的一个延时的功能,可能一般情况我们是使用协程来做延时,但是协程很麻烦,需要开启,还需要关闭,而UniRx的实现方式更加简洁优雅.
代码做的事情很简单: 开启一个时间上的异步监听,Subscribe订阅需要处理的事情.AddTo(this)表示当前类防止销毁之后的异常处理,就像生命周期一样.Timer和Observable是一起的就跟发布者一样,当触发条件,或者条件达到的时候就会发送事件流到Subscribe中处理订阅者需要的逻辑。
看到这里可能会有点发布者订阅者设计模式意思.的确是这样的,Observable目前就理解一个发布者.Subscribe可以理解成订阅者.Timer是一个可以被观察,只有可以被观察的才可以被订阅.
这里算是一个简单的入门,因为当你熟悉之后,其实Rx 就是一个使用可观察集合和LINQ风格查询运算符组合成的基于异步和基于事件的可编程库.
为什么使用Rx:
通常,在Unity对网络操作要求使用WWW和Coroutine.但是出于以下几点原因(或者其它原因)使用协程来进行异步操作并不顺手:
[*]虽然协程的返回类型必须是IEnumerator,但是协程不能返回任何值。
[*]因为yield return 语句不能被try-catch结构体包裹,协程中不能处理异常。
[*]实现mvp 架构模式
[*]对UGUI进行了大量的增强
这种缺乏可组合性导致程序的紧耦合,往往造成IEnumators中逻辑过于复杂。
Rx可以解决异步调用的“伤痛”,Rx 是一个使用可观察集合和LINQ风格查询运算符组合成的基于异步和基于事件的可编程库。
游戏循环(every Update,OnCollisionEnter),传感器数据(Kinect,Leap Motion,VR Input 等等)这些类型的事件。Rx将事件表示为响应式序列。通过使用LINQ查询运算符,Rx变得容易组合且支持基于时间的操作。
引出Rx链式编程:
在前面的入门示例代码中,发现直接点点点引出下一个方法,编码方式就跟套娃一样,一层套一层.
如果熟悉过Linq编程的可能知道,Linq是操作数+关键字的的形式,RX同样支持Linq一些关键字,而且比Linq更加强大.
简单示例一个代码:
Linq:
private void Awake()
{
List<int> number = new List<int> { 1, 52, 61, 20, 15, 60 };
number.Where(nu => nu > 20)
.ToList()
.ForEach(nu => { Debug.Log(nu); });
}
UniRx:
Observable.EveryUpdate()
.Where(_ => Input.GetMouseButtonDown(0))
.Subscribe(_ => {
Debug.Log(&#34;鼠标点击&#34;);
});
运行结果:
可以看出,两个代码还是很像的,像wher这种操作符还有很多,比如Select,Frist.还有一些只有RX特有的,例如Repeat,skip,take等等,
从这个图(图在结尾)就可以看出RX的强大了.RX不仅支持一些像linq一些关键字,还有一些对monobehaviour脚本生命周期函数的支持,以及UGUI.接下来我在unity中实现一些代码:
public class RxLifeCycle : MonoBehaviour
{
private void Awake()
{
Observable.EveryUpdate().Subscribe(_ => {
Debug.Log(&#34;类似于 unity updata&#34;);
}).AddTo(this);
Observable.EveryLateUpdate().Subscribe(_ => {
Debug.Log(&#34;类似于 unity Lateupdata&#34;);
}).AddTo(this);
Observable.EveryFixedUpdate().Subscribe(_ => {
Debug.Log(&#34;类似于 unity Fixedupdata&#34;);
}).AddTo(this);
Observable.EveryEndOfFrame().Subscribe(_ => {
Debug.Log(&#34;EveryEndOfFrame&#34;);
}).AddTo(this);
}
可以结合一些关键字实现一些比较好用的操作,例如当你点击鼠标点击之后,输出一个结果,或者发送一个事件,一个回调函数
namespace SYFramework
{
public class RxLifeCycle : MonoBehaviour
{
private void Awake()
{
Observable.EveryUpdate().
Where(_=>Input.GetMouseButtonDown(0)) //相当于if 判断
.First() //只执行一次Subscribe:订阅的事件或者回调
.Subscribe(_ => {
Debug.Log(&#34;鼠标只能点击一次&#34;);
}).AddTo(this);
}
}
}
是不是很实用,只需要一个简单的代码就可以执行一个复杂的操作,而且代码还很简洁。如果你熟练使用之后,在你的项目中使用,会一劳永逸的。
Rx关键字:
在上面的示例代码中使用了常用的关键字,其实Rx可以理解成:
Rx = Observables + LINQ + Schedulers。
微软给的定义是,Rx 是一个函数库,让开发者可以利用可观察序列和 LINQ 风格查询操作符来编写异步和基于事件的程序,使用 Rx,开发者可以用 Observables 表示异步数据流,用 LINQ 操作符查询异步数据流, 用 Schedulers 参数化异步数据流的并发处理。
那接下来学习Rx一定要学习关键字和类似于LINQ查询操作符了。
Where :范围限定 条件过滤
Select:(以什么样的结果输出) 变换 选择什么样的结果做为输出成集合中 用于使用
addto:生命周期 游戏物体销毁的时候一同销毁
Skip(value): (Linq: 跳过序列中指定数量的元素 然后返回剩余的元素)忽略初始值设置 可以设置1 第一次忽略
Observable.FromCoroutine(_ => StartIe()): 协程调用
ReactiveCommand: Execute():CanExecute();
var proge = new ScheduledNotifier<float>():加载进度
Frist:只做一次 判断
Distinct :去重
last: 取最后一个数据 可以使用条件表达式进行判断 输出
SelectMany:(Linq解释:将序列的每个元素投影到 IEnumerable<T> 并将结果序列合并为一个序列。 对每项再进行遍历处理再进行合成序列。)
Take :连续获取指定个数的事件
Concat:连接两个事件源
WhenAll:(linq 解释:确定序列中的所有元素是否都满足条件) 监听所有的事件源的完成完成事件 并行执行Observables
OfType: (linq:获取指定类型) 过滤指定类型的元素 事件
Cast:(Linq:将 IEnumerable 的元素强制转换为指定的类型) UniRx一样
GroupBy:(Linq:对序列中的元素进行分组。) UniRx:根据key 分组
Range:(Linq:生成指定范围内的整数的序列。)
TakeWhile:(Linq: 如果指定的条件为 true,则返回序列中的元素,然后跳过剩余的元素。)一直接收 直到条件满足
SkipWhile:(linq:如果指定的条件为 true,则跳过序列中的元素,然后返回剩余的元素。)一直忽略 直到满足事件
Zip:(,net4支持操作符 将指定函数应用于两个序列的对应元素,以生成结果序列。) 将一个匿名桉树,应用于两个事件源 函数会成为对处理两个事件源的事件
Repeat:(Linq: 在生成序列中重复该值的次数。)重复生成事件
TakeLast :Observable 完成后获取最后一个或指定个数
Single :(Linq:返回序列中的单个特定元素,与 First 非常类似,但是 Single 要确保其满足条件的元素在序列中只有一个。) 查询一个符合条件的元素
ToArray :(linq:创建数组)将完成的事件组成一个数组
ToList:(linq:创建list 集合) 将完成的事件组成一个list集合
Aggregate:(linq:对序列应用累加器函数。 将指定的种子值用作累加器的初始值,并使用指定的函数选择结果值。)聚合 实现累加或者累乘的功能
Empty:(Linq:返回具有指定类型参数的空 IEnumerable<T>)空的Observable
UniRx 特有的关键字:
Interval: Observable.Interval(TimeSpan.FromSeconds(1f))固定是时间执行一次回调
TakeUntil:在满足条件之前,连续获取数据(事件)
SkipUntil:在满足条件之前,忽略所有数据(事件)
Buffer(缓冲):每次缓存指定个数数据 满足则发送
Throttle(节流阀):
Observable.EveryUpdate().Where(_ => Input.GetMouseButtonDown(0))
.Throttle(TimeSpan.FromSeconds(1))
.Subscribe(_ => Debug.Log(&#34;一秒过后&#34;));
Return: 直接返回指定数据,并发送完成事件
Observable.Return(&#34;Heloo&#34;).Subscribe(Debug.Log);
Timer:延时Observable
Observable.Timer(TimeSpan.FromSeconds(1))
.Subscribe(_=> {
Debug.Log(&#34;延时一秒&#34;);
});
Delay:在每件事情上延时
Observable.EveryUpdate()
.Where(_ => Input.GetMouseButtonDown(0))
.Delay(TimeSpan.FromSeconds(1.0f))
.Subscribe(_ => { Debug.Log(&#34;mouse clicked&#34;); })
.AddTo(this);
Sample:定期发送离当前事件最近的数据(事件)
Observable.Interval(TimeSpan.FromMilliseconds(50))
.Sample(TimeSpan.FromSeconds(1))
.Subscribe(_ => { Debug.Log(DateTime.Now.Second); })
.AddTo(this);
Timestamp:为每个数据(事件) 包装一个时间戳
Observable.Interval(TimeSpan.FromSeconds(1.0f))
.Timestamp()
.Subscribe(timestamp => { Debug.Log(timestamp); })
.AddTo(this);
ThrottleFirst:每隔一段时间内,发送第一个数据(事件)
Observable.EveryUpdate()
.Where(_ => Input.GetMouseButtonDown(0))
.ThrottleFirst(TimeSpan.FromSeconds(5))
.Subscribe(_ => {
Debug.Log(&#34;鼠标点击&#34;);
})
.AddTo(this);
TimeInterval:计算当前的事件与上一个事件的事件间隔,并发送
Observable.Interval(TimeSpan.FromMilliseconds(750))
.TimeInterval()
.Subscribe(timeInterval => Debug.LogFormat(&#34;{0}: {1}&#34;, timeInterval.Value, timeInterval.Interval));
Defer:直到有观察者订阅时才创建 Observable,并且为每个观察者创建一个新的 Observable
var ran =new System.Random();
Observable.Defer(() => Observable.Start(() => ran.Next()))
.Delay(TimeSpan.FromSeconds(1f))
.Repeat()
.Subscribe(_ =>
{
Debug.Log(_);
})
.AddTo(this);
这里列举了一些常用的关键字,其实还有很多,如果想仔细了解RX具体使用,可以查阅官方文档。这里就介绍这么多了。
响应式属性:ReactiveProperty
接下来我通过Rx实现一个MVP架构,在项目中如果具体的去使用一些Rx特有的一些东西,也是接下来重点介绍的响应式属性。什么是响应式属性,顾名思义,就是通过改变其值的方法,该属性就可以通知订阅当前属性的一些事件。还有其强大之处,就是可以代替一切变量,并且可以让这个变量有更多的功能。一般情况下我们写一些可以回调的变量都是需要写一些委托,是需要一定的维护成本的。例如:
namespace SYFramework
{
publicclass NomalProprety :MonoBehaviour
{
private static string values;
public static string Values
{
get => values;
set
{
if (!values.Equals(value))
{
values = value;
callback?.Invoke(value);
}
}
}
public static Action<string> callback;
private void Awake()
{
callback = (va) => {
Debug.Log(&#34;va:&#34; + va);
};
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
Values = &#34;鼠标点击了&#34;;
}
}
}
}
所以如果引入RX可以让你的代码更加简洁明了,RX一行代码就可以解决,在下面的MVP中有具体使用。
下面是mvp实现代码:
Model类:
using UniRx;
namespace SYFramework
{
/// <summary>
/// 定义一个Model 接口
/// </summary>
public interface IModel
{
//定义整型变量
IntReactiveProperty Score { get; }
//定义字符串变量
StringReactiveProperty Name { get; }
}
/// <summary>
/// 实现Imodel 接口
/// </summary>
publicclass GameData : IModel
{
public IntReactiveProperty Score { get; } = new IntReactiveProperty() { Value = 0 };
public StringReactiveProperty Name { get; } = new StringReactiveProperty() { Value = &#34;&#34; };
}
}
publicIntReactiveProperty Score {get;}=newIntReactiveProperty(){ Value =0}; 这种写法是4.0 .Net的一个特性,不了解的可以去查一下。具体使用就是像申明一个变量一样,但是这个属性具有事件流的性质,可以被订阅。
view类:
using UnityEngine;
using UniRx;
using UnityEngine.UI;
namespace SYFramework
{
public class UniRxPanel : MonoBehaviour
{
public Button send_Btn;
public Text score_Txt;
public Text gameName_txt;
public IModel model;
private void Awake()
{
model = new GameData();
//订阅 RX写法 对ugui的扩展支持
model.Score.SubscribeToText(score_Txt);
model.Name.SubscribeToText(gameName_txt);
//发送消息
send_Btn.OnClickAsObservable().Subscribe(_ => {
model.Name.Value = &#34;游戏数据&#34;;
model.Score.Value = Random.Range(1,100);
});
}
}
}
view类中使用了RX对UGUI的封装,例如:send_Btn.OnClickAsObservable()就是对按钮监听,点击之后会以事件流的形式发送到Subscriber订阅中。其实Rx对UGUI的封装格式基本上都是以xxxAsObservable()形式,所以了解了这个格式基本上就知道常用UI控件的使用了。
UniRx 支持了可序列化的 ReactiveProperty 类型,比如 Int/LongReactivePropety、Float/DoubleReactiveProperty、StringReactiveProperty、BoolReactiveProperty,还有更多,请参见 UniRx 中的源码 InspectableReactiveProperty.cs。也支持泛型类型例如ReactiveProperty<int> ,ReactiveProperty<string> ,ReactiveProperty<bool>等等。
其实RX有很多强大的功能,我这里只是写了一小部分,很多大佬写的一些框架,例如uFrame 框架QFramework框架等等。这里是对自己学习的一个记录,如果你也在学习框架的编写,可以认真的学完RX,或许对你有用。
结尾送一整图
写文章不易,留个小心心吧[爱] [赞] [爱]
页:
[1]