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

182 lines
5.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: Godot 扩展方法
description: 以当前 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://`
```csharp
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 信号系统](./signal.md)。
### `UnRegisterExtension`
`UnRegisterExtension` 当前也只有一个公开方法:
- `UnRegisterWhenNodeExitTree(this IUnRegister unRegister, Node node)`
它做的事情很明确:把 `unRegister.UnRegister` 挂到 `node.TreeExiting` 上。这样框架侧的订阅句柄就能跟 Godot 节点生命周期对齐。
```csharp
IUnRegister subscription = eventBus.Subscribe<SettingsChangedEvent>(OnSettingsChanged);
subscription.UnRegisterWhenNodeExitTree(this);
```
它不会接管普通 Godot signal 的断开逻辑,也不会帮你推断别的释放时机。
## 最小接入路径
### 1. 节点进入树之后再做装配
如果你的节点可能在 `_Ready()` 前就被访问,先用 `WaitUntilReadyAsync()`
```csharp
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`,可以把释放时机绑到节点退出树:
```csharp
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` 页面要覆盖的核心范围
## 继续阅读
- [Godot 运行时集成](./index.md)
- [Godot 信号系统](./signal.md)
- [Godot 场景系统](./scene.md)
- [Godot UI 系统](./ui.md)