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