響應式編程入門教程第三節:ReactiveCommand 與 UI 交互

響應式編程入門教程第一節:揭秘 UniRx 核心 - ReactiveProperty - 讓你的數據動起來!

響應式編程入門教程第二節:構建 ObservableProperty<T> — 封裝 ReactiveProperty 的高級用法

響應式編程入門教程第三節:ReactiveCommand 與 UI 交互

響應式編程入門教程第四節:響應式集合與數據綁定

在前面的教程中,我們了解了 ReactiveProperty 如何幫助我們管理和響應數據的變化。現在,我們將步入響應式編程在 Unity UI 交互中的另一個核心角色:ReactiveCommand。它不僅僅是一個簡單的命令模式實現,更是將命令的執行與 UI 狀態、異步操作以及數據流緊密結合的強大工具。


1. 傳統命令模式的局限性與響應式需求

在傳統的 Unity UI 開發中,我們經常使用按鈕的 onClick.AddListener() 來觸發某個操作。當操作邏輯變得復雜,比如需要判斷前置條件(玩家是否有足夠的金幣施放技能)、操作是異步的(網絡請求),或者需要根據操作狀態更新 UI(按鈕禁用、加載動畫),我們往往需要寫大量的條件判斷和狀態管理代碼。

考慮一個簡單的例子:一個技能按鈕。

  • 點擊后施放技能。
  • 施放技能需要消耗能量。
  • 能量不足時,按鈕應該禁用。
  • 技能施放過程中,按鈕也應該禁用,并顯示冷卻時間。

傳統實現中,這些邏輯會散布在按鈕的點擊回調、Update 函數、以及各種狀態變量中,導致代碼耦合、難以維護和測試。

響應式編程的目標就是解決這類問題:將狀態的變化、事件的發生都視為數據流,然后通過操作符對其進行轉換、組合和響應。ReactiveCommand 正是這種理念在命令執行上的體現。


2. ReactiveCommand 核心概念

ReactiveCommand 本質上是一個 ICommand 的響應式實現。它包含兩個核心功能:

  • 執行命令: 當命令被觸發時,執行預設的邏輯。
  • 判斷能否執行: 提供一個 IObservable<bool> 流,該流的值決定了命令當前是否可執行。當此流的值變為 false 時,任何綁定到該命令的 UI 元素(如按鈕)會自動禁用;變為 true 時則會啟用。

讓我們看看它的基本構造:

public class SkillSystem : MonoBehaviour
{// ReactiveProperty 用于管理玩家能量public ReactiveProperty<int> PlayerEnergy = new ReactiveProperty<int>(100);// ReactiveCommand 用于施放技能public ReactiveCommand ReleaseSkillCommand { get; private set; }private void Awake(){// 1. 創建 ReactiveCommand// 參數是一個 IObservable<bool>,用于決定命令是否可執行// 這里表示當 PlayerEnergy.Value >= 10 時,命令可執行ReleaseSkillCommand = PlayerEnergy.Select(energy => energy >= 10) // 根據能量值判斷是否可施放.ToReactiveCommand(); // 將 IObservable<bool> 轉換為 ReactiveCommand// 2. 訂閱命令的執行// 當命令被執行時,扣除能量并打印日志ReleaseSkillCommand.Subscribe(_ =>{PlayerEnergy.Value -= 10;Debug.Log("技能施放成功!當前能量:" + PlayerEnergy.Value);}).AddTo(this); // 生命周期管理:當 GameObject 銷毀時,自動取消訂閱// 3. 訂閱命令的可執行狀態變化// 這可以用于在控制臺觀察按鈕的禁用啟用狀態,實際UI綁定后會自動處理ReleaseSkillCommand.CanExecute.Subscribe(canExecute => Debug.Log("技能按鈕是否可用: " + canExecute)).AddTo(this);}// 假設這是 UI 按鈕的點擊事件處理器// 我們會直接將按鈕綁定到 ReleaseSkillCommand,所以這個方法通常不需要手動調用public void OnSkillButtonClick(){// 手動執行命令 (通常通過 UI 綁定自動觸發)ReleaseSkillCommand.Execute();}
}

在上面的例子中,ReleaseSkillCommand 的可執行性完全由 PlayerEnergy 的值決定。當 PlayerEnergy.Value 低于 10 時,ReleaseSkillCommand.CanExecute 流會發出 false,此時任何綁定到此命令的 UI 元素將自動禁用。


3. UI 按鈕綁定與數據驅動

這是 ReactiveCommand 最直觀的應用場景。UniRx 提供了方便的擴展方法,可以直接將 ButtonToggle 等 UI 元素綁定到 ReactiveCommand

