GFramework/docs/zh-CN/source-generators/get-node-generator.md
GeWuYou 38020c32a2 docs(source-generators): 添加源代码生成器完整文档
- 新增 GFramework.SourceGenerators 主文档,介绍编译时代码生成工具
- 详细说明 Log 属性生成器的使用方法和配置选项
- 完整描述 ContextAware 属性生成器的功能和测试场景配置
- 添加 GenerateEnumExtensions 属性生成器文档和使用示例
- 介绍 GetNode 生成器(Godot 专用)的节点获取功能
- 新增 BindNodeSignal 生成器文档,说明信号绑定与解绑机制
- 提供 Context Get 注入生成器的完整使用指南
- 添加诊断信息章节,涵盖所有生成器的错误提示和解决方案
- 包含性能优势对比和基准测试结果
- 提供多个完整使用示例,展示实际应用场景
- 整理最佳实践和常见问题解答
- 添加 BindNodeSignal 生成器专用文档,详细介绍其高级用法
2026-03-31 15:10:06 +08:00

497 lines
12 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.

# 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)