---
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` 对应的字段还没完成解析。

这也是项目侧节点类的常见接法。

## 什么时候适合用 `[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)
