[Unity3D plug-in] UniRx (a responsive programming framework based on Unity) plug-in learning

1. Introduce the UniRx plug-in

UniRxIs a Unity3D-based 响应式编程框架.

UniRxIt 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.

UniRxRewrote .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 UniRxcan make multithreading easier.

UniRxIt can simplify the programming of UGUI, and all UI events can be converted into UniRx event streams.

UniRxSupported platforms are PC/Mac/Android/iOS/WebGL/WindowsStoreand 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, WWWand 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

UniRxIt is here to solve these problems, so what are its advantages:

  • UniRxThe 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.
  • UniRxIt promotes multi-threaded operation, provides UGUI UI programming, and UI events can be converted into UniRxevent streams.
  • Unity3D supports C# after the 2017 version astnc/await, UniRxand also provides a lighter and more powerful astnc/awaitintegration 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

UniRxastnc/awaitIntegration in
https://github.com/Cysharp/UniTask

4. How to use the UniRx plugin

4-1. Quick start

Import the plugin into the project:

insert image description here

 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));}
}

insert image description here

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.

UniRxDesign patterns can be easily implemented  using  MVP(MVRP).

Why should  MVPa pattern be used instead of  MVVMa 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. PresentersLayers 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:

insert image description here

 (2) Select Add package from git URL...

insert image description here

(3) Add https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask to the package manager

insert image description here

 (4) The import is complete

insert image description here

 (5) Modify the Api Compatibility Level:

insert image description here

 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:

        Basic use of UniRx

        UniRx - Short Book

        UniRx Official Getting Started Documents_Sun.ME's Blog-CSDN Blog

Guess you like

Origin blog.csdn.net/qq_38721111/article/details/129148461