步驟:

  1. 在 Unity Inspector 中創建一個 UI Button
  2. 確保你的腳本 (SkillSystem) 掛載在一個 GameObject 上。
  3. Button 拖拽到腳本中需要綁定的地方(如果使用 Inspector 綁定)。

代碼實現:

using UnityEngine;
using UnityEngine.UI;
using UniRx;
using UniRx.Triggers; // 用于將 UI 事件轉換為 Observablepublic class SkillSystemWithUI : MonoBehaviour
{public ReactiveProperty<int> PlayerEnergy = new ReactiveProperty<int>(100);public ReactiveCommand ReleaseSkillCommand { get; private set; }public Button skillButton; // 在 Inspector 中拖拽賦值public Text energyText; // 用于顯示能量值的Textprivate void Awake(){// 創建 ReactiveCommandReleaseSkillCommand = PlayerEnergy.Select(energy => energy >= 10).ToReactiveCommand();// 訂閱命令的執行邏輯ReleaseSkillCommand.Subscribe(_ =>{PlayerEnergy.Value -= 10;Debug.Log("技能施放成功!當前能量:" + PlayerEnergy.Value);}).AddTo(this);// UI 綁定:將按鈕的點擊事件與 ReleaseSkillCommand 關聯// BindTo 擴展方法會自動處理按鈕的禁用啟用狀態skillButton.onClick.AsObservable() // 將 onClick 事件轉換為 Observable.SubscribeWithState(ReleaseSkillCommand, (x, command) => command.Execute()) // 當點擊時執行命令.AddTo(this);// 或者更簡潔的方式,直接使用 BindTo:// releaseSkillCommand.BindTo(skillButton).AddTo(this);// 注意:BindTo(Button) 會將 Command 的可執行性綁定到 Button 的 Interactable 屬性,// 并且 Button 點擊時會自動執行 Command。ReleaseSkillCommand.BindTo(skillButton).AddTo(this);// 綁定能量值到 TextPlayerEnergy.SubscribeToText(energyText, energy => $"能量: {energy}").AddTo(this);}
}

skillButton.BindTo(ReleaseSkillCommand).AddTo(this); 這一行中,UniRx 幫我們做了兩件事:

  1. ReleaseSkillCommand.CanExecute 的值變化時,自動更新 skillButton.interactable 屬性。
  2. skillButton 被點擊時,自動調用 ReleaseSkillCommand.Execute()

這樣一來,按鈕的禁用啟用狀態完全由 PlayerEnergy 的值驅動,我們無需手動在 Update 或其他地方去修改按鈕的 interactable 屬性。這就是數據驅動 UI 的魅力!


4. 異步命令與命令的禁用

很多時候,我們的命令執行會涉及到異步操作,比如網絡請求、加載資源、播放動畫等。ReactiveCommand 能夠很好地處理這些異步場景,并且在異步操作進行時,自動將命令標記為不可執行。

核心機制: ToReactiveCommand 的重載方法可以接受一個 IObservable<bool> 作為 canExecute 源,而當命令被執行時,它會內部管理一個 IsExecutingReactiveProperty。當 Execute 被調用時,IsExecuting 變為 true,直到內部訂閱的 IObservable 完成(Next 或 Error 或 Complete),IsExecuting 才變為 false。我們可以將這個 IsExecuting 結合到 canExecute 的邏輯中。

