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

15 KiB
Raw Blame History

title, description
title description
Godot UI 系统 Godot UI 系统提供了 GFramework UI 管理与 Godot Control 节点的完整集成。

Godot UI 系统

概述

Godot UI 系统是 GFramework.Godot 中连接框架 UI 管理与 Godot Control 节点的核心组件。它提供了 UI 页面行为封装、UI 工厂、UI 注册表等功能,支持多层级 UI 显示,让你可以在 Godot 项目中使用 GFramework 的 UI 管理系统。

通过 Godot UI 系统,你可以使用 GFramework 的 UI 路由、生命周期管理、多层级显示等功能,同时保持与 Godot UI 系统的完美兼容。

主要特性

  • UI 页面行为封装
  • UI 工厂和注册表
  • 与 Godot PackedScene 集成
  • 多层级 UI 支持Page、Overlay、Modal、Toast、Topmost
  • UI 生命周期管理
  • UI 根节点管理

核心概念

UI 页面行为

CanvasItemUiPageBehaviorBase<T> 封装了 Godot Control 节点的 UI 行为:

public abstract class CanvasItemUiPageBehaviorBase<T> : IUiPageBehavior
    where T : CanvasItem
{
    protected readonly T Owner;
    public string Key { get; }
    public UiLayer Layer { get; }
    public bool IsReentrant { get; }
}

UI 工厂

GodotUiFactory 负责创建 UI 实例:

public class GodotUiFactory : IUiFactory
{
    public IUiPageBehavior Create(string uiKey);
}

UI 层级行为

不同层级的 UI 有不同的行为类:

// Page 层(栈管理)
public class PageLayerUiPageBehavior : CanvasItemUiPageBehaviorBase<Control>
{
    public override UiLayer Layer => UiLayer.Page;
    public override bool IsReentrant => false;
}

// Modal 层(模态对话框)
public class ModalLayerUiPageBehavior : CanvasItemUiPageBehaviorBase<Control>
{
    public override UiLayer Layer => UiLayer.Modal;
    public override bool IsReentrant => true;
}

基本用法

创建 UI 脚本

using Godot;
using GFramework.Game.Abstractions.UI;

public partial class MainMenuPage : Control, IUiPage
{
    public void OnEnter(IUiPageEnterParam? param)
    {
        GD.Print("进入主菜单");
        Show();
    }

    public void OnExit()
    {
        GD.Print("退出主菜单");
        Hide();
    }

    public void OnPause()
    {
        GD.Print("暂停主菜单");
    }

    public void OnResume()
    {
        GD.Print("恢复主菜单");
    }

    public void OnShow()
    {
        Show();
    }

    public void OnHide()
    {
        Hide();
    }
}

实现 UI 页面行为提供者

using Godot;
using GFramework.Game.Abstractions.UI;
using GFramework.Godot.UI;

public partial class MainMenuPage : Control, IUiPageBehaviorProvider
{
    private PageLayerUiPageBehavior _behavior;

    public override void _Ready()
    {
        _behavior = new PageLayerUiPageBehavior(this, "MainMenu");
    }

    public IUiPageBehavior GetPage()
    {
        return _behavior;
    }
}

注册 UI

using GFramework.Godot.UI;
using Godot;

public class GameUiRegistry : GodotUiRegistry
{
    public GameUiRegistry()
    {
        // 注册 UI 资源
        Register("MainMenu", GD.Load<PackedScene>("res://ui/MainMenu.tscn"));
        Register("Settings", GD.Load<PackedScene>("res://ui/Settings.tscn"));
        Register("ConfirmDialog", GD.Load<PackedScene>("res://ui/ConfirmDialog.tscn"));
        Register("Toast", GD.Load<PackedScene>("res://ui/Toast.tscn"));
    }
}

设置 UI 系统

using GFramework.Godot.Architecture;
using GFramework.Godot.UI;

