1. 负责数据绑定的"轻量级 ReactiveProperty"
cs
using System;
// 极其轻量、0 GC 负担的响应式属性(示意版)
public class ReactiveProperty<T>
{
private T _value;
private Action<T> _onValueChanged;
public T Value
{
get => _value;
set
{
// 如果值没变,就不触发事件(防抖)
if (Object.Equals(_value, value)) return;
_value = value;
_onValueChanged?.Invoke(_value); // 通知所有订阅者
}
}
public ReactiveProperty(T initialValue = default)
{
_value = initialValue;
}
// 提供给 UI 绑定的方法,返回一个可解绑的 Action
public IDisposable Subscribe(Action<T> action)
{
_onValueChanged += action;
action?.Invoke(_value); // 订阅时立刻推送一次当前值,让 UI 初始化
return new Unsubscriber(() => _onValueChanged -= action);
}
// 内部类用于优雅解绑
private class Unsubscriber : IDisposable
{
private Action _unsubscribe;
public Unsubscriber(Action unsubscribe) => _unsubscribe = unsubscribe;
public void Dispose() => _unsubscribe?.Invoke();
}
}
Step 1: 编写 ViewModel(数据与逻辑中心)
这里包含了轻量级的状态数据,以及使用 UniTask 编写的线性业务流程。
cs
using Cysharp.Threading.Tasks;
using UnityEngine;
using System.Threading;
public class ChestViewModel
{
// 💡 纯粹的数据流:只负责保存状态,并在改变时向外广播
public ReactiveProperty<string> ChestStateText { get; } = new ReactiveProperty<string>("点击开启宝箱");
public ReactiveProperty<bool> IsButtonInteractable { get; } = new ReactiveProperty<bool>(true);
// 💡 纯粹的控制流:使用 UniTask 串联整个异步逻辑
// 传入 CancellationToken 是为了防止玩家在开箱中途关掉界面导致的空引用报错
public async UniTask OpenChestAsync(CancellationToken token)
{
// 1. 改变状态:锁定按钮,更新文本
IsButtonInteractable.Value = false;
ChestStateText.Value = "宝箱正在开启中...";
try
{
// 2. 等待宝箱抖动动画(等待 1 秒)
// 魔法:Delay 不会产生任何 GC,且受 Unity TimeScale 影响
await UniTask.Delay(1000, cancellationToken: token);
// 3. 模拟网络请求战利品(等待 0.5 秒)
ChestStateText.Value = "正在连接服务器...";
await UniTask.Delay(500, cancellationToken: token);
string loot = "SSR 传说级大剑!🗡️"; // 假设这是网络返回的数据
// 4. 最终展示结果
ChestStateText.Value = $"开启成功!获得:{loot}";
}
catch (OperationCanceledException)
{
// 玩家中途关闭了界面,任务被优雅取消,不会有任何报错!
Debug.Log("开箱流程被中断。");
}
finally
{
// 无论成功还是被取消,最终都可以执行清理逻辑
IsButtonInteractable.Value = true;
}
}
}
Step 2: 编写 View(视图表现层)
UI 脚本现在变得 极其清爽 ,它只做两件事:把数据绑定到 UI 组件上,以及把用户的点击事件转发给 ViewModel。
cs
using UnityEngine;
using UnityEngine.UI;
using Cysharp.Threading.Tasks;
using System.Threading;
public class ChestView : MonoBehaviour
{
public Text stateText;
public Button openButton;
private ChestViewModel _viewModel;
private CancellationTokenSource _cts;
void Start()
{
_viewModel = new ChestViewModel();
_cts = new CancellationTokenSource(); // 用于管理异步任务的生命周期
// 🌟 1. UI 数据绑定:将 ViewModel 的数据连接到 UI 元素
_viewModel.ChestStateText.Subscribe(text => stateText.text = text);
_viewModel.IsButtonInteractable.Subscribe(interactable => openButton.interactable = interactable);
// 🌟 2. 事件转发:按钮点击触发 ViewModel 的异步流程
openButton.onClick.AddListener(() =>
{
// Fire and Forget (触发并忘记),启动异步任务
_viewModel.OpenChestAsync(this.GetCancellationTokenOnDestroy()).Forget();
});
}
void OnDestroy()
{
// View 销毁时,取消所有正在进行的异步任务,防止内存泄漏或空引用!
_cts.Cancel();
_cts.Dispose();
}
}