UniRx 入门

news2024/11/23 15:13:03

Reactive X 是 Reactive Extensions 的缩写,一般简写为 Rx,最初是 LINQ 的一个扩展,由微软的团队开发,Rx 是一个编程模型,目标是提供一致的编程接口,帮助开发者更方便的处理异步数据流,支持大部分流行语言。

Rx是一个函数库,让开发者可以利用可观察序列和 LINQ 风格查询操作符来编写异步和基于事件的程序,使用Rx,开发者可以用Observables 表示异步数据流,用 LINO 操作符查询异步数据流,用 Schedulers 参数化异步数据流的并发处理,Rx可以这样定义︰

Rx = Observables + LINQ + Schedulers

Rx 结合了观察者模式、迭代器模式和函数式编程的精华。UniRx 就是 Unity Reactive Extensions,是为 Unity 平台设计的响应式编程框架。

在游戏中,⼤部分的逻辑都是在时间上异步的。⽐如动画的播放、声⾳的播放、⽹络请求、资源加载/卸载、场景过渡等。

在实现 异步逻辑时经常⽤到⼤量的回调,最终随着项⽬的扩张导致传说中的”回调地狱”。
相对较好地⽅法则是使⽤消息/事件的发送,结果导致“消息满天⻜”,导致代码⾮常难以阅读。
⽽ UniRx 的出现刚好解决了这个问题,它介于回调和事件之间。

常用API

监听 mono 生命周期函数

Observable.EveryUpdate()                            // 开启 Update 的事件监听
          .Subscribe(_ =>                           // 订阅/处理事件
          {
              Debug.Log("Update");
          });

Observable.EveryLateUpdate()
          .Subscribe(_ =>
          {
              Debug.Log("LateUpdate");
          });

Observable.EveryFixedUpdate()
          .Subscribe(_ =>
          {
              Debug.Log("FixedUpdate");
          });

Observable.EveryEndOfFrame()
          .Subscribe(_ =>
          {
              Debug.Log("EndOfFrame");
          });

Observable.EveryApplicationFocus()
          .Subscribe(focuse =>
          {
              Debug.Log("Focus: " + focuse);
          });

如果在 Update ⽅法中,除了实现⿏标事件监听这个功能之外,还要实现其他的功能。那么 Update ⾥就会充斥着⼤量的状态判断等逻辑。代码⾮常不容易阅读。
⽽ UniRx 提供了⼀种编程思维,使得平时⼀些⽐较难实现的异步逻辑,使⽤ UniRx 轻松搞定,并且不失代码的可读性

参数下划线 _ 表示帧数,这里用不到就用 _ 表示

定时功能

Observable.Timer(TimeSpan.FromSeconds(5.0f))
          .Subscribe(_ =>
          {
              Debug.Log("do something");
          });

AddTo 与 Trigger

Observable.Timer(TimeSpan.FromSeconds(2.0f))
          .Subscribe(_ =>
          {
              Debug.Log("延时两秒"); 
          })
          .AddTo(this);

this.OnDestroyAsObservable()
    .Subscribe(_ =>
    {
        Debug.Log("OnDestroy");
    });

this.OnCollisionEnterAsObservable()
    .Subscribe(collision =>
    {
        Debug.Log("CollisionEnter");
    });

AddTo 会在 GameObject 上添加一个 ObservableDestroyTrigger 脚本监听它的 OnDestroy 事件,当 GameObject 销毁时也会销毁正在运行的 UniRx 任务,即 GameObject 销毁时,这个 Timer 也会销毁,避免空引用异常。

ObservableDestroyTrigger 是一个 Trigger,Trigger 大部分都是都是 XXXAsObsrevable 命名形式的。
在使用 Trigger 的 GameObject 上都会挂上对应的 ObservableXXXTrigger 的脚本,AddTo 这个 API 就是封装了 ObservableDestroyTrigger

Where,First 过滤

Observable.EveryUpdate()
          .Where(_ => Input.GetMouseButtonDown(0)) //进行一个鼠标是否点击的判断
          .First() 								   //只获取第一次点击事件
          .Subscribe(_ =>
          {
              Debug.Log("left mouse clicked");
          });

