【Unity3D插件】UniRx(基于Unity的响应式编程框架)插件教程

news/2024/11/23 1:38:48/

推荐阅读

  • CSDN主页
  • GitHub开源地址
  • Unity3D插件分享
  • 简书地址
  • 我的个人博客
  • QQ群:1040082875

大家好,我是佛系工程师☆恬静的小魔龙☆,不定时更新Unity开发技巧,觉得有用记得一键三连哦。

一、介绍UniRx插件

UniRx是一种基于Unity3D的响应式编程框架

UniRx就是Unity版本的Rx响应式扩展响应式就是观察者和定时器,扩展指的是LINQ的操作符。Rx响应式扩展的特点就是擅长处理时间上的异步的逻辑。用Rx响应式扩展的方式编程可以很好地组织大量异步与并行处理。

UniRx重写了.Net的响应式扩展,主要作用是解决时间上异步的逻辑,让异步逻辑变得更加简洁和优雅。

Unity3D通常是单线程,但是UniRx可以让多线程更容易。

UniRx可以简化 UGUI 的编程,所有的UI事件可以转化为UniRx 的事件流。

UniRx支持的平台有PC/Mac/Android/iOS/WebGL/WindowsStore等平台和库。

二、为什么使用UniRx插件

在项目中的一些逻辑操作需要做异步时间处理,比如说动画播放、网络请求、资源加载、场景过渡等等,这种情况通常要使用协程,也就是WWWCoroutine,但是使用协程来做异步通常不是一个很好的选择,因为:

  • 协程不能返回值,它的返回类型必须是IEnumerator
  • 协程不能处理异常,因为yield return 语句没有办法try-catch
  • 会导致使用大量的回调来处理逻辑
  • 使用协程会导致程序的耦合性高,造成协程中的逻辑过于复杂

UniRx就是为了解决这些问题来的,那么它有哪些优点呢:

  • UniRx的使用方式介于回调和事件之间,有事件的概念,也使用了回调,回调是在事件经过组织之后,只需要调用一次进行事件的处理。
  • UniRx促进了多线程的操作,提供了UGUI的UI编程,UI事件可以转化为UniRx的事件流。
  • Unity3D在2017版本后支持了C#中的astnc/awaitUniRx也为Unity提供了更轻量、强大的astnc/await集成。

三、UniRx插件下载

源码地址:
https://github.com/neuecc/UniRx

Unity Asset Store 地址(免费):
http://u3d.as/content/neuecc/uni-rx-reactive-extensions-for-unity/7tT

插件下载地址:
https://github.com/neuecc/UniRx/releases

UniRx中的astnc/await集成
https://github.com/Cysharp/UniTask

四、怎么使用UniRx插件

4-1、快速入门

将插件导入到项目中:
在这里插入图片描述
新建脚本UniRxTest.cs编辑代码,实现一个双击检测Demo:

