# GetNode 生成器

> 自动生成 Godot 节点获取逻辑，简化节点引用代码

## 概述

GetNode 生成器为标记了 `[GetNode]` 特性的字段自动生成 Godot 节点获取代码，无需手动调用 `GetNode<T>()` 方法。这在处理复杂
UI 或场景树结构时特别有用。

### 核心功能

- **自动节点获取**：根据路径或字段名自动获取节点
- **多种查找模式**：支持唯一名、相对路径、绝对路径查找
- **可选节点支持**：可以标记节点为可选，获取失败时返回 null
- **智能路径推导**：未显式指定路径时自动从字段名推导
- **_Ready 钩子生成**：自动生成 `_Ready()` 方法注入节点获取逻辑

## 基础使用

### 标记节点字段

使用 `[GetNode]` 特性标记需要自动获取的节点字段：

```csharp
using GFramework.Godot.SourceGenerators.Abstractions;
using Godot;

public partial class PlayerHud : Control
{
    [GetNode]
    private Label _healthLabel = null!;

    [GetNode]
    private ProgressBar _manaBar = null!;

    [GetNode("ScoreContainer/ScoreValue")]
    private Label _scoreLabel = null!;

    public override void _Ready()
    {
        __InjectGetNodes_Generated();
        _healthLabel.Text = "100";
    }
}
```

### 生成的代码

编译器会为标记的类自动生成以下代码：

```csharp
// <auto-generated />
#nullable enable

namespace YourNamespace;

partial class PlayerHud
{
    private void __InjectGetNodes_Generated()
    {
        _healthLabel = GetNode<global::Godot.Label>("%HealthLabel");
        _manaBar = GetNode<global::Godot.ProgressBar>("%ManaBar");
        _scoreLabel = GetNode<global::Godot.Label>("ScoreContainer/ScoreValue");
    }

    partial void OnGetNodeReadyGenerated();

    public override void _Ready()
    {
        __InjectGetNodes_Generated();
        OnGetNodeReadyGenerated();
    }
}
```

## 配置选项

### 节点查找模式

通过 `Lookup` 参数控制节点查找方式：

```csharp
public partial class GameHud : Control
{
    // 自动推断（默认）：根据路径前缀自动选择
    [GetNode]
    private Label _titleLabel = null!;  // 默认使用唯一名 %TitleLabel

    // 唯一名查找
    [GetNode(Lookup = NodeLookupMode.UniqueName)]
    private Button _startButton = null!;  // %StartButton

    // 相对路径查找
    [GetNode("UI/HealthBar", Lookup = NodeLookupMode.RelativePath)]
    private ProgressBar _healthBar = null!;

    // 绝对路径查找
    [GetNode("/root/Main/GameUI/Score", Lookup = NodeLookupMode.AbsolutePath)]
    private Label _scoreLabel = null!;
}
```

### 查找模式说明

| 模式             | 路径前缀 | 适用场景           |
|----------------|------|----------------|
| `Auto`         | 自动选择 | 默认行为，推荐用于大多数场景 |
| `UniqueName`   | `%`  | 场景中使用唯一名的节点    |
| `RelativePath` | 无    | 需要相对路径查找的节点    |
| `AbsolutePath` | `/`  | 场景树根节点的绝对路径    |

### 可选节点

对于可能不存在的节点，可以设置为非必填：

```csharp
public partial class SettingsPanel : Control
{
    // 必须存在的节点（默认）
    [GetNode]
    private Label _titleLabel = null!;

    // 可选节点，可能不存在
    [GetNode(Required = false)]
    private Label? _debugLabel;  // 使用可空类型

    // 显式路径的可选节点
    [GetNode("AdvancedOptions", Required = false)]
    private VBoxContainer? _advancedOptions;

    public override void _Ready()
    {
        __InjectGetNodes_Generated();

        // 安全地访问可选节点
        _debugLabel?.Hide();
        _advancedOptions?.Hide();
    }
}
```

### 路径规则

生成器根据字段名和配置自动推导节点路径：

```csharp
public partial class Example : Control
{
    // 驼峰命名 → PascalCase 路径
    [GetNode]
    private Label _playerNameLabel = null!;  // → %PlayerNameLabel

    // m_ 前缀会被移除
    [GetNode]
    private Button m_confirmButton = null!;  // → %ConfirmButton

    // _ 前缀会被移除
    [GetNode]
    private ProgressBar _healthBar = null!;  // → %HealthBar

    // 显式路径优先于推导
    [GetNode("UI/CustomPath")]
    private Label _myLabel = null!;  // → UI/CustomPath
}
```

