GFramework/docs/zh-CN/godot/extensions.md
gewuyou 4a5e1e74a6 docs(pr-review): 收口当前文档审查意见
- 更新 Game 与 SourceGenerators README 的公开入口命名和重复链接

- 优化 Godot 教程与扩展页的 reader-facing 措辞

- 补充 PR #296 的治理跟踪与验证记录
2026-04-27 12:49:34 +08:00

5.7 KiB
Raw Blame History

title, description
title description
Godot 扩展方法 以当前 GFramework.Godot.Extensions 源码为准说明路径、Node、signal 和 unregister 扩展的真实成员与边界。

Godot 扩展方法

GFramework.Godot.Extensions 当前并不是“覆盖所有 Godot 节点操作”的万能层。按源码看,它实际公开的扩展主要只有四组:

  • GodotPathExtensions
  • NodeExtensions
  • SignalFluentExtensions
  • UnRegisterExtension

按源码看,这些扩展各自解决的问题并不相同,也需要区分哪些能力属于单一运行时辅助,而不是统一入口。

当前公开入口

GodotPathExtensions

这组扩展只负责判断 Godot 虚拟路径前缀:

  • IsUserPath(this string path)
  • IsResPath(this string path)
  • IsGodotPath(this string path)

它们不做文件访问,也不解析目录结构,只是用字符串前缀判断 user://res://

using GFramework.Godot.Extensions;

if ("user://save.json".IsUserPath())
{
}

if ("res://config/gameplay.yaml".IsGodotPath())
{
}

NodeExtensions

NodeExtensions 是当前扩展集合里体量最大的部分,但职责仍然比较具体,主要分成下面几类。

生命周期与有效性辅助

  • QueueFreeX(this Node? node)
  • FreeX(this Node? node)
  • WaitUntilReadyAsync(this Node node)
  • WaitUntilReady(this Node node, Action callback)
  • IsValidNode(this Node? node)
  • IsInvalidNode(this Node? node)

这里最容易写偏的地方有两个:

  • QueueFreeX() / FreeX() 会先检查 null、实例是否仍有效、是否已经进入删除队列
  • IsValidNode() 不只要求实例还活着,还要求节点已经在 SceneTree 里;单纯 new 出来但还没挂树的节点会返回 false

节点访问与装配辅助

  • FindChildX<T>(...)
  • GetOrCreateNode<T>(...)
  • AddChildXAsync(...)
  • GetParentX<T>()
  • GetRootNodeX()
  • ForEachChild<T>(...)
  • OfType<T>()

这几组方法更偏“少量常用装配动作”,不是完整查询 DSL。

特别是 GetOrCreateNode<T>(string path) 的当前实现要注意:

  1. 先尝试 GetNodeOrNull<T>(path)
  2. 如果没找到,就 new T()
  3. 把新节点直接 AddChild(...) 到当前节点
  4. 再把 created.Name = path

它不会按斜杠路径逐级创建中间节点,所以不要把它当成层级化路径构建器。

输入、暂停与调试辅助

  • SetInputAsHandled()
  • Paused(bool paused = true)
  • DisableInput()
  • EnableInput()
  • LogNodePath()
  • PrintTreeX(string indent = "")
  • SafeCallDeferred(string method)

这些方法都很薄,基本是在现有 Viewport / SceneTree / CallDeferred(...) 上做便捷包装,没有额外状态机。

SignalFluentExtensions

SignalFluentExtensions 只提供一个入口:

  • Signal(this GodotObject @object, StringName signal)

它把目标对象和 signal 名称包装成 SignalBuilder。具体连接语义请看 Godot 信号系统

UnRegisterExtension

UnRegisterExtension 当前也只有一个公开方法:

  • UnRegisterWhenNodeExitTree(this IUnRegister unRegister, Node node)

它做的事情很明确:把 unRegister.UnRegister 挂到 node.TreeExiting 上。这样框架侧的订阅句柄就能跟 Godot 节点生命周期对齐。

IUnRegister subscription = eventBus.Subscribe<SettingsChangedEvent>(OnSettingsChanged);
subscription.UnRegisterWhenNodeExitTree(this);

它不会接管普通 Godot signal 的断开逻辑,也不会帮你推断别的释放时机。

最小接入路径

1. 节点进入树之后再做装配

如果你的节点可能在 _Ready() 前就被访问,先用 WaitUntilReadyAsync()

using GFramework.Godot.Extensions;
using GFramework.Godot.Extensions.Signal;
using Godot;

public partial class SettingsPanel : Control
{
    public override async void _Ready()
    {
        await this.WaitUntilReadyAsync();

        var applyButton = FindChildX<Button>("ApplyButton");
        applyButton?.Signal(Button.SignalName.Pressed)
                    .To(Callable.From(OnApplyPressed));
    }

    private void OnApplyPressed()
    {
        this.SetInputAsHandled();
    }
}

2. 框架订阅和节点生命周期一起收尾

当订阅句柄实现了 IUnRegister,可以把释放时机绑到节点退出树:

public override void _Ready()
{
    IUnRegister subscription = _eventBus.Subscribe<SettingsChangedEvent>(OnSettingsChanged);
    subscription.UnRegisterWhenNodeExitTree(this);
}

这比在多个 _ExitTree() / Dispose() 分支里手写解绑更稳定,也更符合当前扩展的职责边界。

3. 只在需要时使用 signal fluent API

Signal(...) 属于扩展集合的一部分,但它已经有独立页面。实践上可以这样分工:

  • 节点查找、ready 等待、输入处理:NodeExtensions
  • 动态 signal 绑定:Signal(...)
  • 框架订阅释放:UnRegisterWhenNodeExitTree(...)
  • 路径前缀判断:GodotPathExtensions

当前边界

  • 当前 NodeExtensions 不提供 GetNodeX()CreateSignalBuilder() 这类额外包装 API
  • 它不是 router、scene factory、UI factory 或生成器的替代层
  • GetOrCreateNode<T>() 只会创建一个直接子节点,不会递归补整条路径
  • SafeCallDeferred(...) 只有在 IsValidNode()true 时才会调用;节点未入树时不会执行
  • UnRegisterWhenNodeExitTree(...) 只针对实现了 IUnRegister 的框架订阅句柄,不会自动处理 Godot 原生 Connect(...)
  • 协程辅助扩展在 GFramework.Godot.Coroutine 命名空间,不属于这组 Extensions 页面要覆盖的核心范围

继续阅读