docs(source-generators): 添加源代码生成器完整文档

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

View File

@ -0,0 +1,680 @@
# BindNodeSignal 生成器
> 自动生成 Godot 节点信号绑定与解绑逻辑,消除事件订阅样板代码
## 概述
BindNodeSignal 生成器为标记了 `[BindNodeSignal]` 特性的方法自动生成节点事件绑定和解绑代码。它将 `_Ready()`
`_ExitTree()` 中重复的 `+=``-=` 样板代码收敛到生成器中统一维护。
### 核心功能
- **自动事件绑定**:在 `_Ready()` 中自动订阅节点事件
- **自动事件解绑**:在 `_ExitTree()` 中自动取消订阅
- **多事件绑定**:一个方法可以绑定到多个节点事件
- **类型安全检查**:编译时验证方法签名与事件委托的兼容性
- **与 GetNode 集成**:无缝配合 `[GetNode]` 特性使用
## 基础使用
### 标记事件处理方法
使用 `[BindNodeSignal]` 特性标记处理节点事件的方法:
```csharp
using GFramework.Godot.SourceGenerators.Abstractions;
using Godot;
public partial class MainMenu : Control
{
private Button _startButton = null!;
private Button _settingsButton = null!;
private Button _quitButton = null!;
[BindNodeSignal(nameof(_startButton), nameof(Button.Pressed))]
private void OnStartButtonPressed()
{
StartGame();
}
[BindNodeSignal(nameof(_settingsButton), nameof(Button.Pressed))]
private void OnSettingsButtonPressed()
{
ShowSettings();
}
[BindNodeSignal(nameof(_quitButton), nameof(Button.Pressed))]
private void OnQuitButtonPressed()
{
QuitGame();
}
public override void _Ready()
{
__BindNodeSignals_Generated();
}
public override void _ExitTree()
{
__UnbindNodeSignals_Generated();
}
}
```
### 生成的代码
编译器会为标记的类自动生成以下代码:
```csharp
// <auto-generated />
#nullable enable
namespace YourNamespace;
partial class MainMenu
{
private void __BindNodeSignals_Generated()
{
_startButton.Pressed += OnStartButtonPressed;
_settingsButton.Pressed += OnSettingsButtonPressed;
_quitButton.Pressed += OnQuitButtonPressed;
}
private void __UnbindNodeSignals_Generated()
{
_startButton.Pressed -= OnStartButtonPressed;
_settingsButton.Pressed -= OnSettingsButtonPressed;
_quitButton.Pressed -= OnQuitButtonPressed;
}
}
```
## 参数说明
`[BindNodeSignal]` 特性需要两个参数:
| 参数 | 类型 | 说明 |
|-----------------|--------|-----------------------------|
| `nodeFieldName` | string | 目标节点字段名(使用 `nameof` 推荐) |
| `signalName` | string | 目标节点上的 CLR 事件名(使用 `nameof` |
```csharp
[BindNodeSignal("_startButton", "Pressed")] // 字符串字面量
[BindNodeSignal(nameof(_startButton), nameof(Button.Pressed))] // 推荐nameof 表达式
```
## 高级用法
### 带参数的事件处理
处理带参数的事件(如 `SpinBox.ValueChanged`
```csharp
using Godot;
public partial class SettingsPanel : Control
{
private SpinBox _volumeSpinBox = null!;
private SpinBox _brightnessSpinBox = null!;
// 参数类型必须与事件委托匹配
[BindNodeSignal(nameof(_volumeSpinBox), nameof(SpinBox.ValueChanged))]
private void OnVolumeChanged(double value)
{
SetVolume((float)value);
}
[BindNodeSignal(nameof(_brightnessSpinBox), nameof(SpinBox.ValueChanged))]
private void OnBrightnessChanged(double value)
{
SetBrightness((float)value);
}
public override void _Ready()
{
__BindNodeSignals_Generated();
}
public override void _ExitTree()
{
__UnbindNodeSignals_Generated();
}
}
```
### 多事件绑定
一个方法可以同时绑定到多个节点的事件:
```csharp
public partial class MultiButtonHud : Control
{
private Button _buttonA = null!;
private Button _buttonB = null!;
private Button _buttonC = null!;
// 一个方法处理多个按钮的点击
[BindNodeSignal(nameof(_buttonA), nameof(Button.Pressed))]
[BindNodeSignal(nameof(_buttonB), nameof(Button.Pressed))]
[BindNodeSignal(nameof(_buttonC), nameof(Button.Pressed))]
private void OnAnyButtonPressed()
{
PlayClickSound();
}
public override void _Ready()
{
__BindNodeSignals_Generated();
}
public override void _ExitTree()
{
__UnbindNodeSignals_Generated();
}
}
```
### 与 [GetNode] 组合使用
推荐与 `[GetNode]` 特性结合使用:
```csharp
using GFramework.Godot.SourceGenerators.Abstractions;
using Godot;
public partial class GameHud : Control
{
// 使用 GetNode 自动获取节点
[GetNode]
private Button _pauseButton = null!;
[GetNode]
private ProgressBar _healthBar = null!;
[GetNode("UI/ScoreLabel")]
private Label _scoreLabel = null!;
// 使用 BindNodeSignal 绑定事件
[BindNodeSignal(nameof(_pauseButton), nameof(Button.Pressed))]
private void OnPauseButtonPressed()
{
TogglePause();
}
// 多事件绑定示例
[BindNodeSignal(nameof(_healthBar), nameof(Range.ValueChanged))]
private void OnHealthChanged(double value)
{
UpdateHealthDisplay(value);
}
public override void _Ready()
{
// 先注入节点,再绑定信号
__InjectGetNodes_Generated();
__BindNodeSignals_Generated();
}
public override void _ExitTree()
{
__UnbindNodeSignals_Generated();
}
}
```
### 复杂事件处理场景
实现完整的 UI 事件处理:
```csharp
public partial class InventoryUI : Control
{
// 节点
[GetNode]
private ItemList _itemList = null!;
[GetNode]
private Button _useButton = null!;
[GetNode]
private Button _dropButton = null!;
[GetNode]
private LineEdit _searchBox = null!;
// 事件处理
[BindNodeSignal(nameof(_itemList), nameof(ItemList.ItemSelected))]
private void OnItemSelected(long index)
{
SelectItem((int)index);
}
[BindNodeSignal(nameof(_itemList), nameof(ItemList.ItemActivated))]
private void OnItemActivated(long index)
{
UseItem((int)index);
}
[BindNodeSignal(nameof(_useButton), nameof(Button.Pressed))]
private void OnUseButtonPressed()
{
UseSelectedItem();
}
[BindNodeSignal(nameof(_dropButton), nameof(Button.Pressed))]
private void OnDropButtonPressed()
{
DropSelectedItem();
}
[BindNodeSignal(nameof(_searchBox), nameof(LineEdit.TextChanged))]
private void OnSearchTextChanged(string newText)
{
FilterItems(newText);
}
public override void _Ready()
{
__InjectGetNodes_Generated();
__BindNodeSignals_Generated();
InitializeInventory();
}
public override void _ExitTree()
{
__UnbindNodeSignals_Generated();
}
}
```
## 生命周期管理
### 自动生成生命周期方法
如果类没有 `_Ready()``_ExitTree()`,生成器会自动生成:
```csharp
public partial class AutoLifecycleHud : Control
{
private Button _button = null!;
[BindNodeSignal(nameof(_button), nameof(Button.Pressed))]
private void OnButtonPressed()
{
// 处理点击
}
// 无需手动声明 _Ready 和 _ExitTree
// 生成器会自动生成:
// public override void _Ready() { __BindNodeSignals_Generated(); }
// public override void _ExitTree() { __UnbindNodeSignals_Generated(); }
}
```
### 手动生命周期调用
如果已有生命周期方法,需要手动调用生成的方法:
```csharp
public partial class CustomLifecycleHud : Control
{
private Button _button = null!;
[BindNodeSignal(nameof(_button), nameof(Button.Pressed))]
private void OnButtonPressed()
{
HandlePress();
}
public override void _Ready()
{
// 必须手动调用绑定方法
__BindNodeSignals_Generated();
// 自定义初始化逻辑
InitializeUI();
}
public override void _ExitTree()
{
// 必须手动调用解绑方法
__UnbindNodeSignals_Generated();
// 自定义清理逻辑
CleanupResources();
}
}
```
**注意**:如果在 `_Ready()` 中不调用 `__BindNodeSignals_Generated()`,编译器会发出警告 `GF_Godot_BindNodeSignal_008`
## 诊断信息
生成器会在以下情况报告编译错误或警告:
### GF_Godot_BindNodeSignal_001 - 不支持嵌套类
**错误信息**`Class '{ClassName}' cannot use [BindNodeSignal] inside a nested type`
**解决方案**:将嵌套类提取为独立的类
```csharp
// ❌ 错误
public partial class Outer
{
public partial class Inner
{
[BindNodeSignal(nameof(_button), nameof(Button.Pressed))]
private void OnPressed() { } // 错误
}
}
// ✅ 正确
public partial class Inner
{
[BindNodeSignal(nameof(_button), nameof(Button.Pressed))]
private void OnPressed() { }
}
```
### GF_Godot_BindNodeSignal_002 - 不支持静态方法
**错误信息**`Method '{MethodName}' cannot be static when using [BindNodeSignal]`
**解决方案**:改为实例方法
```csharp
// ❌ 错误
[BindNodeSignal(nameof(_button), nameof(Button.Pressed))]
private static void OnPressed() { }
// ✅ 正确
[BindNodeSignal(nameof(_button), nameof(Button.Pressed))]
private void OnPressed() { }
```
### GF_Godot_BindNodeSignal_003 - 节点字段不存在
**错误信息**
`Method '{MethodName}' references node field '{FieldName}', but no matching field exists on class '{ClassName}'`
**解决方案**:确保引用的字段存在且名称正确
```csharp
// ❌ 错误_button 字段不存在
[BindNodeSignal(nameof(_button), nameof(Button.Pressed))]
private void OnPressed() { }
// ✅ 正确
private Button _button = null!;
[BindNodeSignal(nameof(_button), nameof(Button.Pressed))]
private void OnPressed() { }
```
### GF_Godot_BindNodeSignal_004 - 节点字段必须是实例字段
**错误信息**`Method '{MethodName}' references node field '{FieldName}', but that field must be an instance field`
**解决方案**:将节点字段改为实例字段(非静态)
```csharp
// ❌ 错误
private static Button _button = null!;
// ✅ 正确
private Button _button = null!;
```
### GF_Godot_BindNodeSignal_005 - 字段类型必须继承自 Godot.Node
**错误信息**`Field '{FieldName}' must be a Godot.Node type to use [BindNodeSignal]`
**解决方案**:确保字段类型继承自 `Godot.Node`
```csharp
// ❌ 错误
private string _text = null!; // string 不是 Node 类型
[BindNodeSignal(nameof(_text), "Changed")] // 错误
// ✅ 正确
private Button _button = null!; // Button 继承自 Node
[BindNodeSignal(nameof(_button), nameof(Button.Pressed))]
```
### GF_Godot_BindNodeSignal_006 - 目标事件不存在
**错误信息**`Field '{FieldName}' does not contain an event named '{EventName}'`
**解决方案**:确保事件名称正确
```csharp
private Button _button = null!;
// ❌ 错误Click 不是 Button 的事件
[BindNodeSignal(nameof(_button), "Click")]
// ✅ 正确:使用正确的事件名
[BindNodeSignal(nameof(_button), nameof(Button.Pressed))]
```
### GF_Godot_BindNodeSignal_007 - 方法签名不兼容
**错误信息**`Method '{MethodName}' is not compatible with event '{EventName}' on field '{FieldName}'`
**解决方案**:确保方法签名与事件委托匹配
```csharp
private SpinBox _spinBox = null!;
// ❌ 错误SpinBox.ValueChanged 需要 double 参数
[BindNodeSignal(nameof(_spinBox), nameof(SpinBox.ValueChanged))]
private void OnValueChanged() { } // 缺少参数
// ✅ 正确
[BindNodeSignal(nameof(_spinBox), nameof(SpinBox.ValueChanged))]
private void OnValueChanged(double value) { }
```
### GF_Godot_BindNodeSignal_008 - 需要在 _Ready 中调用绑定方法
**警告信息**
`Class '{ClassName}' defines _Ready(); call __BindNodeSignals_Generated() there to bind [BindNodeSignal] handlers`
**解决方案**:在 `_Ready()` 中手动调用 `__BindNodeSignals_Generated()`
```csharp
public override void _Ready()
{
__BindNodeSignals_Generated(); // ✅ 必须手动调用
// 其他初始化...
}
```
### GF_Godot_BindNodeSignal_009 - 需要在 _ExitTree 中调用解绑方法
**警告信息**
`Class '{ClassName}' defines _ExitTree(); call __UnbindNodeSignals_Generated() there to unbind [BindNodeSignal] handlers`
**解决方案**:在 `_ExitTree()` 中手动调用 `__UnbindNodeSignals_Generated()`
```csharp
public override void _ExitTree()
{
__UnbindNodeSignals_Generated(); // ✅ 必须手动调用
// 其他清理...
}
```
### GF_Godot_BindNodeSignal_010 - 构造参数无效
**错误信息**
`Method '{MethodName}' uses [BindNodeSignal] with an invalid '{ParameterName}' constructor argument; it must be a non-empty string literal`
**解决方案**:使用有效的字符串字面量或 nameof 表达式
```csharp
// ❌ 错误:空字符串
[BindNodeSignal("", nameof(Button.Pressed))]
// ❌ 错误null 值
[BindNodeSignal(null, nameof(Button.Pressed))]
// ✅ 正确
[BindNodeSignal(nameof(_button), nameof(Button.Pressed))]
```
## 最佳实践
### 1. 使用 nameof 表达式
使用 `nameof` 而不是字符串字面量,以获得重构支持和编译时检查:
```csharp
// ❌ 不推荐:字符串字面量
[BindNodeSignal("_button", "Pressed")]
// ✅ 推荐nameof 表达式
[BindNodeSignal(nameof(_button), nameof(Button.Pressed))]
```
### 2. 保持方法命名一致
使用统一的命名约定提高代码可读性:
```csharp
// ✅ 推荐On + 节点名 + 事件名
[BindNodeSignal(nameof(_startButton), nameof(Button.Pressed))]
private void OnStartButtonPressed() { }
[BindNodeSignal(nameof(_volumeSlider), nameof(Slider.ValueChanged))]
private void OnVolumeSliderValueChanged(double value) { }
```
### 3. 分组相关事件处理
将相关的事件处理方法放在一起,便于维护:
```csharp
public partial class GameHud : Control
{
// UI 节点
[GetNode]
private Button _pauseButton = null!;
[GetNode]
private Button _menuButton = null!;
// UI 事件处理(放在一起)
[BindNodeSignal(nameof(_pauseButton), nameof(Button.Pressed))]
private void OnPauseButtonPressed() { }
[BindNodeSignal(nameof(_menuButton), nameof(Button.Pressed))]
private void OnMenuButtonPressed() { }
}
```
### 4. 正确处理生命周期
始终确保事件解绑,避免内存泄漏:
```csharp
public partial class SafeHud : Control
{
private Button _button = null!;
[BindNodeSignal(nameof(_button), nameof(Button.Pressed))]
private void OnButtonPressed() { }
public override void _Ready()
{
__BindNodeSignals_Generated();
}
public override void _ExitTree()
{
// 确保解绑事件
__UnbindNodeSignals_Generated();
}
}
```
### 5. 对比手动事件绑定
| 方式 | 代码量 | 可维护性 | 错误风险 | 推荐场景 |
|--------------------|-----|------|----------|------------|
| 手动 `+=` / `-=` | 多 | 中 | 高(易遗漏解绑) | 简单场景 |
| `[BindNodeSignal]` | 少 | 高 | 低(编译器检查) | 复杂 UI、频繁事件 |
```csharp
// ❌ 不推荐:手动绑定
public override void _Ready()
{
_startButton.Pressed += OnStartButtonPressed;
_settingsButton.Pressed += OnSettingsButtonPressed;
_quitButton.Pressed += OnQuitButtonPressed;
}
public override void _ExitTree()
{
// 容易遗漏解绑
_startButton.Pressed -= OnStartButtonPressed;
_quitButton.Pressed -= OnQuitButtonPressed; // 遗漏了 _settingsButton
}
// ✅ 推荐:使用 [BindNodeSignal]
[BindNodeSignal(nameof(_startButton), nameof(Button.Pressed))]
private void OnStartButtonPressed() { }
[BindNodeSignal(nameof(_settingsButton), nameof(Button.Pressed))]
private void OnSettingsButtonPressed() { }
[BindNodeSignal(nameof(_quitButton), nameof(Button.Pressed))]
private void OnQuitButtonPressed() { }
```
### 6. 与 [ContextAware] 组合使用
在需要架构访问的场景中,与 `[ContextAware]` 结合:
```csharp
using GFramework.SourceGenerators.Abstractions.Rule;
using GFramework.Godot.SourceGenerators.Abstractions;
[ContextAware]
public partial class GameController : Node
{
[GetNode]
private Button _actionButton = null!;
private IGameModel _gameModel = null!;
[BindNodeSignal(nameof(_actionButton), nameof(Button.Pressed))]
private void OnActionButtonPressed()
{
// 可以直接使用架构功能
this.SendCommand(new PlayerActionCommand());
}
public override void _Ready()
{
__InjectContextBindings_Generated();
__InjectGetNodes_Generated();
__BindNodeSignals_Generated();
}
public override void _ExitTree()
{
__UnbindNodeSignals_Generated();
}
}
```
## 相关文档
- [Source Generators 概述](./index)
- [GetNode 生成器](./get-node-generator)
- [ContextAware 生成器](./context-aware-generator)
- [Godot 信号文档](https://docs.godotengine.org/en/stable/classes/class_signal.html)

View File

@ -0,0 +1,496 @@
# 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)

View File

@ -14,6 +14,8 @@ GFramework.SourceGenerators 是 GFramework 框架的源代码生成器包,通
- [GenerateEnumExtensions 属性生成器](#generateenumextensions-属性生成器)
- [Priority 属性生成器](#priority-属性生成器)
- [Context Get 注入生成器](#context-get-注入生成器)
- [GetNode 生成器 (Godot)](#getnode-生成器)
- [BindNodeSignal 生成器 (Godot)](#bindnodesignal-生成器)
- [诊断信息](#诊断信息)
- [性能优势](#性能优势)
- [使用示例](#使用示例)
@ -41,6 +43,11 @@ GFramework.SourceGenerators 利用 Roslyn 源代码生成器技术,在编译
- **[Priority] 属性**:自动实现 IPrioritized 接口,为类添加优先级标记
- **Context Get 注入特性**自动注入架构组件GetModel/GetSystem/GetUtility/GetService/GetAll
### Godot 专用生成器
- **[GetNode] 属性 (Godot)**:自动获取 Godot 节点引用,支持多种查找模式
- **[BindNodeSignal] 属性 (Godot)**:自动生成 Godot 节点信号绑定与解绑逻辑
### 🔧 高级特性
- **智能诊断**:生成器包含详细的错误诊断信息
@ -379,6 +386,82 @@ public enum PlayerState
| GenerateIsMethods | bool | true | 是否为每个枚举值生成 IsX 方法 |
| GenerateIsInMethod | bool | true | 是否生成 IsIn 方法 |
## GetNode 生成器
GetNode 生成器为标记了 `[GetNode]` 特性的字段自动生成 Godot 节点获取代码,无需手动调用 `GetNode<T>()` 方法。
### 主要功能
- **自动节点获取**:根据路径或字段名自动获取 Godot 节点
- **多种查找模式**:支持唯一名(`%Name`)、相对路径、绝对路径查找
- **可选节点支持**:可以标记节点为可选,获取失败时返回 null
- **_Ready 钩子**:自动生成 `_Ready()` 方法注入节点获取逻辑
### 基础示例
```csharp
using GFramework.Godot.SourceGenerators.Abstractions;
using Godot;
public partial class PlayerHud : Control
{
[GetNode]
private Label _healthLabel = null!;
[GetNode("HUD/ScoreValue")]
private Label _scoreLabel = null!;
public override void _Ready()
{
__InjectGetNodes_Generated();
_healthLabel.Text = "100";
}
}
```
**完整文档**[GetNode 生成器](./get-node-generator)
## BindNodeSignal 生成器
BindNodeSignal 生成器为标记了 `[BindNodeSignal]` 特性的方法自动生成节点事件绑定和解绑代码。
### 主要功能
- **自动事件绑定**:在 `_Ready()` 中自动订阅节点事件
- **自动事件解绑**:在 `_ExitTree()` 中自动取消订阅
- **多事件绑定**:一个方法可以绑定到多个节点事件
- **类型安全检查**:编译时验证方法签名与事件委托的兼容性
### 基础示例
```csharp
using GFramework.Godot.SourceGenerators.Abstractions;
using Godot;
public partial class MainMenu : Control
{
private Button _startButton = null!;
[BindNodeSignal(nameof(_startButton), nameof(Button.Pressed))]
private void OnStartButtonPressed()
{
StartGame();
}
public override void _Ready()
{
__BindNodeSignals_Generated();
}
public override void _ExitTree()
{
__UnbindNodeSignals_Generated();
}
}
```
**完整文档**[BindNodeSignal 生成器](./bind-node-signal-generator)
## 诊断信息
GFramework.SourceGenerators 提供详细的编译时诊断信息,帮助开发者快速定位和解决问题。