## 高级用法

### 与 [ContextAware] 组合使用

在 Godot 项目中结合使用 `[GetNode]` 和 `[ContextAware]`：

```csharp
using GFramework.Godot.SourceGenerators.Abstractions;
using GFramework.SourceGenerators.Abstractions.Rule;
using Godot;

[ContextAware]
public partial class GameController : Node
{
    [GetNode]
    private Label _scoreLabel = null!;

    [GetNode("HUD/HealthBar")]
    private ProgressBar _healthBar = null!;

    private IGameModel _gameModel = null!;

    public override void _Ready()
    {
        __InjectContextBindings_Generated();  // ContextAware 生成
        __InjectGetNodes_Generated();          // GetNode 生成

        _gameModel.Score.Register(OnScoreChanged);
    }

    private void OnScoreChanged(int newScore)
    {
        _scoreLabel.Text = newScore.ToString();
    }
}
```

### 复杂 UI 场景

处理复杂的嵌套 UI 结构：

```csharp
public partial class InventoryUI : Control
{
    // 主容器
    [GetNode]
    private GridContainer _itemGrid = null!;

    // 详细信息面板
    [GetNode("DetailsPanel/ItemName")]
    private Label _itemNameLabel = null!;

    [GetNode("DetailsPanel/ItemDescription")]
    private RichTextLabel _itemDescription = null!;

    // 操作按钮
    [GetNode("Actions/UseButton")]
    private Button _useButton = null!;

    [GetNode("Actions/DropButton")]
    private Button _dropButton = null!;

    // 可选的统计信息
    [GetNode("DetailsPanel/Stats", Required = false)]
    private VBoxContainer? _statsContainer;

    public override void _Ready()
    {
        __InjectGetNodes_Generated();

        // 使用注入的节点
        _useButton.Pressed += OnUseButtonPressed;
        _dropButton.Pressed += OnDropButtonPressed;
    }
}
```

### 手动 _Ready 调用

如果类已经有 `_Ready()` 方法，需要手动调用注入方法：

```csharp
public partial class CustomHud : Control
{
    [GetNode]
    private Label _statusLabel = null!;

    public override void _Ready()
    {
        // 必须手动调用节点注入
        __InjectGetNodes_Generated();

        // 自定义初始化逻辑
        _statusLabel.Text = "Ready";
        InitializeOtherComponents();
    }

    partial void OnGetNodeReadyGenerated()
    {
        // 这个方法会被生成器调用，可以在此添加额外初始化
    }
}
```

**注意**：如果不手动调用 `__InjectGetNodes_Generated()`，编译器会发出警告 `GF_Godot_GetNode_006`。

## 诊断信息

生成器会在以下情况报告编译错误或警告：

### GF_Godot_GetNode_001 - 不支持嵌套类

**错误信息**：`Class '{ClassName}' cannot use [GetNode] inside a nested type`

**解决方案**：将嵌套类提取为独立的类

```csharp
// ❌ 错误
public partial class Outer
{
    public partial class Inner
    {
        [GetNode]
        private Label _label = null!;  // 错误
    }
}

// ✅ 正确
public partial class Inner
{
    [GetNode]
    private Label _label = null!;
}
```

### GF_Godot_GetNode_002 - 不支持静态字段

**错误信息**：`Field '{FieldName}' cannot be static when using [GetNode]`

**解决方案**：改为实例字段

```csharp
// ❌ 错误
[GetNode]
private static Label _label = null!;

// ✅ 正确
[GetNode]
private Label _label = null!;
```

### GF_Godot_GetNode_003 - 不支持只读字段

**错误信息**：`Field '{FieldName}' cannot be readonly when using [GetNode]`

**解决方案**：移除 `readonly` 关键字

```csharp
// ❌ 错误
[GetNode]
private readonly Label _label = null!;

// ✅ 正确
[GetNode]
private Label _label = null!;
```

### GF_Godot_GetNode_004 - 字段类型必须继承自 Godot.Node

**错误信息**：`Field '{FieldName}' must be a Godot.Node type to use [GetNode]`