public class GameArchitecture : AbstractArchitecture
{
    protected override void InstallModules()
    {
        // 注册 UI 注册表
        var uiRegistry = new GameUiRegistry();
        RegisterUtility<IGodotUiRegistry>(uiRegistry);

        // 注册 UI 工厂
        var uiFactory = new GodotUiFactory();
        RegisterUtility<IUiFactory>(uiFactory);

        // 注册 UI 路由
        var uiRouter = new GodotUiRouter();
        RegisterSystem<IUiRouter>(uiRouter);
    }
}

使用 UI 路由

using Godot;
using GFramework.Godot.Extensions;

public partial class GameController : Node
{
    public override void _Ready()
    {
        ShowMainMenu();
    }

    private async void ShowMainMenu()
    {
        var uiRouter = this.GetSystem<IUiRouter>();
        await uiRouter.PushAsync("MainMenu");
    }

    private async void ShowSettings()
    {
        var uiRouter = this.GetSystem<IUiRouter>();
        await uiRouter.PushAsync("Settings");
    }

    private void ShowDialog()
    {
        var uiRouter = this.GetSystem<IUiRouter>();
        uiRouter.Show("ConfirmDialog", UiLayer.Modal);
    }

    private void ShowToast(string message)
    {
        var uiRouter = this.GetSystem<IUiRouter>();
        uiRouter.Show("Toast", UiLayer.Toast, new ToastParam { Message = message });
    }
}

高级用法

不同层级的 UI 行为

// Page 层 UI栈管理不可重入
public partial class MainMenuPage : Control, IUiPageBehaviorProvider
{
    public IUiPageBehavior GetPage()
    {
        return new PageLayerUiPageBehavior(this, "MainMenu");
    }
}

// Overlay 层 UI浮层可重入
public partial class InfoPanel : Control, IUiPageBehaviorProvider
{
    public IUiPageBehavior GetPage()
    {
        return new OverlayLayerUiPageBehavior(this, "InfoPanel");
    }
}

// Modal 层 UI模态对话框可重入
public partial class ConfirmDialog : Control, IUiPageBehaviorProvider
{
    public IUiPageBehavior GetPage()
    {
        return new ModalLayerUiPageBehavior(this, "ConfirmDialog");
    }
}

// Toast 层 UI提示可重入
public partial class ToastMessage : Control, IUiPageBehaviorProvider
{
    public IUiPageBehavior GetPage()
    {
        return new ToastLayerUiPageBehavior(this, "Toast");
    }
}

// Topmost 层 UI顶层不可重入
public partial class LoadingScreen : Control, IUiPageBehaviorProvider
{
    public IUiPageBehavior GetPage()
    {
        return new TopmostLayerUiPageBehavior(this, "Loading");
    }
}

UI 参数传递

// 定义 UI 参数
public class ConfirmDialogParam : IUiPageEnterParam
{
    public string Title { get; set; }
    public string Message { get; set; }
    public Action OnConfirm { get; set; }
    public Action OnCancel { get; set; }
}

// 在 UI 中接收参数
public partial class ConfirmDialog : Control, IUiPage
{
    private Label _titleLabel;
    private Label _messageLabel;
    private Action _onConfirm;
    private Action _onCancel;

    public override void _Ready()
    {
        _titleLabel = GetNode<Label>("Title");
        _messageLabel = GetNode<Label>("Message");

        GetNode<Button>("ConfirmButton").Pressed += OnConfirmPressed;
        GetNode<Button>("CancelButton").Pressed += OnCancelPressed;
    }

    public void OnEnter(IUiPageEnterParam? param)
    {
        if (param is ConfirmDialogParam dialogParam)
        {
            _titleLabel.Text = dialogParam.Title;
            _messageLabel.Text = dialogParam.Message;
            _onConfirm = dialogParam.OnConfirm;
            _onCancel = dialogParam.OnCancel;
        }

        Show();
    }

    private void OnConfirmPressed()
    {
        _onConfirm?.Invoke();
        CloseDialog();
    }