//也可以写成这样
Observable.EveryUpdate()
          .First(_ => Input.GetMouseButtonDown(0)) //只获取第一次点击事件
          .Subscribe(_ =>
          {
              Debug.Log("left mouse clicked");
          });

EveryUpdate 是事件的发布者。他会每帧会发送⼀个事件过来。
Subscribe 是事件的接收者,接收的是 EveryUpdate 发送的事件。
Where 则是在事件的发布者和接收者之间的⼀个过滤操作。会过滤掉不满⾜条件的事件
First 又进行了一个过滤

Merge

在 UniRx 世界中,任何东西都是以事件流的形式存在,EveryUpdate 和 Timer 都是开启一条事件流。UniRx 可以开启多条事件流,然后使用 Merge 合并。

private void Start()
{
    var leftClickEvents = Observable.EveryUpdate().Where(_ => Input.GetMouseButtonDown(0));
    var rightClickEvents = Observable.EveryUpdate().Where(_ => Input.GetMouseButtonDown(1));

    //点击左键或右键都会触发
    Observable.Merge(leftClickEvents, rightClickEvents)
              .Subscribe(_ =>
              {
                  Debug.Log("mouse clicked");
              });
}

WhenAll

当所有事件流都结束后触发

IEnumerator A()
{
    yield return new WaitForSeconds(1.0f);
    Debug.Log("A");
}

IEnumerator B()
{
    yield return new WaitForSeconds(2.0f);
    Debug.Log("B");
}

void Start()
{
    var aStream = Observable.FromCoroutine(_ => A());
    var bStream = Observable.FromCoroutine(_ => B());

    Observable.WhenAll(aStream, bStream)
              .Subscribe(_ =>
              {
                  Debug.Log("全部处理完");
              });
}

Start

开启线程

void Start()
{
    var threadAStream = Observable.Start(() =>
    {
        Thread.Sleep(TimeSpan.FromSeconds(1));
        return 10;
    });

    var threadBStream = Observable.Start(() =>
    {
        Thread.Sleep(TimeSpan.FromSeconds(3));
        return 10;
    });

    Observable.WhenAll(threadAStream, threadBStream)
              .ObserveOnMainThread() //转到主线程
              .Subscribe(results =>
              {
                  Debug.LogFormat("{0}:{1}", results[0], results[1]);
              });
}

UGUI 支持

public Button button;
public Toggle toggle;
public Image image;
public Slider slider;
public InputField inputField;

void Start()
{
    button.OnClickAsObservable()
          .Subscribe(_ =>
          {
              Debug.Log("button clicked");
          });
    
    toggle.OnValueChangedAsObservable()
          .Subscribe(on =>
          {
              Debug.LogFormat("toggle value changed: {0}", on);
          });

    image.OnDragAsObservable()
         .Subscribe(_ =>
        {
            Debug.Log("dragging");
        });

    slider.OnValueChangedAsObservable()
        .Subscribe(value =>
        {
            Debug.Log("slider: " + value);
        });

    inputField.OnValueChangedAsObservable()
        .Subscribe(value =>
        {
            Debug.Log("input: " + value);
        });
        
	inputField.OnEndEditAsObservable()
	    .Subscribe(value =>
	    {
	        Debug.Log("input: " + value);
	    });
}

查看源码,button.onClick 是 ButtonClickedEvent 类型,而 ButtonClickedEvent 继承自 UnityEvent, button.OnClickAsObservable 是对点击事件进行了封装,同理 UniRx 对其他组件的事件进行了封装

public static IObservable<Unit> OnClickAsObservable(this Button button)
{
    return button.onClick.AsObservable();
}
public static IObservable<Unit> AsObservable(this UnityEngine.Events.UnityEvent unityEvent)
{
    return Observable.FromEvent<UnityAction>(h => new UnityAction(h), h => unityEvent.AddListener(h), h => unityEvent.RemoveListener(h));
}
public static IObservable<Unit> FromEvent<TDelegate>(Func<Action, TDelegate> conversion, Action<TDelegate> addHandler, Action<TDelegate> removeHandler)
{
    return new FromEventObservable<TDelegate>(conversion, addHandler, removeHandler);
}

绑定两个组件