using UnityEngine;
using UnityEngine.UI;
using UniRx;
using System;
using System.Threading.Tasks; // 為了使用 Taskpublic class AsyncSkillSystem : MonoBehaviour
{public ReactiveProperty<int> PlayerEnergy = new ReactiveProperty<int>(100);public ReactiveCommand AsyncSkillCommand { get; private set; }public Button asyncSkillButton;public Text energyText;public Text statusText; // 用于顯示異步操作狀態private void Awake(){// 組合條件:能量充足 并且 當前命令沒有在執行var canExecuteSource = PlayerEnergy.Select(energy => energy >= 10);// 創建異步 ReactiveCommand// 注意這里 ToReactiveCommand() 的重載,它會自動跟蹤內部異步操作的執行狀態AsyncSkillCommand = canExecuteSource.ToReactiveCommand(); // 不傳入參數,內部會自動處理 IsExecuting// 訂閱命令的執行邏輯 (異步操作)// 注意:這里我們使用 SelectMany 來處理異步操作AsyncSkillCommand.SelectMany(_ => SimulateAsyncTask()) // 當命令執行時,觸發異步任務.Subscribe(_ => { /* 異步任務完成 */ },ex => Debug.LogError("異步技能施放失敗: " + ex.Message) // 錯誤處理).AddTo(this);// 監聽命令的執行狀態 (用于顯示加載動畫或禁用其他UI)AsyncSkillCommand.IsExecuting.Subscribe(isExecuting =>{statusText.text = isExecuting ? "技能冷卻中..." : "準備就緒";asyncSkillButton.interactable = !isExecuting && AsyncSkillCommand.CanExecute.Value; // 確保在異步執行時不禁用按鈕}).AddTo(this);// 將 AsyncSkillCommand 綁定到按鈕// BindTo 會自動處理 CanExecute 和 IsExecuting 的組合邏輯,// 使得按鈕在能量不足或異步操作進行中時自動禁用AsyncSkillCommand.BindTo(asyncSkillButton).AddTo(this);// 綁定能量值到 TextPlayerEnergy.SubscribeToText(energyText, energy => $"能量: {energy}").AddTo(this);}// 模擬一個異步任務,例如網絡請求或耗時計算private async UniTask<Unit> SimulateAsyncTask(){PlayerEnergy.Value -= 10;Debug.Log("開始施放異步技能...");await UniTask.Delay(TimeSpan.FromSeconds(2)); // 模擬2秒的延遲Debug.Log("異步技能施放完成!當前能量:" + PlayerEnergy.Value);return Unit.Default; // Unit.Default 表示一個空值,類似于 void}
}

在這個例子中:

  1. AsyncSkillCommand 的可執行性不僅取決于 PlayerEnergy,還隱式地取決于它內部的 IsExecuting 狀態。
  2. 當點擊按鈕觸發 AsyncSkillCommand.Execute() 時,SimulateAsyncTask() 會被調用。
  3. SimulateAsyncTask() 執行期間(2秒),AsyncSkillCommand.IsExecuting 會為 true,導致 asyncSkillButton 自動禁用,并且 statusText 顯示“技能冷卻中…”。
  4. SimulateAsyncTask() 完成后,AsyncSkillCommand.IsExecuting 變回 false,按鈕和狀態文本恢復正常。

這種處理異步命令的方式極大地簡化了狀態管理代碼,讓開發者可以專注于業務邏輯本身。


5. ReactiveCommand 的高級用法與注意事項
  • 組合多個 CanExecute 源: 你可以通過 CombineLatestZip 等操作符,組合多個 IObservable<bool> 來決定一個 ReactiveCommand 的可執行性。例如,一個按鈕可能需要同時滿足“玩家在線”和“有足夠的金幣”兩個條件才能點擊。

    public ReactiveProperty<bool> IsOnline = new ReactiveProperty<bool>(true);
    public ReactiveProperty<int> Gold = new ReactiveProperty<int>(50);private void CreateCombinedCommand()
    {var canExecuteSource = IsOnline.CombineLatest(Gold, (online, gold) => online && gold >= 20);var purchaseCommand = canExecuteSource.ToReactiveCommand();purchaseCommand.Subscribe(_ =>{Gold.Value -= 20;Debug.Log("購買成功!");}).AddTo(this);purchaseCommand.BindTo(GetComponent<Button>()).AddTo(this);
    }
    
  • 指定執行參數: ReactiveCommand<TParam> 允許你在執行命令時傳入參數。

    public ReactiveCommand<int> SpendGoldCommand { get; private set; }private void CreateSpendGoldCommand()
    {SpendGoldCommand = PlayerEnergy.Select(energy => energy > 0) // 假設只要有能量就能執行此命令.ToReactiveCommand<int>(); // 指定參數類型為 intSpendGoldCommand.Subscribe(amount =>{PlayerEnergy.Value -= amount;Debug.Log($"花費了 {amount} 能量。當前能量:{PlayerEnergy.Value}");}).AddTo(this);// 可以在 UI 元素的回調中調用:// SpendGoldCommand.Execute(10); // 花費10能量
    }
    
  • 錯誤處理: 如果命令的訂閱鏈中發生錯誤,錯誤會傳播并可能導致訂閱終止。你可以使用 CatchOnErrorResumeNext 等操作符來處理這些錯誤,保持命令的健壯性。

  • 生命周期管理: 再次強調,務必使用 AddTo(this)CompositeDisposable 來管理訂閱的生命周期,避免內存泄漏。當 GameObject 被銷毀時,所有通過 AddTo(this) 添加的訂閱都會自動取消。


6. 總結與展望

ReactiveCommand 極大地提升了 Unity UI 交互開發的效率和代碼質量。它提供了一種聲明式的方式來管理命令的可執行性,優雅地處理了異步操作,并與 UI 元素進行了無縫集成。通過將命令視為數據流的一部分,我們能夠構建更加響應式、可維護和可測試的 Unity 應用程序。

