mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-04-01 02:47:13 +08:00
- 新增 GFramework.SourceGenerators 主文档,介绍编译时代码生成工具 - 详细说明 Log 属性生成器的使用方法和配置选项 - 完整描述 ContextAware 属性生成器的功能和测试场景配置 - 添加 GenerateEnumExtensions 属性生成器文档和使用示例 - 介绍 GetNode 生成器(Godot 专用)的节点获取功能 - 新增 BindNodeSignal 生成器文档,说明信号绑定与解绑机制 - 提供 Context Get 注入生成器的完整使用指南 - 添加诊断信息章节,涵盖所有生成器的错误提示和解决方案 - 包含性能优势对比和基准测试结果 - 提供多个完整使用示例,展示实际应用场景 - 整理最佳实践和常见问题解答 - 添加 BindNodeSignal 生成器专用文档,详细介绍其高级用法
497 lines
12 KiB
Markdown
497 lines
12 KiB
Markdown
# 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)
|