--- title: UI 系统 description: UI 系统提供了完整的 UI 页面管理、路由导航和多层级显示功能。 --- # UI 系统 ## 概述 UI 系统是 GFramework.Game 中用于管理游戏 UI 界面的核心组件。它提供了 UI 页面的生命周期管理、基于栈的导航机制,以及多层级的 UI 显示系统(Page、Overlay、Modal、Toast、Topmost)。 通过 UI 系统,你可以轻松实现 UI 页面之间的切换,管理 UI 栈(如主菜单 -> 设置 -> 关于),以及在不同层级显示各种类型的 UI(对话框、提示、加载界面等)。 **主要特性**: - 完整的 UI 生命周期管理 - 基于栈的 UI 导航 - 多层级 UI 显示(5 个层级) - UI 转换管道和钩子 - 路由守卫(Route Guard) - UI 工厂和行为模式 ## 核心概念 ### UI 页面接口 `IUiPage` 定义了 UI 页面的生命周期: ```csharp public interface IUiPage { void OnEnter(IUiPageEnterParam? param); // 进入页面 void OnExit(); // 退出页面 void OnPause(); // 暂停页面 void OnResume(); // 恢复页面 void OnShow(); // 显示页面 void OnHide(); // 隐藏页面 } ``` ### UI 路由 `IUiRouter` 管理 UI 的导航和切换: ```csharp public interface IUiRouter : ISystem { int Count { get; } // UI 栈深度 IUiPageBehavior? Peek(); // 栈顶 UI ValueTask PushAsync(string uiKey, IUiPageEnterParam? param = null); ValueTask PopAsync(UiPopPolicy policy = UiPopPolicy.Destroy); ValueTask ReplaceAsync(string uiKey, IUiPageEnterParam? param = null); ValueTask ClearAsync(); } ``` ### UI 层级 UI 系统支持 5 个显示层级: ```csharp public enum UiLayer { Page, // 页面层(栈管理,不可重入) Overlay, // 浮层(可重入,对话框等) Modal, // 模态层(可重入,带遮罩) Toast, // 提示层(可重入,轻量提示) Topmost // 顶层(不可重入,系统级) } ``` ## 基本用法 ### 定义 UI 页面 实现 `IUiPage` 接口创建 UI 页面: ```csharp using GFramework.Game.Abstractions.UI; public class MainMenuPage : IUiPage { public void OnEnter(IUiPageEnterParam? param) { Console.WriteLine("进入主菜单"); // 初始化 UI、绑定事件 } public void OnExit() { Console.WriteLine("退出主菜单"); // 清理资源、解绑事件 } public void OnPause() { Console.WriteLine("暂停主菜单"); // 暂停动画、停止交互 } public void OnResume() { Console.WriteLine("恢复主菜单"); // 恢复动画、启用交互 } public void OnShow() { Console.WriteLine("显示主菜单"); // 显示 UI 元素 } public void OnHide() { Console.WriteLine("隐藏主菜单"); // 隐藏 UI 元素 } } ``` ### 切换 UI 页面 使用 UI 路由进行导航: ```csharp using GFramework.Core.Abstractions.Controller; using GFramework.SourceGenerators.Abstractions.Rule; [ContextAware] public partial class UiController : IController { public async Task ShowSettings() { var uiRouter = this.GetSystem(); // 压入设置页面(保留当前页面) await uiRouter.PushAsync("Settings"); } public async Task CloseSettings() { var uiRouter = this.GetSystem(); // 弹出当前页面(返回上一页) await uiRouter.PopAsync(); } public async Task ShowMainMenu() { var uiRouter = this.GetSystem(); // 替换所有页面(清空 UI 栈) await uiRouter.ReplaceAsync("MainMenu"); } } ``` ### 显示不同层级的 UI ```csharp [ContextAware] public partial class UiController : IController { public void ShowDialog() { var uiRouter = this.GetSystem(); // 在 Modal 层显示对话框 var handle = uiRouter.Show("ConfirmDialog", UiLayer.Modal); } public void ShowToast(string message) { var uiRouter = this.GetSystem(); // 在 Toast 层显示提示 var handle = uiRouter.Show("ToastMessage", UiLayer.Toast, new ToastParam { Message = message }); } public void ShowLoading() { var uiRouter = this.GetSystem(); // 在 Topmost 层显示加载界面 var handle = uiRouter.Show("LoadingScreen", UiLayer.Topmost); } } ``` ## 高级用法 ### UI 参数传递 ```csharp // 定义 UI 参数 public class SettingsEnterParam : IUiPageEnterParam { public string Category { get; set; } } // 在 UI 中接收参数 public class SettingsPage : IUiPage { private string _category; public void OnEnter(IUiPageEnterParam? param) { if (param is SettingsEnterParam settingsParam) { _category = settingsParam.Category; Console.WriteLine($"打开设置分类: {_category}"); } } // ... 其他生命周期方法 } // 传递参数 await uiRouter.PushAsync("Settings", new SettingsEnterParam { Category = "Audio" }); ``` ### 路由守卫 ```csharp using GFramework.Game.Abstractions.UI; public class UnsavedChangesGuard : IUiRouteGuard { public async ValueTask CanLeaveAsync( IUiPageBehavior from, string toKey, IUiPageEnterParam? param) { // 检查是否有未保存的更改 if (from.Key == "Settings" && HasUnsavedChanges()) { var confirmed = await ShowConfirmDialog(); return confirmed; } return true; } public async ValueTask CanEnterAsync( string toKey, IUiPageEnterParam? param) { // 进入前的验证 return true; } private bool HasUnsavedChanges() => true; private async Task ShowConfirmDialog() => await Task.FromResult(true); } // 注册守卫 uiRouter.AddGuard(new UnsavedChangesGuard()); ``` ### UI 转换处理器 ```csharp using GFramework.Game.Abstractions.UI; public class FadeTransitionHandler : IUiTransitionHandler { public async ValueTask OnBeforeEnterAsync(UiTransitionEvent @event) { Console.WriteLine($"准备进入 UI: {@event.ToKey}"); await PlayFadeIn(); } public async ValueTask OnAfterEnterAsync(UiTransitionEvent @event) { Console.WriteLine($"已进入 UI: {@event.ToKey}"); } public async ValueTask OnBeforeExitAsync(UiTransitionEvent @event) { Console.WriteLine($"准备退出 UI: {@event.FromKey}"); await PlayFadeOut(); } public async ValueTask OnAfterExitAsync(UiTransitionEvent @event) { Console.WriteLine($"已退出 UI: {@event.FromKey}"); } private async Task PlayFadeIn() => await Task.Delay(200); private async Task PlayFadeOut() => await Task.Delay(200); } // 注册转换处理器 uiRouter.RegisterHandler(new FadeTransitionHandler()); ``` ### UI 句柄管理 ```csharp using GFramework.Core.Abstractions.Controller; using GFramework.SourceGenerators.Abstractions.Rule; [ContextAware] public partial class DialogController : IController { private UiHandle? _dialogHandle; public void ShowDialog() { var uiRouter = this.GetSystem(); // 显示对话框并保存句柄 _dialogHandle = uiRouter.Show("ConfirmDialog", UiLayer.Modal); } public void CloseDialog() { if (_dialogHandle.HasValue) { var uiRouter = this.GetSystem(); // 使用句柄关闭对话框 uiRouter.Hide(_dialogHandle.Value, UiLayer.Modal, destroy: true); _dialogHandle = null; } } } ``` ### UI 栈管理 ```csharp using GFramework.Core.Abstractions.Controller; using GFramework.SourceGenerators.Abstractions.Rule; [ContextAware] public partial class NavigationController : IController { public void ShowUiStack() { var uiRouter = this.GetSystem(); Console.WriteLine($"UI 栈深度: {uiRouter.Count}"); var current = uiRouter.Peek(); if (current != null) { Console.WriteLine($"当前 UI: {current.Key}"); } } public bool IsSettingsOpen() { var uiRouter = this.GetSystem(); return uiRouter.Contains("Settings"); } public bool IsTopPage(string uiKey) { var uiRouter = this.GetSystem(); return uiRouter.IsTop(uiKey); } } ``` ### 多层级 UI 管理 ```csharp using GFramework.Core.Abstractions.Controller; using GFramework.SourceGenerators.Abstractions.Rule; [ContextAware] public partial class LayerController : IController { public void ShowMultipleToasts() { var uiRouter = this.GetSystem(); // Toast 层支持重入,可以同时显示多个 uiRouter.Show("Toast1", UiLayer.Toast); uiRouter.Show("Toast2", UiLayer.Toast); uiRouter.Show("Toast3", UiLayer.Toast); } public void ClearAllToasts() { var uiRouter = this.GetSystem(); // 清空 Toast 层的所有 UI uiRouter.ClearLayer(UiLayer.Toast, destroy: true); } public void HideAllDialogs() { var uiRouter = this.GetSystem(); // 隐藏 Modal 层的所有对话框 uiRouter.HideByKey("ConfirmDialog", UiLayer.Modal, hideAll: true); } } ``` ## 最佳实践 1. **使用合适的层级**:根据 UI 类型选择正确的层级 ```csharp ✓ Page: 主要页面(主菜单、设置、游戏界面) ✓ Overlay: 浮层(信息面板、小窗口) ✓ Modal: 模态对话框(确认框、输入框) ✓ Toast: 轻量提示(消息、通知) ✓ Topmost: 系统级(加载界面、全屏遮罩) ``` 2. **使用 Push/Pop 管理临时 UI**:如设置、帮助页面 ```csharp // 打开设置(保留当前页面) await uiRouter.PushAsync("Settings"); // 关闭设置(返回上一页) await uiRouter.PopAsync(); ``` 3. **使用 Replace 切换主要页面**:如从菜单到游戏 ```csharp // 开始游戏(清空 UI 栈) await uiRouter.ReplaceAsync("Gameplay"); ``` 4. **在 OnEnter/OnExit 中管理资源**:保持资源管理清晰 ```csharp public void OnEnter(IUiPageEnterParam? param) { // 加载资源、绑定事件 BindEvents(); } public void OnExit() { // 清理资源、解绑事件 UnbindEvents(); } ``` 5. **使用句柄管理非栈 UI**:对于 Overlay、Modal、Toast 层 ```csharp // 保存句柄 var handle = uiRouter.Show("Dialog", UiLayer.Modal); // 使用句柄关闭 uiRouter.Hide(handle, UiLayer.Modal, destroy: true); ``` 6. **避免在 UI 切换时阻塞**:使用异步操作 ```csharp ✓ await uiRouter.PushAsync("Settings"); ✗ uiRouter.PushAsync("Settings").Wait(); // 可能死锁 ``` ## 常见问题 ### 问题:Push、Pop、Replace 有什么区别? **解答**: - **Push**:压入新 UI,暂停当前 UI(用于临时页面) - **Pop**:弹出当前 UI,恢复上一个 UI(用于关闭临时页面) - **Replace**:清空 UI 栈,加载新 UI(用于主要页面切换) ### 问题:什么时候使用不同的 UI 层级? **解答**: - **Page**:主要页面,使用栈管理 - **Overlay**:浮层,可叠加显示 - **Modal**:模态对话框,阻挡下层交互 - **Toast**:轻量提示,不阻挡交互 - **Topmost**:系统级,最高优先级 ### 问题:如何在 UI 之间传递数据? **解答**: 1. 通过 UI 参数 2. 通过 Model 3. 通过事件 ### 问题:UI 切换时如何显示过渡动画? **解答**: 使用 UI 转换处理器在 `OnBeforeEnter`/`OnAfterExit` 中播放动画。 ### 问题:如何防止用户在 UI 切换时操作? **解答**: 在转换处理器中显示遮罩或禁用输入。 ## 相关文档 - [场景系统](/zh-CN/game/scene) - 场景管理 - [Godot UI 系统](/zh-CN/godot/ui) - Godot 引擎集成 - [事件系统](/zh-CN/core/events) - UI 事件通信 - [状态机系统](/zh-CN/core/state-machine) - UI 状态管理