**解决方案**：确保字段类型继承自 `Godot.Node`

```csharp
// ❌ 错误
[GetNode]
private string _text = null!;  // string 不是 Node 类型

// ✅ 正确
[GetNode]
private Label _label = null!;  // Label 继承自 Node
```

### GF_Godot_GetNode_005 - 无法推导路径

**错误信息**：`Field '{FieldName}' does not provide a path and its name cannot be converted to a node path`

**解决方案**：显式指定节点路径

```csharp
// ❌ 错误：字段名无法转换为有效路径
[GetNode]
private Label _ = null!;

// ✅ 正确
[GetNode("UI/Label")]
private Label _ = null!;
```

### GF_Godot_GetNode_006 - 需要在 _Ready 中调用注入方法

**警告信息**：
`Class '{ClassName}' defines _Ready(); call __InjectGetNodes_Generated() there or remove _Ready() to use the generated hook`

**解决方案**：在 `_Ready()` 中手动调用 `__InjectGetNodes_Generated()`

```csharp
public partial class MyHud : Control
{
    [GetNode]
    private Label _label = null!;

    public override void _Ready()
    {
        __InjectGetNodes_Generated();  // ✅ 必须手动调用
        // 其他初始化...
    }
}
```

## 最佳实践

### 1. 使用一致的命名约定

保持字段名与场景树中节点名的一致性：

```csharp
// ✅ 推荐：字段名与节点名一致
[GetNode]
private Label _healthLabel = null!;  // 场景中的节点名为 HealthLabel

[GetNode]
private Button _startButton = null!;  // 场景中的节点名为 StartButton
```

### 2. 优先使用唯一名查找

在 Godot 编辑器中为重要节点启用唯一名（Unique Name），然后使用 `[GetNode]`：

```csharp
// Godot 场景中：%HealthBar（唯一名已启用）
// C# 代码中：
[GetNode]
private ProgressBar _healthBar = null!;  // 自动使用 %HealthBar
```

### 3. 合理处理可选节点

对于可能不存在的节点，使用 `Required = false`：

```csharp
public partial class DynamicUI : Control
{
    [GetNode]
    private Label _titleLabel = null!;

    // 可选组件
    [GetNode(Required = false)]
    private TextureRect? _iconImage;

    public override void _Ready()
    {
        __InjectGetNodes_Generated();

        // 安全地初始化可选组件
        if (_iconImage != null)
        {
            _iconImage.Texture = LoadDefaultIcon();
        }
    }
}
```

### 4. 组织复杂 UI 的路径

对于深层嵌套的 UI，使用显式路径：

```csharp
public partial class ComplexUI : Control
{
    // 使用相对路径明确表达层级关系
    [GetNode("MainContent/Header/Title")]
    private Label _title = null!;

    [GetNode("MainContent/Body/Stats/Health")]
    private Label _healthValue = null!;

    [GetNode("MainContent/Footer/ActionButtons/Save")]
    private Button _saveButton = null!;
}
```

### 5. 与 GetNode 方法的对比

| 方式             | 代码量 | 可维护性 | 类型安全   | 推荐场景      |
|----------------|-----|------|--------|-----------|
| 手动 `GetNode()` | 多   | 中    | 需要显式转换 | 简单场景      |
| `[GetNode]` 特性 | 少   | 高    | 编译时检查  | 复杂 UI、控制器 |

```csharp
// ❌ 不推荐：手动获取
public override void _Ready()
{
    _healthLabel = GetNode<Label>("%HealthLabel");
    _manaBar = GetNode<ProgressBar>("%ManaBar");
    _scoreLabel = GetNode<Label>("ScoreContainer/ScoreValue");
}

// ✅ 推荐：使用 [GetNode] 特性
[GetNode]
private Label _healthLabel = null!;

[GetNode]
private ProgressBar _manaBar = null!;

[GetNode("ScoreContainer/ScoreValue")]
private Label _scoreLabel = null!;

public override void _Ready()
{
    __InjectGetNodes_Generated();
}
```

## 相关文档

- [Source Generators 概述](./index)
- [BindNodeSignal 生成器](./bind-node-signal-generator)
- [ContextAware 生成器](./context-aware-generator)
- [Godot 节点文档](https://docs.godotengine.org/en/stable/classes/class_node.html)