    private void OnCancelPressed()
    {
        _onCancel?.Invoke();
        CloseDialog();
    }

    private void CloseDialog()
    {
        var uiRouter = this.GetSystem<IUiRouter>();
        if (Handle.HasValue)
        {
            uiRouter.Hide(Handle.Value, UiLayer.Modal, destroy: true);
        }
    }

    // ... 其他生命周期方法
}

// 显示对话框
var uiRouter = this.GetSystem<IUiRouter>();
uiRouter.Show("ConfirmDialog", UiLayer.Modal, new ConfirmDialogParam
{
    Title = "确认",
    Message = "确定要退出吗?",
    OnConfirm = () => GD.Print("确认"),
    OnCancel = () => GD.Print("取消")
});

UI 根节点管理

using Godot;
using GFramework.Godot.UI;

public partial class UiRoot : CanvasLayer, IUiRoot
{
    private Control _pageLayer;
    private Control _overlayLayer;
    private Control _modalLayer;
    private Control _toastLayer;
    private Control _topmostLayer;

    public override void _Ready()
    {
        // 创建各层级容器
        _pageLayer = new Control { Name = "PageLayer" };
        _overlayLayer = new Control { Name = "OverlayLayer" };
        _modalLayer = new Control { Name = "ModalLayer" };
        _toastLayer = new Control { Name = "ToastLayer" };
        _topmostLayer = new Control { Name = "TopmostLayer" };

        AddChild(_pageLayer);
        AddChild(_overlayLayer);
        AddChild(_modalLayer);
        AddChild(_toastLayer);
        AddChild(_topmostLayer);
    }

    public void AttachPage(Control page, UiLayer layer)
    {
        var container = GetLayerContainer(layer);
        container.AddChild(page);
    }

    public void DetachPage(Control page, UiLayer layer)
    {
        var container = GetLayerContainer(layer);
        container.RemoveChild(page);
    }

    private Control GetLayerContainer(UiLayer layer)
    {
        return layer switch
        {
            UiLayer.Page => _pageLayer,
            UiLayer.Overlay => _overlayLayer,
            UiLayer.Modal => _modalLayer,
            UiLayer.Toast => _toastLayer,
            UiLayer.Topmost => _topmostLayer,
            _ => _pageLayer
        };
    }
}

UI 动画和过渡

public partial class AnimatedPage : Control, IUiPage
{
    public void OnEnter(IUiPageEnterParam? param)
    {
        // 淡入动画
        Modulate = new Color(1, 1, 1, 0);
        Show();

        var tween = CreateTween();
        tween.TweenProperty(this, "modulate:a", 1.0f, 0.3f);
    }

    public void OnExit()
    {
        // 淡出动画
        var tween = CreateTween();
        tween.TweenProperty(this, "modulate:a", 0.0f, 0.3f);
        tween.TweenCallback(Callable.From(Hide));
    }

    public void OnShow()
    {
        Show();
    }

    public void OnHide()
    {
        Hide();
    }

    // ... 其他方法
}

UI 句柄管理

public partial class DialogManager : Node
{
    private UiHandle? _currentDialog;

    public void ShowDialog(string dialogKey)
    {
        // 关闭当前对话框
        CloseCurrentDialog();

        // 显示新对话框
        var uiRouter = this.GetSystem<IUiRouter>();
        _currentDialog = uiRouter.Show(dialogKey, UiLayer.Modal);
    }

    public void CloseCurrentDialog()
    {
        if (_currentDialog.HasValue)
        {
            var uiRouter = this.GetSystem<IUiRouter>();
            uiRouter.Hide(_currentDialog.Value, UiLayer.Modal, destroy: true);
            _currentDialog = null;
        }
    }
}

多个 Toast 显示

public partial class ToastManager : Node
{
    private readonly List<UiHandle> _activeToasts = new();

