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