Object.has_signal
2026/4/14大约 7 分钟
最后同步日期:2026-04-15 | Godot 官方原文 — Object.has_signal
Object.has_signal
定义
has_signal 就像查字典——你想确认某个对象身上有没有一个叫特定名字的"信号"。就像你翻开一本产品说明书,想看看这个设备有没有"震动提醒"功能。说明书上有,就有;没有,就没有。
再打个比方:你收到了一个快递包裹,上面写着"按铃通知"。但在按铃之前,你想先确认这个包裹上到底有没有门铃。has_signal 就是帮你"看一眼有没有铃铛"的工具。
在游戏开发中,这个方法常用于动态检查:当你拿到一个不确定类型的节点时,先看看它有没有某个信号,再决定要不要连接,避免因为信号不存在而报错。
一句话总结
has_signal 的作用是"查字典":检查一个对象身上是否存在指定名称的信号,避免盲目连接导致错误。
函数签名
C#
public bool HasSignal(StringName signal)GDScript
has_signal(signal: StringName) -> bool参数说明
| 参数 | 类型 | 必需 | 说明 |
|---|---|---|---|
| signal | StringName | 是 | 要检查的信号名称,比如 "timeout"、"pressed"、"body_entered" 等 |
返回值
| 类型 | 说明 |
|---|---|
| bool | 返回 true 表示该对象拥有这个信号;返回 false 表示没有这个信号 |
代码示例
基础用法
最简单的用法——检查 Button 节点是否拥有 pressed 信号:
C#
using Godot;
public partial class SignalCheckDemo : Node
{
public override void _Ready()
{
var button = new Button();
var timer = new Timer();
// 检查 Button 是否有 pressed 信号
bool hasPressed = button.HasSignal("pressed");
GD.Print("Button 是否有 pressed 信号: " + hasPressed);
// 运行结果: Button 是否有 pressed 信号: True
// 检查 Button 是否有 timeout 信号(这是 Timer 的信号)
bool hasTimeout = button.HasSignal("timeout");
GD.Print("Button 是否有 timeout 信号: " + hasTimeout);
// 运行结果: Button 是否有 timeout 信号: False
// 检查 Timer 是否有 timeout 信号
bool timerHasTimeout = timer.HasSignal("timeout");
GD.Print("Timer 是否有 timeout 信号: " + timerHasTimeout);
// 运行结果: Timer 是否有 timeout 信号: True
// 检查一个不存在的信号名
bool hasFake = button.HasSignal("not_a_real_signal");
GD.Print("Button 是否有 not_a_real_signal 信号: " + hasFake);
// 运行结果: Button 是否有 not_a_real_signal 信号: False
}
}GDScript
extends Node
func _ready() -> void:
var button = Button.new()
var timer = Timer.new()
# 检查 Button 是否有 pressed 信号
var has_pressed = button.has_signal("pressed")
print("Button 是否有 pressed 信号: %s" % has_pressed)
# 运行结果: Button 是否有 pressed 信号: True
# 检查 Button 是否有 timeout 信号(这是 Timer 的信号)
var has_timeout = button.has_signal("timeout")
print("Button 是否有 timeout 信号: %s" % has_timeout)
# 运行结果: Button 是否有 timeout 信号: False
# 检查 Timer 是否有 timeout 信号
var timer_has_timeout = timer.has_signal("timeout")
print("Timer 是否有 timeout 信号: %s" % timer_has_timeout)
# 运行结果: Timer 是否有 timeout 信号: True
# 检查一个不存在的信号名
var has_fake = button.has_signal("not_a_real_signal")
print("Button 是否有 not_a_real_signal 信号: %s" % has_fake)
# 运行结果: Button 是否有 not_a_real_signal 信号: False实际场景
在实际游戏开发中,has_signal 常用于处理"不确定类型的节点"。比如你有一个检测区域(Area2D),当有物体进入时,你想检查进入的物体是否有某个自定义信号(比如 health_depleted),如果有就连接它来监听该物体的死亡事件:
C#
using Godot;
public partial class DetectionZone : Area2D
{
public override void _Ready()
{
// 监听有物体进入检测区域
BodyEntered += OnBodyEntered;
}
private void OnBodyEntered(Node2D body)
{
GD.Print($"检测到物体进入: {body.Name}(类型: {body.GetType().Name})");
// 检查进入的物体是否拥有 health_depleted 信号
// (只有带血量系统的物体才有这个信号)
if (body.HasSignal("HealthDepleted"))
{
GD.Print($" -> {body.Name} 拥有 HealthDepleted 信号,正在连接...");
body.Connect("HealthDepleted", Callable.From(() => OnTargetDied(body)));
// 运行结果:
// 检测到物体进入: Enemy(类型: Enemy)
// -> Enemy 拥有 HealthDepleted 信号,正在连接...
}
else
{
GD.Print($" -> {body.Name} 没有 HealthDepleted 信号,忽略");
// 运行结果(普通装饰物进入时):
// 检测到物体进入: Barrel(类型: RigidBody2D)
// -> Barrel 没有 HealthDepleted 信号,忽略
}
}
private void OnTargetDied(Node2D body)
{
GD.Print($"目标 {body.Name} 已死亡,移除标记");
// 运行结果: 目标 Enemy 已死亡,移除标记
}
}GDScript
extends Area2D
func _ready() -> void:
# 监听有物体进入检测区域
body_entered.connect(_on_body_entered)
func _on_body_entered(body: Node2D) -> void:
print("检测到物体进入: %s(类型: %s)" % [body.name, body.get_class()])
# 检查进入的物体是否拥有 health_depleted 信号
# (只有带血量系统的物体才有这个信号)
if body.has_signal("health_depleted"):
print(" -> %s 拥有 health_depleted 信号,正在连接..." % body.name)
body.health_depleted.connect(_on_target_died.bind(body))
# 运行结果:
# 检测到物体进入: Enemy(类型: Enemy)
# -> Enemy 拥有 health_depleted 信号,正在连接...
else:
print(" -> %s 没有 health_depleted 信号,忽略" % body.name)
# 运行结果(普通装饰物进入时):
# 检测到物体进入: Barrel(类型: RigidBody2D)
# -> Barrel 没有 health_depleted 信号,忽略
func _on_target_died(body: Node2D) -> void:
print("目标 %s 已死亡,移除标记" % body.name)
# 运行结果: 目标 Enemy 已死亡,移除标记进阶用法
在更复杂的场景中,has_signal 可以用于编写通用的、类型无关的信号连接工具方法,或者用于动态遍历一个对象的所有信号:
C#
using Godot;
using System.Collections.Generic;
public partial class GenericSignalConnector : Node
{
/// <summary>
/// 通用信号连接方法:安全地尝试连接任意信号,自动处理信号不存在的情况
/// </summary>
public override void _Ready()
{
var button = new Button();
var timer = new Timer();
// 尝试连接多个可能的信号
var signalsToTry = new List<string>
{
"pressed", // Button 有这个信号
"timeout", // Button 没有这个信号
"toggled", // Button 有这个信号
"body_entered" // Button 没有这个信号
};
GD.Print("=== 检查 Button 的信号 ===");
foreach (var signalName in signalsToTry)
{
TryConnectSignal(button, signalName);
}
GD.Print("\n=== 检查 Timer 的信号 ===");
TryConnectSignal(timer, "timeout");
TryConnectSignal(timer, "pressed");
// 运行结果:
// === 检查 Button 的信号 ===
// [成功] Button 拥有信号 "pressed"
// [跳过] Button 没有信号 "timeout"
// [成功] Button 拥有信号 "toggled"
// [跳过] Button 没有信号 "body_entered"
//
// === 检查 Timer 的信号 ===
// [成功] Timer 拥有信号 "timeout"
// [跳过] Timer 没有信号 "pressed"
}
private void TryConnectSignal(GodotObject obj, string signalName)
{
string typeName = obj.GetClass();
if (obj.HasSignal(signalName))
{
GD.Print($"[成功] {typeName} 拥有信号 \"{signalName}\"");
}
else
{
GD.Print($"[跳过] {typeName} 没有信号 \"{signalName}\"");
}
}
}GDScript
extends Node
func _ready() -> void:
var button = Button.new()
var timer = Timer.new()
# 尝试连接多个可能的信号
var signals_to_try = [
"pressed", # Button 有这个信号
"timeout", # Button 没有这个信号
"toggled", # Button 有这个信号
"body_entered" # Button 没有这个信号
]
print("=== 检查 Button 的信号 ===")
for signal_name in signals_to_try:
try_connect_signal(button, signal_name)
print("\n=== 检查 Timer 的信号 ===")
try_connect_signal(timer, "timeout")
try_connect_signal(timer, "pressed")
# 运行结果:
# === 检查 Button 的信号 ===
# [成功] Button 拥有信号 "pressed"
# [跳过] Button 没有信号 "timeout"
# [成功] Button 拥有信号 "toggled"
# [跳过] Button 没有信号 "body_entered"
#
# === 检查 Timer 的信号 ===
# [成功] Timer 拥有信号 "timeout"
# [跳过] Timer 没有信号 "pressed"
## 通用信号检查方法:安全地检查任意信号是否存在
func try_connect_signal(obj: Object, signal_name: String) -> void:
var type_name = obj.get_class()
if obj.has_signal(signal_name):
print("[成功] %s 拥有信号 \"%s\"" % [type_name, signal_name])
else:
print("[跳过] %s 没有信号 \"%s\"" % [type_name, signal_name])注意事项
- 只检查信号是否存在,不检查连接状态:
has_signal只能告诉你"这个对象有没有这个信号",不能告诉你"这个信号有没有被连接"。如果要检查连接状态,应该使用is_connected方法。 - 自定义信号也能检测到:如果一个脚本中用
[Signal](C#)或signal(GDScript)声明了自定义信号,has_signal同样能检测到。自定义信号和内置信号在运行时没有区别。 - 继承的信号也能检测到:如果一个节点继承了父类的信号(比如所有
Node都有tree_entered信号),has_signal也会返回true。你不需要区分这个信号是当前类定义的还是从父类继承的。 - 信号名大小写敏感:C# 中自定义信号的名称会被转换为 snake_case(例如声明
PlayerDied信号,检查时需要用"PlayerDied"或通过SignalName常量)。GDScript 中信号名就是声明时的原始名称。 - 不存在的信号名不会报错:如果你传入一个完全不存在的信号名,
has_signal只是返回false,不会抛出异常或报错。这让它在做条件检查时非常安全。 - C# 差异:C# 中方法名用 PascalCase(
HasSignal),GDScript 中用 snake_case(has_signal)。C# 中推荐使用SignalName常量来引用信号名,避免手写字符串出错。
