1. Introduce the UniRx plug-in
UniRx
Is a Unity3D-based 响应式编程框架
.
UniRx
It is the Unity version Rx响应式扩展
, 响应式
which is the observer and timer, 扩展
referring to the LINQ operator. Rx响应式扩展
The characteristic is that it is good at dealing with asynchronous logic in time. Programming in this Rx响应式扩展
way can well organize a lot of asynchronous and parallel processing.
UniRx
Rewrote .Net 响应式扩展
, the main function is to solve the asynchronous logic in time, making the asynchronous logic more concise and elegant.
Unity3D is usually single threaded, but UniRx
can make multithreading easier.
UniRx
It can simplify the programming of UGUI, and all UI events can be converted into UniRx
event streams.
UniRx
Supported platforms are PC/Mac/Android/iOS/WebGL/WindowsStore
and other platforms and libraries.
2. Why use the UniRx plugin
Some logical operations in the project require asynchronous time processing. For example 动画播放、网络请求、资源加载、场景过渡等等
, in this case, coroutines are usually used, that is, WWW
and Coroutine
, but using coroutines to do asynchronously is usually not a good choice because:
- A coroutine cannot return a value, its return type must be IEnumerator
- The coroutine cannot handle exceptions, because the yield return statement has no way to try-catch
- can result in the use of a large number of callbacks to handle logic
- The use of coroutines will lead to high coupling of the program, resulting in too complicated logic in the coroutines
UniRx
It is here to solve these problems, so what are its advantages:
UniRx
The usage method is between callback and event. There is the concept of event, and callback is also used. After the event is organized, the callback only needs to be called once to process the event.UniRx
It promotes multi-threaded operation, provides UGUI UI programming, and UI events can be converted intoUniRx
event streams.- Unity3D supports C# after the 2017 version
astnc/await
,UniRx
and also provides a lighter and more powerfulastnc/await
integration for Unity.
Three, UniRx plug-in download
Source address:
https://github.com/neuecc/UniRx
Unity Asset Store address (free):
http://u3d.as/content/neuecc/uni-rx-reactive-extensions-for-unity/7tT
Plug-in download address:
https://github.com/neuecc/UniRx/releases
UniRx
astnc/await
Integration in
https://github.com/Cysharp/UniTask
4. How to use the UniRx plugin
4-1. Quick start
Import the plugin into the project:
Create a new script UniRxTest.cs to edit the code to realize a double-click detection 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));}
}
This demo uses 5 lines of code to demonstrate the following functions:
- Update as a stream of events
- combined event stream
- merge own flow
- Convenient handling of time-based operations
4-2. Timing function (compared with coroutine)
In normal project development, you may encounter operations that require some logic to start after a period of time. You can use coroutines to write:
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");
}
}
So how to write it with 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");
}
}
It can even be simplified to one line of code:
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"); });
}
}
In order to avoid the situation that the process has not been destroyed when this is destroyed, you can add a line of code:
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");}
}
After AddTo(this), the delay will be bound to this(MonoBehaviour). When this is destroyed, the defined process will also be destroyed.
4-3. GET and POST operations
General writing:
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);}
}
}
Written in 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);
}
}
Note: It is not to discuss which way of writing is good, then the way of writing is not good, but it is more concise to use UniRx, and it is more recommended to use UnityWebRequest, because UnityWebRequest has more complete functions and is more effective.
4-4. Loading scene-AsyncOperation
AsyncOperation is often used when loading resources asynchronously or loading scenes asynchronously.
UniRx supports AsyncOperation. Makes loading progress easy to monitor.
The sample code is as follows:
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 support
Sample code:
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());
}
}
It can be seen that UniRx is still very useful for binding UI, but not only that.
UniRx
Design patterns can be easily implemented using MVP(MVRP)
.
Why should MVP
a pattern be used instead of MVVM
a pattern? Unity does not provide a UI binding mechanism, creating a binding layer is too complicated and has a performance impact (using reflection). Still, the view needs to be updated. Presenters
Layers know about View components and can update them.
Although there is no real binding, Observables
subscribers can be notified, and the function is similar. This mode is called Reactive Presenter 设计模式
, the sample code is as follows:
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 also has a strong attribute ReactiveProperty, which is a responsive attribute. The reason why it is powerful is that it allows more functions to be added during variable changes and is more flexible.
For example, to monitor whether a variable value changes, you can write:
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变化了");}
}
Although the above code can also complete the function of monitoring variable value changes, if you want to access it externally, you need to write a delegate to monitor, which is cumbersome. If you use UniRx, it will be much simpler:
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 plays a certain frame of animation
The code mainly uses UniRx.Async. Later, UniRx.Async is divided into Cysharp/UniTask, and the Cysharp/UniTask package needs to be imported again. The steps are as follows:
(1) Window→Package Manager to open the package manager:
(2) Select Add package from git URL...
(3) Add https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask to the package manager
(4) The import is complete
(5) Modify the Api Compatibility Level:
sample code
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;
}
}
More fine-grained usage:
UniRx Official Getting Started Documents_Sun.ME's Blog-CSDN Blog