using System;
using UniRx;
using UnityEngine;public class UniRxTest : MonoBehaviour
{void Start(){// Observable.EveryUpdate调用协程的yield return null。// 它位于Update之后,LateUpdate之前。// Where等待操作的事件(当前事件是左键单击)var doubleClick = Observable.EveryUpdate().Where(value => Input.GetMouseButtonDown(0));// Buffer 添加一个事件// Throttle 响应的最大间隔// TimeSpan.FromMilliseconds(250) 设置为250毫秒// Where 等待操作的事件(当前事件是左键单击)// Subscribe 绑定委托doubleClick.Buffer(doubleClick.Throttle(TimeSpan.FromMilliseconds(250))).Where(value => value.Count >= 2).Subscribe(value => Debug.Log("双击! 点击次数:" + value.Count));}
}

运行结果:
在这里插入图片描述
这个Demo使用了5行代码就演示了以下功能:

  • Update作为事件流
  • 组合事件流
  • 合并自身流
  • 方便处理基于时间的操作

4-2、定时功能(与协程对比)

在平时项目开发中,可能会遇到需要经过一段时间出发某些逻辑的操作,可以用协程这么写:

using System;
using System.Collections;
using UniRx;
using UnityEngine;public class UniRxTest : MonoBehaviour
{void Start(){// 每5秒调用一次函数StartCoroutine(Timer(5, DoSomething));}// 定时器IEnumerator Timer(float seconds, Action callback){yield return new WaitForSeconds(seconds);callback();}// 调用函数void DoSomething(){Debug.Log("TODO");}
}

那么用UniRx怎么写呢:

using System;
using System.Collections;
using UniRx;
using UnityEngine;public class UniRxTest : MonoBehaviour
{void Start(){// 每5秒调用一次函数Observable.Timer(TimeSpan.FromSeconds(5)).Subscribe(value => {DoSomething();});}// 调用函数void DoSomething(){Debug.Log("TODO");}
}

甚至可以简化成一行代码:

using System;
using System.Collections;
using UniRx;
using UnityEngine;public class UniRxTest : MonoBehaviour
{void Start(){// 每5秒调用一次函数Observable.Timer(TimeSpan.FromSeconds(5)).Subscribe(value => { Debug.Log("TODO"); });}
}

为了避免this销毁的时候,流程还没有销毁的情况,可以加一行代码:

using System;
using System.Collections;
using UniRx;
using UnityEngine;public class UniRxTest : MonoBehaviour
{void Start(){// 每5秒调用一次函数Observable.Timer(TimeSpan.FromSeconds(5)).Subscribe(value => { DoSomething(); }).AddTo(this);}// 调用函数void DoSomething(){Debug.Log("TODO");}
}

AddTo(this)之后,就会将延迟和this(MonoBehaviour)绑定在一起了,当this被销毁的时候,定义的流程也会被销毁。

4-3、GET和POST操作

一般写法:

using System;
using System.Collections;
using UniRx;
using UnityEngine;
using UnityEngine.Networking;public class UniRxTest : MonoBehaviour
{void Start(){StartCoroutine(RequestData("www.baidu.com", new WWWForm(), ReturnValue));}//回调函数private void ReturnValue(string value){Debug.Log(value);}/// <summary>/// 数据请求与发送/// </summary>/// <param name="url">请求的url</param>/// <param name="form">表单</param>/// <param name="dele">返回数据</param>/// <returns></returns>private IEnumerator RequestData(string url, WWWForm form, Action<string> dele = null){UnityWebRequest req = UnityWebRequest.Post(url, form);yield return req.SendWebRequest();if (req.result == UnityWebRequest.Result.ProtocolError){dele?.Invoke(req.error);}if (req.isDone){dele?.Invoke(req.downloadHandler.text);}}
}

用UniRx写法:

using System;
using System.Collections;
using UniRx;
using UnityEngine;
using UnityEngine.Networking;public class UniRxTest : MonoBehaviour
{void Start(){var request = ObservableWWW.Post("www.baidu.com", new WWWForm()).Subscribe(value => Debug.Log(value)).AddTo(this);}
}

注意:不是讨论那个写法好,那么写法不好,只是使用UniRx更加简洁,更推荐是用UnityWebRequest,因为UnityWebRequest功能更完善,更加有效。

4-4、加载场景-AsyncOperation

在异步加载资源或者异步加载场景的时候往往会用到 AsyncOperation。

UniRx 对 AsyncOperation 做了支持。使得加载进度可以很容易地监听。

示例代码如下:

using UniRx;
using UnityEngine;
using UnityEngine.SceneManagement;namespace UniRxLesson
{public class AsyncOperationExample : MonoBehaviour{void Start(){var progressObservable = new ScheduledNotifier();SceneManager.LoadSceneAsync(0).AsAsyncOperationObservable(progressObservable).Subscribe(asyncOperation =>{Debug.Log("load done");Resources.LoadAsync("TestCanvas").AsAsyncOperationObservable().Subscribe(resourceRequest =>{Instantiate(resourceRequest.asset);});});progressObservable.Subscribe(progress =>{Debug.LogFormat("加载了:{0}", progress);});}}
} 

4-5、UGUI支持

示例代码:

using System;
using System.Collections;
using UniRx;
using UniRx.Diagnostics;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;public class UniRxTest : MonoBehaviour
{public Button mButton;public Toggle mToggle;public InputField mInput;public Text mText;public Slider mSlider;void Start(){// Button 按钮绑定事件mButton.onClick.AsObservable().Subscribe(_ => Debug.Log("clicked"));// Toggle 控制其他UI对象的激活mToggle.OnValueChangedAsObservable().SubscribeToInteractable(mButton);// mInput Where筛选值不等于空的情况 绑定Text组件mInput.OnValueChangedAsObservable().Where(x => x != null).SubscribeToText(mText);// mSlider 绑定Text组件mSlider.OnValueChangedAsObservable().SubscribeToText(mText, x => Math.Round(x, 2).ToString());}
}

可以看出来,UniRx去绑定UI还是很好用的,但不仅于此。

使用 UniRx可以很容易地实现 MVP(MVRP)设计模式。

为什么应该用 MVP模式而不是 MVVM模式?Unity 没有提供 UI 绑定机制,创建一个绑定层过于复杂并且会对性能造成影响(使用反射)。尽管如此,视图还是需要更新。 Presenters层知道 View 组件并且能更新它们。

虽然没有真的绑定,但 Observables 可以通知订阅者,功能上也差不多。这种模式叫做 Reactive Presenter 设计模式,示例代码如下:

using System;
using System.Collections;
using UniRx;
using UniRx.Diagnostics;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;public class UniRxTest : MonoBehaviour
{public Button mButton;public Toggle mToggle;public Text MyText;// 状态更改ModelEnemy enemy = new Enemy(1000);void Start(){// 以响应式的方式从视图和模型中提供用户事件mButton.OnClickAsObservable().Subscribe(_ => enemy.CurrentHp.Value -= 100);mToggle.OnValueChangedAsObservable().SubscribeToInteractable(mButton);// Model通过Rx通知更新视图enemy.CurrentHp.SubscribeToText(MyText);enemy.IsDead.Where(isDead => isDead).Subscribe(_ =>{mToggle.interactable = mButton.interactable = false;});}
}
// 所有属性的值更改时都会通知
public class Enemy
{public ReactiveProperty<long> CurrentHp { get; private set; }public ReadOnlyReactiveProperty<bool> IsDead { get; private set; }public Enemy(int initialHp){// 声明属性CurrentHp = new ReactiveProperty<long>(initialHp);IsDead = CurrentHp.Select(x => x <= 10).ToReadOnlyReactiveProperty();}
}

4-6、响应式属性ReactiveProperty

UniRx还有一个很强的属性ReactiveProperty,也就是响应式属性,之所以强大,是因为它让变量的变化过程中可以增加更多的功能更加的灵活。

比如,要监听一个变量值是否发生变化,可以这么写:

using System;
using System.Collections;
using UniRx;
using UniRx.Diagnostics;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;public class UniRxTest : MonoBehaviour
{public int mAge;public int Age{get{return mAge;}set{if (mAge != value){mAge = value;OnAgeChanged();}}}public void OnAgeChanged(){Debug.Log("Value变化了");}
}

上述代码虽然也可以完成监听变量值变化的功能,但是如果要在外部访问,还需要写一个委托来监听,比较麻烦,如果用UniRx就会简单许多:

using System;
using System.Collections;
using UniRx;
using UniRx.Diagnostics;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;public class UniRxTest : MonoBehaviour
{public ReactiveProperty<int> Age = new ReactiveProperty<int>();void Start(){// 绑定值Age.Subscribe(value =>{Debug.Log("通知值变化");});// 改变值可以用 变量.value来获取或者更改Age.Value = 5;}
}

4-7、Animation播放某一帧的动画

代码主要用到了UniRx.Async,后面UniRx.Async被分割成Cysharp/UniTask,需要再导入Cysharp/UniTask包,步骤如下:

(1)Window→Package Manager打开包管理器:
在这里插入图片描述
(2)选择Add package from git URL…
在这里插入图片描述
(3)添加 https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask 至包管理器
在这里插入图片描述
(4)导入完成
在这里插入图片描述
(5)修改Api Compatibility Level:
在这里插入图片描述
示例代码:

using Cysharp.Threading.Tasks;
using UniRx;
using UnityEngine;public class UniRxTest : MonoBehaviour
{private bool stopLoop = false;//动画控制void Start(){}/// <summary>/// Animation播放指定帧的动画/// </summary>/// <param name="myAnim">动画组件</param>/// <param name="startTimeInt">开始时间</param>/// <param name="endTimeInt">结束时间</param>private async void PlayAnimation(Animation myAnim, int startTimeInt, int endTimeInt){int speed = GetSpeed(startTimeInt, endTimeInt);float frame = GetFrame(myAnim);float startTime;float endTime;if (speed == 1){startTime = frame * startTimeInt;endTime = frame * endTimeInt;}else{startTime = frame * endTimeInt;endTime = frame * startTimeInt;}stopLoop = false;while (!stopLoop){myAnim[myAnim.clip.name].time = startTime;//跳过开始帧myAnim[myAnim.clip.name].speed = speed;//正播还是倒播myAnim.Play(myAnim.clip.name);//Play()await UniTask.DelayFrame(1);//帧延缓,等Play()启动                                                               await UniTask.WaitUntil(() => myAnim[myAnim.clip.name].time > endTime);//播放到指定的进度点则停止        myAnim.Stop();//停止播放stopLoop = true;//停止播放}}// 判断是正播还是倒播int GetSpeed(int startTime, int endTime){if (endTime - startTime > 0){return 1;}else if (endTime - startTime < 0){return -1;}else{return 1;}}// 得到动画的播放帧率float GetFrame(Animation myAnim){return myAnim[myAnim.clip.name].length / 100;}
}

五、后记

讲解了UniRx插件以及UniTask插件的使用方法,难度有点高,适合慢慢学习(收藏吃灰)。

在学习过程中有什么不懂的都可以在博客主页找到我的联系方式。


你的点赞就是对博主的支持,有问题记得留言:

博主主页有联系方式。

博主还有跟多宝藏文章等待你的发掘哦:

专栏方向简介
Unity3D开发小游戏小游戏开发教程分享一些使用Unity3D引擎开发的小游戏,分享一些制作小游戏的教程。
Unity3D从入门到进阶入门从自学Unity中获取灵感,总结从零开始学习Unity的路线,有C#和Unity的知识。
Unity3D之UGUIUGUIUnity的UI系统UGUI全解析,从UGUI的基础控件开始讲起,然后将UGUI的原理,UGUI的使用全面教学。
Unity3D之读取数据文件读取使用Unity3D读取txt文档、json文档、xml文档、csv文档、Excel文档。
Unity3D之数据集合数据集合数组集合:数组、List、字典、堆栈、链表等数据集合知识分享。
Unity3D之VR/AR(虚拟仿真)开发虚拟仿真总结博主工作常见的虚拟仿真需求进行案例讲解。
Unity3D之插件插件主要分享在Unity开发中用到的一些插件使用方法,插件介绍等
Unity3D之日常开发日常记录主要是博主日常开发中用到的,用到的方法技巧,开发思路,代码分享等
Unity3D之日常BUG日常记录记录在使用Unity3D编辑器开发项目过程中,遇到的BUG和坑,让后来人可以有些参考。

http://www.ppmy.cn/news/21106.html

相关文章

文本数据预处理:可能需要关注这些点

文章目录1、文本数据获取2、常规文本数据预处理2.1 将文本数据清洗干净2.2 将文本数据格式化3、任务相关的文本数据预处理3.1 不平衡问题3.2 数据增强问题3.3 数据标注问题4、一些可用的文本预处理工具5、总结本文关键词&#xff1a; 文本数据预处理、中文文本预处理、自然语言…

TOGAF概述

TOGAF主要分为8个元素:念、法、技、导、行、连、考、能 念--概念阐述 法--架构开发方法 技--32个最佳实践技术 导--4种向导 行--架构内容框架 连--企业连续系列 考--参考成熟架构资产 能--架构工作能力

【C语言练习】逻辑分析题

目录题目一&#xff1a;求名次题目详情&#xff1a;解题思路&#xff1a;题目二&#xff1a;找凶手题目详情&#xff1a;解题思路&#xff1a;题目一&#xff1a;求名次 题目详情&#xff1a; 5位运动员参加了10米台跳水比赛&#xff0c;有人让他们预测比赛结果&#xff1a; A…

多线程之Callable接口、ReentrantLock、信号量 Semaphore以及CountDownLatch

目录&#xff1a;一、Callable接口Callable的用法小结二、ReentrantLockReentrantLock 的用法ReentrantLock 和 synchronized 的区别&#xff1f;为什么有了 synchronized 还需要 juc(java.util.concurrent) 下的 lock&#xff1f;三、信号量 Semaphore如何理解信号量&#xff…

gma 地理空间绘图:(1)绘制简单的世界地图-1.地图绘制与细节调整

了解 gma gma 是什么&#xff1f; gma 是一个基于 Python 的地理、气象数据快速处理和数据分析函数包&#xff08;Geographic and Meteorological Analysis&#xff0c;gma&#xff09;。gma 网站&#xff1a;地理与气象分析库。 gma 的主要功能有哪些&#xff1f; 气候气象&a…

操作系统(四)--进程的地址空间

目录 一、引言 二、进程的地址空间 ------> 2.1、进程的段 ------> 2.2、查看地址空间 ------------> 2.2.1、静态链接 ------------> 2.2.2、动态链接 ------------> 2.2.3、无名段 ------------> 2.2.4、vdso、vvaar、vsyscall 三、mmap ------&…

第八届蓝桥杯省赛 C++ A/B组 - 分巧克力

✍个人博客&#xff1a;https://blog.csdn.net/Newin2020?spm1011.2415.3001.5343 &#x1f4da;专栏地址&#xff1a;蓝桥杯题解集合 &#x1f4dd;原题地址&#xff1a;后缀表达式 &#x1f4e3;专栏定位&#xff1a;为想参加蓝桥杯的小伙伴整理常考算法题解&#xff0c;祝大…

ElasticSearch从入门到出门【上】

文章目录初识elasticsearch了解ESelasticsearch的作用ELK技术栈elasticsearch和lucene为什么不是其他搜索技术&#xff1f;倒排索引正向索引倒排索引正向和倒排ES的一些概念文档和字段索引和映射mysql与elasticsearch安装elasticsearch部署单点es部署kibana安装IK分词器在线安装…