public Toggle mToggle;
public Button mButton;
public Slider mSlider;
public Text mText;


void Start()
{
    // 当 mToggle.isOn = true 时,mButton.interactable = true
    mToggle.OnValueChangedAsObservable().SubscribeToInteractable(mButton);

    // 滑动条变化时更新文本
    mSlider.OnValueChangedAsObservable()
        .SubscribeToText(mText, x => x.ToString());
}

Select

Select 是转换操作符,用于将一个事件源中的值转换成另一个类型的值

public Button buttonA;
public Button buttonB;

private void Start()
{
    // Select将空参数Unit转换为string
    buttonA.OnClickAsObservable()
        .Select(_ => "A")
        .Subscribe(btnId =>
        {
            Debug.LogFormat("button {0} clicked", btnId);
        });

    buttonB.OnClickAsObservable()
        .Select(_ => "B")
        .Subscribe(btnId =>
        {
            Debug.LogFormat("button {0} clicked", btnId);
        });
}
public ReactiveCollection<int> IntList = new ReactiveCollection<int>();

void Start()
{
	// Select 把每个值进行平方
    IntList.ObserveAdd()
        .Select(x => x.Value * x.Value)
        .Subscribe(value =>
        {
            Debug.Log("add:" + value);
        });

    // 添加后触发事件
    IntList.Add(10);
}

ReactiveProperty(响应式属性)

监听属性的变化,发送通知

// 0 是默认值,可序列化展示在面板上
public IntReactiveProperty Age = new IntReactiveProperty(0);
// 泛型写法,序列化很麻烦
public ReactiveProperty<string> Name = new ReactiveProperty<string>("Tom");

void Start()
{
    Age.Subscribe(age =>
    {
        Debug.Log("inner received age changed");
    });

    // 赋值后触发事件
    Age.Value = 10;
}

ReactiveCollection(响应式集合)

ReactiveCollection 类似于 List,监听集合的变化,发送通知

public ReactiveCollection<int> IntList = new ReactiveCollection<int>();

void Start()
{
    IntList.ObserveAdd().Subscribe(value => Debug.Log("add:" + value));
	IntList.ObserveRemove().Subscribe(value => Debug.Log("remove:" + value));
	IntList.ObserveCountChanged().Subscribe(count => Debug.Log("count:" + count));

    // 添加后触发事件
    IntList.Add(10);
}

ReactiveDictionary(响应式字典)

ReactiveDictionary<string, string> mLanguageCode = new ReactiveDictionary<string, string>
{
    {"cn","中文"},
    {"en","英文"}
};

void Start()
{
    mLanguageCode.ObserveAdd().Subscribe(addedLanguage => Debug.LogFormat("add:{0}", addedLanguage));
    mLanguageCode.ObserveRemove().Subscribe(removedLanguage => Debug.LogFormat("remove:{0}", removedLanguage));
    mLanguageCode.ObserveCountChanged().Subscribe(count => Debug.LogFormat("count:{0}", count));

    mLanguageCode.Add("jp", "日文");
    mLanguageCode.Remove("en");
}

MVP

在这里插入图片描述
View 依赖于 Presenter:View通过Presenter来获取数据并处理用户输入。
Presenter 依赖于 View 和 Model:负责处理View和Model之间的交互逻辑。
Model 不依赖于 View 或 Presenter:Model是独立的业务逻辑和数据层,它只负责数据的存储、处理和业务逻辑的实现。当Model的状态发生变化时,它可能会通过回调或事件通知Presenter。

// Presenter
public class EnemyExample : MonoBehaviour
{
    // View
    public Button attackBtn;
    public Text HPText;

    EnemyModel mEnemy = new EnemyModel(200);

    void Start()
    {
        // UGUI组件和响应式属性绑定
        attackBtn.OnClickAsObservable()
                 .Subscribe(_ =>
                 {
                     mEnemy.HP.Value -= 99;
                 });

        mEnemy.HP.SubscribeToText(HPText);

        mEnemy.IsDead
              .Where(isDead => isDead)
              .Select(isDead => !isDead)
              .SubscribeToInteractable(attackBtn);
    }
}

// Model
public class EnemyModel
{
    public ReactiveProperty<long> HP;
    public IReadOnlyReactiveProperty<bool> IsDead;

