问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

Unity官方开发者社区:详解UniRx插件的使用

创作时间:
2025-03-15 12:04:57
作者:
@小白创作中心

Unity官方开发者社区:详解UniRx插件的使用

引用
CSDN
1.
https://unity.csdn.net/677b6d2861f6a61b9464ef74.html

UniRx是Unity官方提供的一个响应式编程库,它通过观察者模式简化了事件和异步操作的管理。本文将详细介绍UniRx的核心概念、常用API,并通过具体代码示例展示如何使用UniRx改写传统的协程实现。

一、定义

UniRx是一个用于Unity的响应式编程库,它通过响应式编程的模式来简化事件和异步操作的管理。

二、核心组成

  • Observable(可观察的):表示一个数据流或事件流。
  • Operators(操作符):用于转换、组合和过滤Observable的工具。
  • Subscription(订阅):观察者订阅一个Observable来接收事件。

三、常用API

(1)常用UI事件

button.OnClickAsObservable()用于监听UI中按钮被按下事件。

// UniRx 方式(Observable)
button.OnClickAsObservable()
    .Subscribe(_ => Debug.Log("Button clicked"));

_ =>是一个简化的Lambda表达式语法,表示在这个订阅的过程中不需要使用传入的参数。如果关心事件传递的参数,可以使用其他名称来接收这个参数。比如:

button.OnClickAsObservable()
    .Subscribe(button => Debug.Log("Button clicked: " + button.name));

(2)操作符

  • Select:用于映射Observable中的数据
Observable.Range(1, 5)
    .Select(x => x * 2)
    .Subscribe(x => Debug.Log(x));
  • Where:用于过滤Observable中的数据
Observable.Range(1, 5)
    .Where(x => x % 2 == 0)
    .Subscribe(x => Debug.Log(x));  // 输出:2, 4
  • TakeWhile:当条件不满足时停止流

(3)订阅与取消订阅

  • Subscribe:订阅Observable,事件发生后,执行某个操作
Observable.Range(1, 5)
    .Subscribe(x => Debug.Log(x));
  • AddTo:将订阅与MonoBehaviour或GameObject生命周期绑定,自动取消订阅
IDisposable subscription = Observable.EveryUpdate()
    .Subscribe(_ => Debug.Log("Every frame"))
    .AddTo(this);

四、优势

UniRx避免了传统回调函数的复杂性,并且可以通过AddTo()自动管理生命周期,有效防止内存泄漏。

五、注意

订阅应该放在Start()中,而不是Update()中,因为后者意味着每一帧都会创建一个新的订阅。但大多数的订阅只需要在对象被激活时创建。

六、使用UniRx改写协程

源代码

每帧检测是否满足调用协程的条件:

void Update()
{
    //Enable lazer
    if (Input.GetMouseButtonDown(0))
    {
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        //如果射线检测到了Enemy层
        if (Physics.Raycast(ray, out RaycastHit hit, Mathf.Infinity,layer))
        {
            targetEnemy = hit.collider.transform.root.gameObject;
            Debug.Log($"目标敌军是:{targetEnemy.name}");
            StartCoroutine(AimAtEnemy());
        }
        
    }
}

协程实现:

private IEnumerator AimAtEnemy()
{
    // 确保 MultiHingeJoint 实例存在
    if (MultiHingeJoint.Instance == null)
    {
        Debug.LogError("MultiHingeJoint 实例不存在");
        yield break;
    }
    // 瞄准目标
    bool isYAligned = false;
    bool isXAligned = false;
    MultiHingeJoint.Instance.RotateAroundY(aimPart1, FirePoint, targetEnemy);
    // 等待炮筒对准目标
    while (!isYAligned )
    {
        // 检查当前是否已经对准
        isYAligned = CheckIfAimed(aimPart1, targetEnemy.transform, "Y");
        yield return null; // 等待下一帧
    }
    MultiHingeJoint.Instance.RotateAroundX(aimPart2, FirePoint, targetEnemy);
    while (!isXAligned)
    {
        isXAligned = CheckIfAimed(aimPart2, targetEnemy.transform, "X");
        yield return null; // 等待下一帧
    }
    Debug.Log("炮筒对准完成,发射激光!");
}

代码改写

判断逻辑改写:

void Start()
{
    // 使用 UniRx 订阅每帧更新
    Observable.EveryUpdate()
        .Where(_ => Input.GetMouseButtonDown(0))  // 检测鼠标左键点击
        .Select(_ => Camera.main.ScreenPointToRay(Input.mousePosition)) // 计算射线
        .Where(ray => Physics.Raycast(ray, out RaycastHit hit, Mathf.Infinity, layer)) // 射线检测是否击中目标
        .Subscribe(hit =>
        {
            targetEnemy = hit.collider.transform.root.gameObject;  // 获取目标敌人
            Debug.Log($"目标敌军是:{targetEnemy.name}");
            AimAtEnemy().Subscribe().AddTo(this); // 启动瞄准过程
        })
        .AddTo(this); // 确保在对象销毁时取消订阅
}

协程部分改写:

private IObservable<Unit> AimAtEnemy()
{
    // 确保 MultiHingeJoint 实例存在
    if (MultiHingeJoint.Instance == null)
    {
        Debug.LogError("MultiHingeJoint 实例不存在");
        return Observable.Empty<Unit>(); // 返回空的 Observable,表示终止操作
    }
    // 瞄准目标
    return Observable.Create<Unit>(observer =>
    {
        bool isYAligned = false;
        bool isXAligned = false;
        // 旋转炮筒对准 Y 轴
        MultiHingeJoint.Instance.RotateAroundY(aimPart1, FirePoint, targetEnemy);
        
        // 等待炮筒对准 Y 轴
        Observable.EveryUpdate()
            .Where(_ => !isYAligned)  // 只在未对准时继续检查
            .TakeWhile(_ => !isYAligned) // 直到条件不满足时停止
            .Subscribe(_ =>
            {
                isYAligned = CheckIfAimed(aimPart1, targetEnemy.transform, "Y");
            });
        // 旋转炮筒对准 X 轴
        MultiHingeJoint.Instance.RotateAroundX(aimPart2, FirePoint, targetEnemy);
        
        // 等待炮筒对准 X 轴
        Observable.EveryUpdate()
            .Where(_ => !isXAligned) // 只在未对准时继续检查
            .TakeWhile(_ => !isXAligned) // 直到对准完成
            .Subscribe(_ =>
            {
                isXAligned = CheckIfAimed(aimPart2, targetEnemy.transform, "X");
            });
        // 等待两个轴都对准完成
        Observable.EveryUpdate()
            .Where(_ => isYAligned && isXAligned)  // 检查是否两个轴都对准
            .Take(1)  // 只需要触发一次
            .Subscribe(_ =>
            {
                Debug.Log("炮筒对准完成,发射激光!");
                EmitLaser();  // 发射激光
                observer.OnCompleted();  // 结束 Observable 操作
            });
        return Disposable.Empty;  // 结束 Observable
    });
}

知识点

  • Observable.Create:用来创建一个自定义的Observable。Observable.Create让我们能够手动控制数据流的发出。observer是一个对象,用来控制Observable的状态。
  • Disposable.Empty:表示返回一个空的Disposable对象,这意味着没有其他资源需要清理。这个方法在Observable执行完后自动终止。一般和create成对存在。
  • observer.OnCompleted():通知观察者,表示Observable数据流已经完成。这意味着所有数据已经发送完毕,没有更多的数据将会被发出。

更多关于UniRx的详细内容可以参考:知乎专栏

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号