GeWuYou fb14d7122c docs(style): 更新文档中的命名空间导入格式
- 将所有小写的命名空间导入更正为首字母大写格式
- 统一 GFramework 框架的命名空间引用规范
- 修复 core、ecs、godot 等模块的命名空间导入错误
- 标准化文档示例代码中的 using 语句格式
- 确保所有文档中的命名空间引用保持一致性
- 更新 global using 语句以匹配正确的命名空间格式
2026-03-10 07:18:49 +08:00

13 KiB
Raw Blame History

title, description
title description
UI 系统 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 页面的生命周期:

public interface IUiPage
{
    void OnEnter(IUiPageEnterParam? param);  // 进入页面
    void OnExit();                           // 退出页面
    void OnPause();                          // 暂停页面
    void OnResume();                         // 恢复页面
    void OnShow();                           // 显示页面
    void OnHide();                           // 隐藏页面
}

UI 路由

IUiRouter 管理 UI 的导航和切换:

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 个显示层级:

public enum UiLayer
{
    Page,      // 页面层(栈管理,不可重入)
    Overlay,   // 浮层(可重入,对话框等)
    Modal,     // 模态层(可重入,带遮罩)
    Toast,     // 提示层(可重入,轻量提示)
    Topmost    // 顶层(不可重入,系统级)
}

基本用法

定义 UI 页面

实现 IUiPage 接口创建 UI 页面:

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 路由进行导航:

using GFramework.Core.Abstractions.Controller;
using GFramework.SourceGenerators.Abstractions.Rule;

[ContextAware]
public partial class UiController : IController
{
    public async Task ShowSettings()
    {
        var uiRouter = this.GetSystem<IUiRouter>();

        // 压入设置页面(保留当前页面)
        await uiRouter.PushAsync("Settings");
    }

    public async Task CloseSettings()
    {
        var uiRouter = this.GetSystem<IUiRouter>();

        // 弹出当前页面(返回上一页)
        await uiRouter.PopAsync();
    }

    public async Task ShowMainMenu()
    {
        var uiRouter = this.GetSystem<IUiRouter>();

        // 替换所有页面(清空 UI 栈)
        await uiRouter.ReplaceAsync("MainMenu");
    }
}

显示不同层级的 UI

[ContextAware]
public partial class UiController : IController
{
    public void ShowDialog()
    {
        var uiRouter = this.GetSystem<IUiRouter>();

        // 在 Modal 层显示对话框
        var handle = uiRouter.Show("ConfirmDialog", UiLayer.Modal);
    }

    public void ShowToast(string message)
    {
        var uiRouter = this.GetSystem<IUiRouter>();

        // 在 Toast 层显示提示
        var handle = uiRouter.Show("ToastMessage", UiLayer.Toast,
            new ToastParam { Message = message });
    }

    public void ShowLoading()
    {
        var uiRouter = this.GetSystem<IUiRouter>();

        // 在 Topmost 层显示加载界面
        var handle = uiRouter.Show("LoadingScreen", UiLayer.Topmost);
    }
}

高级用法

UI 参数传递

// 定义 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"
});

路由守卫

using GFramework.Game.Abstractions.UI;

public class UnsavedChangesGuard : IUiRouteGuard
{
    public async ValueTask<bool> CanLeaveAsync(
        IUiPageBehavior from,
        string toKey,
        IUiPageEnterParam? param)
    {
        // 检查是否有未保存的更改
        if (from.Key == "Settings" && HasUnsavedChanges())
        {
            var confirmed = await ShowConfirmDialog();
            return confirmed;
        }

        return true;
    }

    public async ValueTask<bool> CanEnterAsync(
        string toKey,
        IUiPageEnterParam? param)
    {
        // 进入前的验证
        return true;
    }

    private bool HasUnsavedChanges() => true;
    private async Task<bool> ShowConfirmDialog() => await Task.FromResult(true);
}

// 注册守卫
uiRouter.AddGuard(new UnsavedChangesGuard());