    public EnemyModel(long initialHP)
    {
        HP = new ReactiveProperty<long>(initialHP);
        IsDead = HP.Select(hp => hp <= 0).ToReactiveProperty();
    }
}

ReactiveCommand

用法类似命令模式,ReactiveCommand 实现 IReactiveCommand<T> 接口

public interface IReactiveCommand<T> : IObservable<T>
{
    IReadOnlyReactiveProperty<bool> CanExecute { get; } //内部使用,外部只读
    bool Execute(T parameter);  //外部调用的
}

当 CanExecute 为 true 时,调用 Execute,Command 才会执行,默认 CanExecute 为 true

void Start()
{
	// 默认 CanExecute 为 true
    var reactiveCommand = new ReactiveCommand();

    reactiveCommand.Subscribe(_ =>
    {
        Debug.Log("每次Execute成功调用后执行");
    });

    reactiveCommand.Execute();
    reactiveCommand.Execute();
}

泛型作为参数

void Start()
{
	
    var reactiveCommand = new ReactiveCommand<int>();

    reactiveCommand.Where(x => x % 2 == 0).Subscribe(x => Debug.LogFormat("{0}是偶数", x));
    reactiveCommand.Where(x => x % 2 != 0).Timestamp().Subscribe(x => Debug.LogFormat("{0}是奇数,{1}", x.Value, x.Timestamp));

    reactiveCommand.Execute(10);
    reactiveCommand.Execute(11);
}

设置事件源

private void Start()
{
	
    var mouseDownStream = Observable.EveryUpdate().Where(_ => Input.GetMouseButtonDown(0)).Select(_ => true);
    var mouseUpStream = Observable.EveryUpdate().Where(_ => Input.GetMouseButtonUp(0)).Select(_ => false);

    var isMouseUp = Observable.Merge(mouseUpStream, mouseDownStream);
    // 设置事件源和 CanExecute 为 false
    var reactiveCommand = new ReactiveCommand(isMouseUp, false);

    reactiveCommand.Subscribe(_ =>
    {
        Debug.Log("鼠标按下");
    });

    Observable.EveryUpdate().Subscribe(_ =>
    {
        reactiveCommand.Execute();
    });
}

AsyncOperation(异步操作)

加载资源

void Start()
{
    Resources.LoadAsync<GameObject>("资源名称").AsAsyncOperationObservable()
    .Subscribe(resourceRequest =>
    {
        Instantiate(resourceRequest.asset);
    });
}

加载场景

void Start()
{
    // 加载进度
    var progressObservable = new ScheduledNotifier<float>();
    // 加载 Build Settings 中第 0 个场景
    SceneManager.LoadSceneAsync(0).AsAsyncOperationObservable(progressObservable)
                .Subscribe(_ =>
                {
                    Debug.Log("load done");
                });

    progressObservable.Subscribe(progress =>
    {
        Debug.LogFormat("加载了:{0}%", progress * 100);
    });
}

类 LINQ 操作符

Distinct

Distinct 意思是清晰的,不同的,用于查询不重复的结果集

List<string> list = new List<string>
{
    "张三", "张三", "李四"
};

list.ToObservable()
    .Distinct()
    .Subscribe(name =>
    {
        Debug.Log(name);
    });

Last

取列表最后一个元素,和 First 相反

class Student
{
    public string Name;
    public int Age;
}

void Start()
{
    List<Student> students = new List<Student>
    {
        new Student{ Name = "张三", Age = 10 },
        new Student{ Name = "张三", Age = 15 },
        new Student{ Name = "李四", Age = 21 },
    };

    students.ToObservable()
        .Last(student => student.Name == "张三")
        .Subscribe(student =>
        {
            Debug.Log(student.Age);
        });
}

SelectMany

将一个序列中的每个元素投影到另一个序列,并将这些序列合并为一个单一的序列。具体来说,它会对每个元素进行遍历处理,然后将结果序列合并起来

List<string> list = new List<string>
{
    "123", "456"
};

list.ToObservable()
    .SelectMany(c => c)
    .Subscribe(c =>
    {
        Debug.Log(c);
    });

输出

1
2
3
4
5
6

