GFramework/docs/zh-CN/source-generators/bind-node-signal-generator.md
gewuyou 979db3b5a5 docs(reader-facing): 统一站内入口与公开术语
- 更新入口页的 reader-facing 骨架,统一起步路线、阅读顺序与站内导航
- 收口公开 README 与 Godot 页面中的内部口吻、文件名式表述和术语噪音
- 移除 docs/zh-CN 中残留的 GitHub README 外链,并同步刷新文档治理恢复状态
2026-04-27 08:55:18 +08:00

193 lines
5.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: BindNodeSignal 生成器
description: 说明 [BindNodeSignal] 当前生成什么、如何与 GetNode 协作,以及 _Ready 和 _ExitTree 的接入要求。
---
# BindNodeSignal 生成器
`[BindNodeSignal]` 把 Godot CLR event 的 `+=` / `-=` 样板收敛成生成方法。它只生成“如何订阅与解绑”,不会替你查找节点,也不会自动生成完整生命周期方法。
## 当前包关系
- 特性来源:`GFramework.Godot.SourceGenerators.Abstractions`
- 生成器实现:`GFramework.Godot.SourceGenerators`
- 使用前提:`nodeFieldName` 指向的字段必须继承 `Godot.Node`
## 最小用法
```csharp
using GFramework.Godot.SourceGenerators.Abstractions;
using Godot;
public partial class Hud : Control
{
[GetNode]
private Button _startButton = null!;
[GetNode]
private SpinBox _startOreSpinBox = null!;
[BindNodeSignal(nameof(_startButton), nameof(Button.Pressed))]
private void OnStartButtonPressed()
{
}
[BindNodeSignal(nameof(_startOreSpinBox), nameof(SpinBox.ValueChanged))]
private void OnStartOreValueChanged(double value)
{
}
public override void _Ready()
{
__InjectGetNodes_Generated();
__BindNodeSignals_Generated();
}
public override void _ExitTree()
{
__UnbindNodeSignals_Generated();
}
}
```
当前生成器会产出:
```csharp
private void __BindNodeSignals_Generated()
{
_startButton.Pressed += OnStartButtonPressed;
_startOreSpinBox.ValueChanged += OnStartOreValueChanged;
}
private void __UnbindNodeSignals_Generated()
{
_startButton.Pressed -= OnStartButtonPressed;
_startOreSpinBox.ValueChanged -= OnStartOreValueChanged;
}
```
## 生命周期边界
### 它只生成辅助方法,不生成 `_Ready()` / `_ExitTree()`
这是当前和 `[GetNode]` 最大的区别:
- `[GetNode]` 在缺少 `_Ready()` 时会补一个 override
- `[BindNodeSignal]` 只生成 `__BindNodeSignals_Generated()``__UnbindNodeSignals_Generated()`
所以你需要自己决定在哪个生命周期里调用它们。
### 已有生命周期但没调用时会给 warning
如果类型已经定义了 `_Ready()``_ExitTree()`,但没有调用对应生成方法,当前会给出 warning提醒你完成接线。
这意味着它更像“声明式订阅语法”,而不是“自动生命周期织入”。
## 当前契约
`[BindNodeSignal(nodeFieldName, signalName)]` 的两个参数都指向现有代码里的稳定符号:
- `nodeFieldName`:目标节点字段名
- `signalName`:该节点类型上的 CLR event 名
最推荐的写法仍然是:
```csharp
[BindNodeSignal(nameof(_startButton), nameof(Button.Pressed))]
```
这样字段或事件改名时,编译器能一起帮你更新。
## 当前会验证什么
生成器不是盲目拼字符串。按当前源码,它会在编译期验证:
- 方法必须是实例方法
- `nodeFieldName` 必须能解析到当前类型里的实例字段
- 该字段类型必须继承 `Godot.Node`
- `signalName` 必须能解析到该字段类型上的 CLR event
- 处理方法签名必须和 event delegate 兼容
例如:
- `Button.Pressed` 对应无参处理方法
- `SpinBox.ValueChanged` 对应 `double` 参数
如果签名不匹配,会直接报错,而不是生成一个运行时才失败的订阅。
## 多重绑定
`BindNodeSignalAttribute` 允许重复标记在同一个方法上,所以一个处理方法可以绑定多个事件:
```csharp
[BindNodeSignal(nameof(_buttonA), nameof(Button.Pressed))]
[BindNodeSignal(nameof(_buttonB), nameof(Button.Pressed))]
[BindNodeSignal(nameof(_buttonC), nameof(Button.Pressed))]
private void OnAnyButtonPressed()
{
}
```
当前生成器会为每个特性都生成一条 `+=` 和一条 `-=`
`ai-libs/CoreGrid` 里的 `GameplayHud``PauseMenu``OptionBrowser` 都在大量使用这种声明式绑定方式。
## 与 GetNode 的协作边界
`[BindNodeSignal]` 不负责拿到字段实例,只负责在字段已经可用的前提下做事件接线。
因此同类型同时使用时,顺序应该是:
1. `__InjectGetNodes_Generated()`
2. `__BindNodeSignals_Generated()`
3.`_ExitTree()` 调用 `__UnbindNodeSignals_Generated()`
这是当前项目侧真实采用路径,不是文档偏好。
## 当前强约束
以下约束直接来自生成器源码与测试:
- 目标类型必须是顶层 `partial class`
- 不支持嵌套类
- 方法不能是 `static`
- 节点字段必须存在且是实例字段
- 节点字段类型必须继承 `Godot.Node`
- 事件名必须是 CLR event不是任意字符串
- 如果你自己声明了 `__BindNodeSignals_Generated()``__UnbindNodeSignals_Generated()`,会触发命名冲突诊断
## 什么时候适合用 `[BindNodeSignal]`
适合:
- UI、菜单、HUD、面板类里按钮或输入事件很多
- 你想把订阅/解绑语义放回方法声明旁边,而不是堆在 `_Ready()` / `_ExitTree()`
- 你已经用 `[GetNode]` 或其他方式稳定拿到节点字段
不适合:
- 事件目标需要在运行时动态决定
- 你用的是 `Connect()` / `Disconnect()` 风格,而不是 CLR event
- 你需要比“字段 + 事件名”更复杂的订阅条件
## 与旧写法的边界
下面这些旧说法已经不准确:
-`[BindNodeSignal]` 会自动生成 `_Ready()` / `_ExitTree()`
- “它能处理所有 Godot signal 连接方式”
- “有没有 `__UnbindNodeSignals_Generated()` 都无所谓”
当前更准确的理解是:
- 它只生成成对的绑定/解绑辅助方法
- 当前设计面向 CLR event不自动调用 `Connect()` / `Disconnect()`
- 如果要避免节点退出后残留订阅,应在 `_ExitTree()` 中显式解绑
## 推荐阅读
1. [GetNode 生成器](./get-node-generator.md)
2. [Godot 项目生成器](./godot-project-generator.md)
3. [Godot UI 系统](../godot/ui.md)
4. [Godot 模块总览](../godot/index.md)