UI 转换处理器

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 句柄管理

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<IUiRouter>();

        // 显示对话框并保存句柄
        _dialogHandle = uiRouter.Show("ConfirmDialog", UiLayer.Modal);
    }

    public void CloseDialog()
    {
        if (_dialogHandle.HasValue)
        {
            var uiRouter = this.GetSystem<IUiRouter>();

            // 使用句柄关闭对话框
            uiRouter.Hide(_dialogHandle.Value, UiLayer.Modal, destroy: true);
            _dialogHandle = null;
        }
    }
}

UI 栈管理

using GFramework.Core.Abstractions.Controller;
using GFramework.SourceGenerators.Abstractions.Rule;

[ContextAware]
public partial class NavigationController : IController
{
    public void ShowUiStack()
    {
        var uiRouter = this.GetSystem<IUiRouter>();

        Console.WriteLine($"UI 栈深度: {uiRouter.Count}");

        var current = uiRouter.Peek();
        if (current != null)
        {
            Console.WriteLine($"当前 UI: {current.Key}");
        }
    }

    public bool IsSettingsOpen()
    {
        var uiRouter = this.GetSystem<IUiRouter>();
        return uiRouter.Contains("Settings");
    }

    public bool IsTopPage(string uiKey)
    {
        var uiRouter = this.GetSystem<IUiRouter>();
        return uiRouter.IsTop(uiKey);
    }
}

多层级 UI 管理

using GFramework.Core.Abstractions.Controller;
using GFramework.SourceGenerators.Abstractions.Rule;

[ContextAware]
public partial class LayerController : IController
{
    public void ShowMultipleToasts()
    {
        var uiRouter = this.GetSystem<IUiRouter>();

        // Toast 层支持重入,可以同时显示多个
        uiRouter.Show("Toast1", UiLayer.Toast);
        uiRouter.Show("Toast2", UiLayer.Toast);
        uiRouter.Show("Toast3", UiLayer.Toast);
    }

    public void ClearAllToasts()
    {
        var uiRouter = this.GetSystem<IUiRouter>();

        // 清空 Toast 层的所有 UI
        uiRouter.ClearLayer(UiLayer.Toast, destroy: true);
    }

    public void HideAllDialogs()
    {
        var uiRouter = this.GetSystem<IUiRouter>();

        // 隐藏 Modal 层的所有对话框
        uiRouter.HideByKey("ConfirmDialog", UiLayer.Modal, hideAll: true);
    }
}

最佳实践

  1. 使用合适的层级:根据 UI 类型选择正确的层级

     Page: 主要页面(主菜单、设置、游戏界面)
     Overlay: 浮层(信息面板、小窗口)
     Modal: 模态对话框(确认框、输入框)
     Toast: 轻量提示(消息、通知)
     Topmost: 系统级(加载界面、全屏遮罩)
    
  2. 使用 Push/Pop 管理临时 UI:如设置、帮助页面

    // 打开设置(保留当前页面)
    await uiRouter.PushAsync("Settings");
    
    // 关闭设置(返回上一页)
    await uiRouter.PopAsync();
    
  3. 使用 Replace 切换主要页面:如从菜单到游戏

    // 开始游戏(清空 UI 栈)
    await uiRouter.ReplaceAsync("Gameplay");
    
  4. 在 OnEnter/OnExit 中管理资源:保持资源管理清晰

    public void OnEnter(IUiPageEnterParam? param)
    {
        // 加载资源、绑定事件
        BindEvents();
    }
    
    public void OnExit()
    {
        // 清理资源、解绑事件
        UnbindEvents();
    }
    
  5. 使用句柄管理非栈 UI:对于 Overlay、Modal、Toast 层

    // 保存句柄
    var handle = uiRouter.Show("Dialog", UiLayer.Modal);
    
    // 使用句柄关闭
    uiRouter.Hide(handle, UiLayer.Modal, destroy: true);
    
  6. 避免在 UI 切换时阻塞:使用异步操作

     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 切换时操作?

解答 在转换处理器中显示遮罩或禁用输入。

相关文档