还可以实现协程的顺序执行

void Start()
{
    var aStream = Observable.FromCoroutine(A);
    var bStream = Observable.FromCoroutine(B);
    aStream.SelectMany(bStream)
           .Subscribe(_ => Debug.Log("A,B结束"));
}

IEnumerator A()
{
    yield return new WaitForSeconds(1f);
    Debug.Log("A");
}

IEnumerator B()
{
    yield return new WaitForSeconds(1f);
    Debug.Log("B");
}

未完待续

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1829812.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Apipost模拟HTTP客户端

模拟HTTP客户端的软件有很多&#xff0c;其中比较著名的就有API-FOX、POSTMAN。 相信很多小伙伴都使用POSTMAN。这篇博客主要介绍Apipost的原因是&#xff0c;Apipost无需下载&#xff0c;具有网页版。 APIFOX的站内下载&#xff1a; Api-Fox&#xff0c;类似于PostMan的软件…

关于HTTP劫持,该如何理解、防范和应对

一、引言 HTTP劫持&#xff08;HTTP Hijacking&#xff09;是一种网络安全威胁&#xff0c;它发生在HTTP通信过程中&#xff0c;攻击者试图通过拦截、篡改或监控用户与服务器之间的数据流量&#xff0c;以达到窃取敏感信息或执行恶意操作的目的。今天我们就来详细了解HTTP劫持…

嵌入式实训day5

1、 from machine import Pin import time # 定义按键引脚控制对象 key1 Pin(27,Pin.IN, Pin.PULL UP) key2 Pin(26,Pin.IN, Pin.PULL UP)led1 Pin(15,Pin.ouT, value0) led2 Pin(2,Pin.ouT, value0) led3 Pin(0,Pin.ouT, value0) # 定义key1按键中断处理函数 def key1 ir…

JavaFX 图像视图

JavaFX ImageView 控件可以在 JavaFX GUI 中显示图像。ImageView 控件必须添加到场景图中才能可见。JavaFX ImageView 控件由类表示 javafx.scene.image.ImageView。 创建一个 ImageView 通过创建类的实例来创建 ImageView 控件实例ImageView。类的构造函数ImageView需要一个…

【JKI SMO】框架讲解(二)

JKI State Machine 讲解 将JKI State Machine 模板拖曳到程序框图中&#xff0c; 如下图&#xff0c; 此模板会默认放置一个OK按钮在前面板中&#xff0c;用于提示用户如何增加一个简单的用户事件去使用此框架。 “Event Structure”&#xff0c;Idle&#xff1a;此分支可以设…

---异常---

我们在运行程序时总遇到各种与报错&#xff0c;数组越界&#xff0c;空指针的引用&#xff0c;这些在java中都称为异常 对于不同的错误都具有一个与他对应的异常类来秒描述 这是对于数组越界这个类里有的方法&#xff0c;这些是描述异常的 在java中有一个完整的描述异常的类的…

JavaFX 节点

JavaFX Node类javafx.scene.Node是添加到JavaFX 场景图的所有组件 的基类&#xff08;超类&#xff09; 。JavaFX Node 类是抽象的&#xff0c;因此你只需将 Node 类的子类添加到场景图中。场景图中的所有 JavaFX Node 实例共享一组由 JavaFX Node 类定义的公共属性。本 JavaFX…

运行vue3项目相关报错

1. VSCode打开TSVue3项目很多地方报错 报错内容 几乎所有文件都会出现未知飘红 error Delete CR prettier/prettier报错原因 插件冲突&#xff0c;Windows系统回车换行符与MAC不一致&#xff08;所以这个问题Windows系统才会出现&#xff09; 解决 需要安装Vue - Official…

掌握高等数学、线性代数、概率论所需数学知识及标题建议

在数学的广袤领域中&#xff0c;高等数学、线性代数和概率论作为三大核心分支&#xff0c;不仅在理论研究中占据重要地位&#xff0c;更在实际应用中发挥着举足轻重的作用。为了深入理解和掌握这三门学科&#xff0c;我们需要掌握一系列扎实的数学知识。 高等数学所需数学知识 …

vitepress搭建的博客系统cdn引入github discussions评论系统

