mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-07 00:39:00 +08:00
- 更新入口页的 reader-facing 骨架,统一起步路线、阅读顺序与站内导航 - 收口公开 README 与 Godot 页面中的内部口吻、文件名式表述和术语噪音 - 移除 docs/zh-CN 中残留的 GitHub README 外链,并同步刷新文档治理恢复状态
219 lines
5.9 KiB
Markdown
219 lines
5.9 KiB
Markdown
---
|
||
title: GetNode 生成器
|
||
description: 说明 [GetNode] 当前生成什么、路径如何推断,以及 _Ready 生命周期里的接入边界。
|
||
---
|
||
|
||
# GetNode 生成器
|
||
|
||
`[GetNode]` 用来把 Godot 节点查找样板收敛到生成器里。它只处理“字段如何取到节点”,不负责事件订阅,也不负责其他运行时装配。
|
||
|
||
## 当前包关系
|
||
|
||
- 特性来源:`GFramework.Godot.SourceGenerators.Abstractions`
|
||
- 生成器实现:`GFramework.Godot.SourceGenerators`
|
||
- 使用前提:字段类型必须继承 `Godot.Node`
|
||
|
||
## 最小用法
|
||
|
||
```csharp
|
||
using GFramework.Godot.SourceGenerators.Abstractions;
|
||
using Godot;
|
||
|
||
public partial class TopBar : HBoxContainer
|
||
{
|
||
[GetNode]
|
||
private HBoxContainer _leftContainer = null!;
|
||
|
||
[GetNode]
|
||
private HBoxContainer m_rightContainer = null!;
|
||
}
|
||
```
|
||
|
||
如果目标类型还没有 `_Ready()`,当前生成器会补出:
|
||
|
||
```csharp
|
||
private void __InjectGetNodes_Generated()
|
||
{
|
||
_leftContainer = GetNode<global::Godot.HBoxContainer>("%LeftContainer");
|
||
m_rightContainer = GetNode<global::Godot.HBoxContainer>("%RightContainer");
|
||
}
|
||
|
||
partial void OnGetNodeReadyGenerated();
|
||
|
||
public override void _Ready()
|
||
{
|
||
__InjectGetNodes_Generated();
|
||
OnGetNodeReadyGenerated();
|
||
}
|
||
```
|
||
|
||
这个行为来自当前生成器测试,不是文档约定。
|
||
|
||
## 当前路径推断规则
|
||
|
||
### 没写路径时
|
||
|
||
如果 `[GetNode]` 没有显式路径,当前默认按字段名推导唯一名路径:
|
||
|
||
- `_leftContainer` -> `%LeftContainer`
|
||
- `m_rightContainer` -> `%RightContainer`
|
||
|
||
也就是说,默认不是普通相对路径,而是 Godot 的 `%Name` 唯一名语法。
|
||
|
||
### 显式路径优先
|
||
|
||
```csharp
|
||
[GetNode("ScoreContainer/ScoreValue")]
|
||
private Label _scoreLabel = null!;
|
||
```
|
||
|
||
显式路径会直接进入生成结果,不再按字段名推断。
|
||
|
||
## `Lookup` 与 `Required` 的当前语义
|
||
|
||
### `Lookup`
|
||
|
||
`GetNodeAttribute.Lookup` 支持 4 个模式:
|
||
|
||
- `Auto`
|
||
- `UniqueName`
|
||
- `RelativePath`
|
||
- `AbsolutePath`
|
||
|
||
对文档来说,最关键的结论是:
|
||
|
||
- `Auto` 在未给路径时默认走唯一名推断
|
||
- 一旦显式给了 `Path`,生成结果直接使用这个字符串,`Lookup` 不再改写它
|
||
|
||
可以直接按下面这张表理解当前行为:
|
||
|
||
| `Path` | `Lookup` | 生成路径 |
|
||
| --- | --- | --- |
|
||
| 未设置 | `Auto` | `%FieldName` |
|
||
| 未设置 | `UniqueName` | `%FieldName` |
|
||
| 未设置 | `RelativePath` | `FieldName` |
|
||
| 未设置 | `AbsolutePath` | `/FieldName` |
|
||
| 已显式设置 | 任意值 | 原样使用显式路径 |
|
||
|
||
例如:
|
||
|
||
```csharp
|
||
[GetNode("HUD/ScoreLabel", Lookup = NodeLookupMode.AbsolutePath)]
|
||
private Label _scoreLabel = null!;
|
||
```
|
||
|
||
当前生成结果仍然会直接使用 `"HUD/ScoreLabel"`,不会因为 `Lookup = AbsolutePath` 被改写成 `"/HUD/ScoreLabel"`。
|
||
|
||
### `Required`
|
||
|
||
默认 `Required = true`,生成器会调用 `GetNode<T>()`:
|
||
|
||
```csharp
|
||
[GetNode]
|
||
private Label _title = null!;
|
||
```
|
||
|
||
如果设为 `false`,生成器会改用 `GetNodeOrNull<T>()`:
|
||
|
||
```csharp
|
||
[GetNode(Required = false, Lookup = NodeLookupMode.RelativePath)]
|
||
private HBoxContainer? _rightContainer;
|
||
```
|
||
|
||
当前生成结果会是:
|
||
|
||
```csharp
|
||
_rightContainer = GetNodeOrNull<global::Godot.HBoxContainer>("RightContainer");
|
||
```
|
||
|
||
所以可选节点最好同时用可空字段类型表达你的意图。
|
||
|
||
## 生命周期边界
|
||
|
||
### 没有 `_Ready()` 时
|
||
|
||
生成器会补:
|
||
|
||
- `__InjectGetNodes_Generated()`
|
||
- `partial void OnGetNodeReadyGenerated()`
|
||
- 一个 `public override void _Ready()`
|
||
|
||
`OnGetNodeReadyGenerated()` 只在这种“生成器自己补 `_Ready()`”的路径里出现。
|
||
|
||
### 已经有 `_Ready()` 时
|
||
|
||
如果类型已经实现了 `_Ready()`,生成器不会覆盖它,也不会再额外生成 `OnGetNodeReadyGenerated()`。你必须自己调用:
|
||
|
||
```csharp
|
||
public override void _Ready()
|
||
{
|
||
__InjectGetNodes_Generated();
|
||
}
|
||
```
|
||
|
||
如果 `_Ready()` 存在但没有调用生成方法,当前会给出 warning,提醒你手动接入。
|
||
|
||
## 当前强约束
|
||
|
||
这些约束都直接来自生成器源码和测试:
|
||
|
||
- 目标类型必须是顶层 `partial class`
|
||
- 不支持嵌套类
|
||
- 字段必须是实例字段
|
||
- 字段不能是 `readonly`
|
||
- 字段类型必须继承 `Godot.Node`
|
||
- 如果无法从字段名或显式参数推断出路径,会报错
|
||
- 如果你自己定义了 `__InjectGetNodes_Generated()`,会触发命名冲突诊断
|
||
|
||
## 与 BindNodeSignal 的配合顺序
|
||
|
||
如果同一个类型同时用了 `[GetNode]` 和 `[BindNodeSignal]`,当前推荐顺序是:
|
||
|
||
```csharp
|
||
public override void _Ready()
|
||
{
|
||
__InjectGetNodes_Generated();
|
||
__BindNodeSignals_Generated();
|
||
}
|
||
```
|
||
|
||
先注入节点,再绑定事件;否则 `BindNodeSignal` 对应的字段还没完成解析。
|
||
|
||
这也是 `ai-libs/CoreGrid` 里项目侧节点类的实际用法。
|
||
|
||
## 什么时候适合用 `[GetNode]`
|
||
|
||
适合:
|
||
|
||
- 节点字段很多,`GetNode<T>()` 样板明显重复
|
||
- 你希望把“字段名到节点路径”的约定收敛到声明式特性
|
||
- 你在 Godot `Control`、`Node`、`CanvasLayer` 等项目侧类型上频繁访问子节点
|
||
|
||
不适合:
|
||
|
||
- 目标不是 `Godot.Node`
|
||
- 节点路径完全动态,必须在运行时决定
|
||
- 你需要更复杂的节点查找策略,而不是字段级静态描述
|
||
|
||
## 与旧写法的边界
|
||
|
||
下面这些旧理解已经不准确:
|
||
|
||
- “`[GetNode]` 总会自动帮你改写 `_Ready()`”
|
||
- “不管是否已有 `_Ready()`,都会生成 `OnGetNodeReadyGenerated()`”
|
||
- “可选节点只是文档建议,生成结果不会变”
|
||
|
||
当前更准确的理解是:
|
||
|
||
- 只有缺少 `_Ready()` 时才会自动补 override
|
||
- `OnGetNodeReadyGenerated()` 只存在于自动补 `_Ready()` 的路径
|
||
- `Required = false` 会真实切换到 `GetNodeOrNull<T>()`
|
||
- `Lookup` 只影响“未显式给路径时”的推断前缀;显式 `Path` 不会被二次改写
|
||
|
||
## 推荐阅读
|
||
|
||
1. [BindNodeSignal 生成器](./bind-node-signal-generator.md)
|
||
2. [Godot 项目生成器](./godot-project-generator.md)
|
||
3. [Godot UI 系统](../godot/ui.md)
|
||
4. [Godot 模块总览](../godot/index.md)
|