    public void ShowToast(string message, float duration = 3.0f)
    {
        var uiRouter = this.GetSystem<IUiRouter>();

        // Toast 层支持重入,可以同时显示多个
        var handle = uiRouter.Show("Toast", UiLayer.Toast, new ToastParam
        {
            Message = message
        });

        _activeToasts.Add(handle);

        // 自动隐藏
        GetTree().CreateTimer(duration).Timeout += () =>
        {
            uiRouter.Hide(handle, UiLayer.Toast, destroy: true);
            _activeToasts.Remove(handle);
        };
    }

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

        foreach (var handle in _activeToasts)
        {
            uiRouter.Hide(handle, UiLayer.Toast, destroy: true);
        }

        _activeToasts.Clear();
    }
}

最佳实践

  1. UI 脚本实现 IUiPage 接口:获得完整的生命周期管理

     public partial class MyPage : Control, IUiPage { }
     public partial class MyPage : Control { } // 无生命周期管理
    
  2. 使用正确的 UI 层级:根据 UI 类型选择合适的层级

     Page: 主要页面(主菜单、设置)
     Overlay: 浮层(信息面板)
     Modal: 模态对话框(确认框)
     Toast: 提示消息
     Topmost: 系统级(加载界面)
    
  3. 在 OnEnter 中显示 UI:确保 UI 正确显示

    public void OnEnter(IUiPageEnterParam? param)
    {
        Show(); // 显示 UI
        // 初始化 UI 状态
    }
    
  4. 在 OnExit 中隐藏 UI:确保 UI 正确隐藏

    public void OnExit()
    {
        Hide(); // 隐藏 UI
        // 清理 UI 状态
    }
    
  5. 使用 UI 句柄管理非栈 UI:对于 Modal、Toast 等层级

    var handle = uiRouter.Show("Dialog", UiLayer.Modal);
    // 保存句柄以便后续关闭
    uiRouter.Hide(handle, UiLayer.Modal, destroy: true);
    
  6. 使用 UI 参数传递数据:避免使用全局变量

     uiRouter.Show("Dialog", UiLayer.Modal, new DialogParam { ... });
     GlobalData.DialogMessage = "..."; // 避免全局状态
    

常见问题

问题:如何在 Godot UI 中使用 GFramework

解答 UI 脚本实现 IUiPageIUiPageBehaviorProvider 接口:

public partial class MyPage : Control, IUiPage, IUiPageBehaviorProvider
{
    public void OnEnter(IUiPageEnterParam? param) { }
    public IUiPageBehavior GetPage() { return new PageLayerUiPageBehavior(this, "MyPage"); }
}

问题UI 层级有什么区别?

解答

  • Page:栈管理,不可重入,用于主要页面
  • Overlay:可重入,用于浮层
  • Modal:可重入,带遮罩,用于对话框
  • Toast:可重入,轻量提示
  • Topmost:不可重入,最高优先级

问题:如何实现 UI 动画?

解答 在生命周期方法中使用 Godot Tween

public void OnEnter(IUiPageEnterParam? param)
{
    var tween = CreateTween();
    tween.TweenProperty(this, "modulate:a", 1.0f, 0.3f);
}

问题:如何在 UI 中访问架构组件?

解答 使用扩展方法:

public partial class MyPage : Control, IUiPage
{
    public void OnEnter(IUiPageEnterParam? param)
    {
        var playerModel = this.GetModel<PlayerModel>();
        var gameSystem = this.GetSystem<GameSystem>();
    }
}

问题:如何关闭 Modal 或 Toast

解答 使用 UI 句柄:

// 显示时保存句柄
var handle = uiRouter.Show("Dialog", UiLayer.Modal);

// 关闭时使用句柄
uiRouter.Hide(handle, UiLayer.Modal, destroy: true);

问题UI 生命周期方法的调用顺序是什么?

解答

  • 进入:OnEnter -> OnShow
  • 暂停:OnPause -> OnHide
  • 恢复:OnShow -> OnResume
  • 退出:OnHide -> OnExit

相关文档