github仓库必须是公开的。 按照CDN的方式引入 打开discussions模块 安装giscus app 配置giscus 就是刚安装了giscus app的仓库 页面往下走&#xff0c;生成了代码&#xff1a; 配置vitepress 采用了CDN的方式引入 使用web component 随便找个地方试试组件 效果 有了…

Web3失败下互联网的未来转型之路

互联网的消亡已不再是夸大其词的说法。在大型科技巨头和生成式AI的推动下&#xff0c;网络的死亡螺旋正在加速&#xff0c;就像希腊神话中的蛇怪&#xff0c;象征着自我吞噬与永生。互联网已经开始自我喂养&#xff0c;并吐出最糟糕的部分供我们消费。 没有价值 Web3未能提供…

LeetCode43.字符串相乘【大整数相乘】

LeetCode刷题记录 文章目录 &#x1f4dc;题目描述&#x1f4a1;解题思路 &#x1f4dc;题目描述 给定两个以字符串形式表示的非负整数 num1 和 num2&#xff0c;返回 num1 和 num2 的乘积&#xff0c;它们的乘积也表示为字符串形式。 注意&#xff1a;不能使用任何内置的 Big…

Windows10安装Docker Desktop(实操步骤版)

1&#xff0c;下载Docker Desktop 官网下载地址&#xff1a; https://desktop.docker.com/win/stable/amd64/Docker%20Desktop%20Installer.exe 国内镜像下载地址&#xff08;本人下载这个&#xff09;&#xff1a; https://smartidedl.blob.core.chinacloudapi.cn/docker/2…

黄仁勋最新建议:找到一门技艺,用一生去完善、磨炼!

“你可能会找到你的英伟达。我希望你们将挫折视为新的机遇。” 黄仁勋职业生涯中最大的教诲并非来自导师或科技公司 CEO&#xff0c;而是来自他在国际旅行时遇到的一位园丁。 近日在加州理工学院毕业典礼上发表演讲时&#xff0c;黄仁勋向毕业生分享了自己在日本京都的小故事。…

windows11 x64 23H2 企业纯净版2024.6.16

闲来无事试安装了下da_nao_yan的 【6月12日更新】Windows11 22631.3737企业版 23H2 自用优化版 &#xff08;原版地址&#xff1a;https://bbs.pcbeta.com/viewthread-1985546-1-1.html&#xff09;&#xff0c;感觉比原版流畅多了&#xff0c;重新按照自己习惯封装了下&#x…

!力扣105. 从前序与中序遍历序列构造二叉树

给定两个整数数组 preorder 和 inorder &#xff0c;其中 preorder 是二叉树的先序遍历&#xff0c; inorder 是同一棵树的中序遍历&#xff0c;请构造二叉树并返回其根节点。 示例 1: 输入: preorder [3,9,20,15,7], inorder [9,3,15,20,7] 输出: [3,9,20,null,null,15,7] …

Postgre 调优工具pgBadger部署

一&#xff0c;简介&#xff1a; pgBadger&#xff08;日志分析器&#xff09;类似于oracle的AWR报告&#xff08;基于1小时&#xff0c;一天&#xff0c;一周&#xff0c;一月的报告&#xff09;&#xff0c;以图形化的方式帮助DBA更方便的找到隐含问题。 pgbadger是为了提高…

springboot集成shardingsphere-分库分表

导入maven依赖&#xff0c;如下 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><a…

【模块缝合】【NIPS 2021】MLP-Mixer: An all-MLP Architecture for Vision

文章目录 简介代码&#xff0c;from&#xff1a;https://github.com/huggingface/pytorch-image-models【多看看成熟仓库的代码】MixerBlock paper and code&#xff1a; https://paperswithcode.com/paper/mlp-mixer-an-all-mlp-architecture-for-vision#code 简介 这个转置…

jeecg快速启动(附带本地运行可用版本下载)

版本整理&#xff08;windows x64位&#xff09;&#xff1a; redis&#xff1a;3.0.504 MYSQL&#xff1a;5.7 Maven&#xff1a;3.9.4(setting文件可下载) Nodejs&#xff1a;v16.20.2&#xff08;建议不要安装默认路径下&#xff0c;如已安装在c盘&#xff0c;运行yarn报…