在下一篇教程中,我們將探討 響應式集合(ReactiveCollection/ReactiveDictionary),以及它們如何與 UI 列表(如 ScrollView)結合,實現動態數據的自動綁定和刷新,進一步解鎖數據驅動 UI 的潛力。

響應式編程入門教程第一節:揭秘 UniRx 核心 - ReactiveProperty - 讓你的數據動起來!

響應式編程入門教程第二節:構建 ObservableProperty<T> — 封裝 ReactiveProperty 的高級用法

響應式編程入門教程第三節:ReactiveCommand 與 UI 交互

響應式編程入門教程第四節:響應式集合與數據綁定

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/91304.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/91304.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/91304.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

500+技術棧覆蓋:Web測試平臺TestComplete的對象識別技術解析

在用戶界面&#xff08;UI&#xff09;測試領域&#xff0c;傳統的測試工具往往依賴于XPath或CSS選擇器來定位頁面元素。然而&#xff0c;在面對動態變化的界面、多語言支持或是跨越多種技術框架的應用時&#xff0c;這些傳統方法常導致腳本失效&#xff0c;增加了維護成本。 …

研究人員利用提示注入漏洞繞過Meta的Llama防火墻防護

Trendyol應用安全團隊發現了一系列繞過技術&#xff0c;使得Meta的Llama防火墻在面對復雜的提示注入攻擊時防護失效。這一發現引發了人們對現有大語言模型&#xff08;LLM&#xff09;安全措施準備情況的擔憂&#xff0c;并凸顯出在企業日益將大語言模型嵌入工作流程時&#xf…

Shell 腳本系統學習 · 第5篇:多命令順序執行的三種方式詳解(`;`、``、`||`)

在日常的 Linux 運維與腳本編寫中&#xff0c;我們經常需要依次執行多條命令。本篇將帶你徹底搞懂三種命令順序執行方式&#xff1a;;、&& 和 ||&#xff0c;并通過實用示例掌握它們的區別與應用場景。一、為什么要了解多命令執行方式&#xff1f; 在實際運維或腳本編寫…

K8s存儲系統(通俗易懂版)

Kubernetes中存儲中有四個重要的概念&#xff1a;Volume、PersistentVolume PV、PersistentVolumeClaim PVC、StorageClass一、存儲系統核心概念Volume&#xff08;卷&#xff09;定義&#xff1a;Kubernetes 中最基礎的存儲單元&#xff0c;用于將外部存儲掛載到 Pod 中的容器…

小白學Python,標準庫篇——隨機庫、正則表達式庫

一、隨機庫1.隨機生成數值在random庫中可以隨機生成數值的方法有uniform()、random()、randint()、randrange()等。&#xff08;1&#xff09;uniform()方法uniform(參數1, 參數2)方法用于生成參數1到參數2之間的隨機小數&#xff0c;其中參數的類型都為數值類型。示例代碼&…

Qt窗口:菜單欄

目錄 一、窗口預覽 二、菜單欄 快捷鍵 子菜單 分割線 圖標 內存泄露 一、窗口預覽 在前面幾篇文章中&#xff0c;或者說&#xff0c;Qt初學階段&#xff0c;接觸到的都是QWidget&#xff0c;QWidget指控件&#xff0c;往往作為一個窗口的一部分出現。所謂的窗口&#x…

STM32裸機開發(中斷,輪詢,狀態機)與freeRTOS

裸機&#xff1a;沒有操作系統&#xff0c;程序是單流程的&#xff08;比如一個大循環里依次執行各個功能&#xff0c;或者用中斷嵌套處理事件&#xff09;。優點是資源占用極少&#xff08;幾乎不占 RAM/Flash&#xff09;、執行流程直觀&#xff1b;但復雜項目里&#xff0c;…

電腦上如何查看WiFi密碼

打開控制面板>點擊網絡和Internet在查看網絡和共享中心找到網絡狀態和任務點擊進去點擊連接的WLAN在WLAN狀態中點擊無線屬性在無線網絡屬性中點擊安全&#xff0c;點擊顯示字符&#xff08;H&#xff09;就可以顯示密碼了

文心一言4.5企業級部署實戰:多模態能力與Docker容器化測評

隨著大語言模型在企業服務中的應用日益廣泛&#xff0c;如何選擇一款既能滿足多模態創作需求&#xff0c;又具備良好企業級適配性的AI模型成為了關鍵問題。文心一言4.5作為百度最新開源的大模型&#xff0c;不僅在傳統的文本處理上表現出色&#xff0c;更是在多模態理解和企業級…

