# GetNode 生成器 > 自动生成 Godot 节点获取逻辑,简化节点引用代码 ## 概述 GetNode 生成器为标记了 `[GetNode]` 特性的字段自动生成 Godot 节点获取代码,无需手动调用 `GetNode()` 方法。这在处理复杂 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 // #nullable enable namespace YourNamespace; partial class PlayerHud { private void __InjectGetNodes_Generated() { _healthLabel = GetNode("%HealthLabel"); _manaBar = GetNode("%ManaBar"); _scoreLabel = GetNode("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