VUE Promise基礎語法

目錄 異步和同步 異步的問題 new Promise語法 promise的狀態 promise.then() Promise.resolve() Promise.reject() Promise.all() Promise.race() Promise.catch() Promise.finally() 異步和同步 同步模式下&#xff0c;代碼按順序執行&#xff0c;前一條執行完畢后…

用TensorFlow進行邏輯回歸(六)

import tensorflow as tfimport numpy as npfrom tensorflow.keras.datasets import mnistimport time# MNIST數據集參數num_classes 10 # 數字0到9, 10類num_features 784 # 28*28# 訓練參數learning_rate 0.01training_steps 1000batch_size 256display_step 50# 預處…

【HTTP版本演變】

在瀏覽器中輸入URL并按回車之后會發生什么1. 輸入URL并解析輸入URL后&#xff0c;瀏覽器會解析出協議、主機、端口、路徑等信息&#xff0c;并構造一個HTTP請求&#xff08;瀏覽器會根據請求頭判斷是否又HTTP緩存&#xff0c;并根據是否有緩存決定從服務器獲取資源還是使用緩存…

Android 16系統源碼_窗口動畫(一)窗口過渡動畫層級圖分析

一 窗口過渡動畫 1.1 案例效果圖1.2 案例源碼 1.2.1 添加權限 (AndroidManifest.xml) <!-- 系統懸浮窗權限&#xff08;Android 6.0需動態請求&#xff09; --> <uses-permission android:name"android.permission.SYSTEM_ALERT_WINDOW" />1.2.2 窗口顯示…

騰訊云WAF域名分級防護實戰筆記

基于業務風險等級、合規要求及騰訊云最佳實踐&#xff0c;提供可直接落地的配置方案&#xff0c;供學習借鑒&#xff1a;一、域名分級與防護原則1. ?域名分級清單&#xff08;核心資產&#xff09;???主域名??業務類型??風險等級??合規要求??防護等級?example.com…

1. 請說出你知道的水平垂直居中的方法

總結 容器 flex 布局&#xff0c;jsutify-content: center; align-items: center;容器 flex 布局&#xff0c;子項 margin: auto;容器 relative 布局&#xff0c;子項 absolute 布局&#xff0c;left: 50%; top: 50%; transform: translate(-50%, -50%);子項 absolute 布局&…

VS Code `launch.json` 完整配置指南:參數詳解 + 配置實例

文章目錄&#x1f4e6; 一、基本結構&#x1f50d; 二、單個配置項詳解示例配置&#xff1a;&#x1f9e9; 三、字段說明與可選值&#x1f4c1; 四、常用變量&#xff08;宏替換&#xff09;&#x1f6e0;? 五、常見配置實例1?? 調試當前打開的 .py 文件2?? 調試 Jupyter …

使用瀏覽器inspect調試wx小程序

edge://inspect/#devices調試wx小程序 背景&#xff1a; 在開發混合項目的過程中&#xff0c;常常需要在app環境排查問題&#xff0c;接口可以使用fiddler等工具來抓包&#xff0c;但是js錯誤就不好抓包了&#xff0c;這里介紹一種調試工具-瀏覽器。 調試過程 首先電腦打開edg…

【論文閱讀】-《Simple Black-box Adversarial Attacks》

簡單黑盒對抗攻擊 Chuan Guo Jacob R. Gardner Yurong You Andrew Gordon Wilson Kilian Q. Weinberger 摘要 我們提出了一種在黑盒&#xff08;black-box&#xff09;場景下構建對抗樣本&#xff08;adversarial images&#xff09;的極其簡單的方法。與白盒&#xff08;…

基于ASP.NET+SQL Server實現(Web)企業進銷存管理系統

企業進銷存管理系統的設計和實現一、摘要進銷存管理是現代企業生產經營中的重要環節&#xff0c;是完成企業資源配置的重要管理工作&#xff0c;對企業生產經營效率的最大化發揮著重要作用。本文以我國中小企業的進銷存管理為研究對象&#xff0c;描述了企業進銷存管理系統從需…

(LeetCode 面試經典 150 題 ) 15. 三數之和 (排序+雙指針)

題目&#xff1a;15. 三數之和 思路&#xff1a;排序雙指針&#xff0c;時間復雜度0(n^2nlogn)。 先將數組nums升序排序&#xff0c;方便去重和使用雙指針。第一層for循環來枚舉第一位數&#xff0c;后面使用雙指針來找到第二個、第三個數即可&#xff0c;細節看注釋。 C版本…