Object.emit_signal
2026/4/14大约 7 分钟
最后同步日期:2026-04-16 | Godot 官方原文 — Object.emit_signal
Object.emit_signal
定义
emit_signal 就像按下门铃按钮——之前用 connect 接好的电线已经就位,现在你按下按钮(触发信号),连接在电线另一端的门铃(处理方法)就会响起来。
再打个比方:你在公司用钉钉群发了一条消息,所有加入了这个群的人都会收到通知。emit_signal 就是你发消息这一步,而"加入群"就是之前 connect 做的事。
信号被触发后,所有连接到这个信号的处理方法都会被依次调用。你可以把信号想象成广播电台——电台发射信号(emit_signal),所有收音机(connect 过的方法)都能收到。
一句话总结
emit_signal 的作用是"按下按钮":主动触发一个信号,让所有连接了这个信号的方法自动执行。
函数签名
C#
// 方式一:使用 EmitSignal 方法
public Error EmitSignal(StringName signal, params Variant[] args)
// 方式二(推荐):使用 C# 信号触发语法
// 对于自定义信号,直接调用信号名:
EmitSignal(SignalName.MySignal);
EmitSignal(SignalName.HealthChanged, newValue);GDScript
emit_signal(signal: StringName, ...) -> int参数说明
| 参数 | 类型 | 必需 | 说明 |
|---|---|---|---|
| signal | StringName | 是 | 要触发的信号名称 |
| ...args | Variant(可变参数) | 否 | 信号携带的参数。比如 HealthChanged 信号可以携带新的血量值 |
返回值
| 类型 | 说明 |
|---|---|
| Error (C#) / int (GDScript) | 返回 Error.Ok(值为 0)表示信号触发成功 |
代码示例
基础用法
最简单的用法——定义一个自定义信号并手动触发它:
C#
using Godot;
public partial class MyButton : Button
{
// 声明一个自定义信号(无参数)
[Signal]
public delegate void ButtonClickedEventHandler();
public override void _Ready()
{
// 先连接自己的信号
ButtonClicked += OnButtonClicked;
// 手动触发信号
EmitSignal(SignalName.ButtonClicked);
// 运行结果: 自定义按钮点击信号被触发了!
}
private void OnButtonClicked()
{
GD.Print("自定义按钮点击信号被触发了!");
}
}GDScript
extends Button
# 声明一个自定义信号(无参数)
signal button_clicked
func _ready() -> void:
# 先连接自己的信号
button_clicked.connect(_on_button_clicked)
# 手动触发信号
emit_signal("button_clicked")
# 运行结果: 自定义按钮点击信号被触发了!
func _on_button_clicked() -> void:
print("自定义按钮点击信号被触发了!")实际场景
在实际游戏开发中,emit_signal 最常用的场景是"数值变化通知":当角色的血量、分数、等级等数据发生变化时,通过信号通知 UI 更新显示。下面是一个完整的血量系统示例:
C#
using Godot;
public partial class HealthSystem : Node
{
// 声明带参数的自定义信号
[Signal]
public delegate void HealthChangedEventHandler(int newHealth, int maxHealth);
[Signal]
public delegate void PlayerDiedEventHandler();
[Export] public int ExMaxHealth = 100;
private int _currentHealth;
public override void _Ready()
{
_currentHealth = ExMaxHealth;
// 连接信号到 UI
HealthChanged += OnHealthChanged;
PlayerDied += OnPlayerDied;
// 初始触发一次,更新 UI
EmitSignal(SignalName.HealthChanged, _currentHealth, ExMaxHealth);
GD.Print($"血量系统初始化完成,当前血量: {_currentHealth}/{ExMaxHealth}");
// 运行结果: 血量系统初始化完成,当前血量: 100/100
}
public void TakeDamage(int damage)
{
_currentHealth -= damage;
if (_currentHealth <= 0)
{
_currentHealth = 0;
// 血量归零,触发死亡信号
EmitSignal(SignalName.HealthChanged, 0, ExMaxHealth);
EmitSignal(SignalName.PlayerDied);
GD.Print("角色已死亡!");
// 运行结果(收到致命伤害后):
// 血量变化: 0/100
// 角色已死亡!
}
else
{
// 触发血量变化信号
EmitSignal(SignalName.HealthChanged, _currentHealth, ExMaxHealth);
GD.Print($"受到 {damage} 点伤害,剩余血量: {_currentHealth}/{ExMaxHealth}");
// 运行结果(受到30点伤害后):
// 血量变化: 70/100
// 受到 30 点伤害,剩余血量: 70/100
}
}
private void OnHealthChanged(int newHealth, int maxHealth)
{
GD.Print($"血量变化: {newHealth}/{maxHealth}");
// 运行结果: 血量变化: 70/100
}
private void OnPlayerDied()
{
GD.Print("游戏结束,显示复活界面");
// 运行结果: 游戏结束,显示复活界面
}
}GDScript
extends Node
# 声明带参数的自定义信号
signal health_changed(new_health: int, max_health: int)
signal player_died
@export var ex_max_health: int = 100
var _current_health: int
func _ready() -> void:
_current_health = ex_max_health
# 连接信号到处理方法
health_changed.connect(_on_health_changed)
player_died.connect(_on_player_died)
# 初始触发一次,更新 UI
emit_signal("health_changed", _current_health, ex_max_health)
print("血量系统初始化完成,当前血量: %d/%d" % [_current_health, ex_max_health])
# 运行结果: 血量系统初始化完成,当前血量: 100/100
func take_damage(damage: int) -> void:
_current_health -= damage
if _current_health <= 0:
_current_health = 0
# 血量归零,触发死亡信号
emit_signal("health_changed", 0, ex_max_health)
emit_signal("player_died")
print("角色已死亡!")
# 运行结果(收到致命伤害后):
# 血量变化: 0/100
# 角色已死亡!
else:
# 触发血量变化信号
emit_signal("health_changed", _current_health, ex_max_health)
print("受到 %d 点伤害,剩余血量: %d/%d" % [damage, _current_health, ex_max_health])
# 运行结果(受到30点伤害后):
# 血量变化: 70/100
# 受到 30 点伤害,剩余血量: 70/100
func _on_health_changed(new_health: int, max_health: int) -> void:
print("血量变化: %d/%d" % [new_health, max_health])
# 运行结果: 血量变化: 70/100
func _on_player_died() -> void:
print("游戏结束,显示复活界面")
# 运行结果: 游戏结束,显示复活界面进阶用法
多个对象监听同一个信号、跨节点触发信号、以及使用 call_deferred 延迟触发:
C#
using Godot;
public partial class QuestSystem : Node
{
// 自定义信号:任务完成,携带任务名和奖励金币数
[Signal]
public delegate void QuestCompletedEventHandler(string questName, int rewardGold);
private int _totalGold = 0;
public override void _Ready()
{
// 多个系统监听同一个信号
QuestCompleted += OnQuestCompletedLog;
QuestCompleted += OnQuestCompletedReward;
QuestCompleted += OnQuestCompletedNotify;
// 完成几个任务
CompleteQuest("消灭史莱姆", 50);
CompleteQuest("收集草药", 30);
CompleteQuest("击败Boss", 200);
GD.Print($"所有任务完成,总计获得金币: {_totalGold}");
// 运行结果:
// [日志] 任务完成: 消灭史莱姆,奖励 50 金币
// [奖励] 获得 50 金币,总计: 50
// [通知] 恭喜完成任务:消灭史莱姆!
// [日志] 任务完成: 收集草药,奖励 30 金币
// [奖励] 获得 30 金币,总计: 80
// [通知] 恭喜完成任务:收集草药!
// [日志] 任务完成: 击败Boss,奖励 200 金币
// [奖励] 获得 200 金币,总计: 280
// [通知] 恭喜完成任务:击败Boss!
// 所有任务完成,总计获得金币: 280
}
public void CompleteQuest(string questName, int rewardGold)
{
// 触发信号,所有监听者都会收到通知
EmitSignal(SignalName.QuestCompleted, questName, rewardGold);
}
private void OnQuestCompletedLog(string questName, int rewardGold)
{
GD.Print($"[日志] 任务完成: {questName},奖励 {rewardGold} 金币");
}
private void OnQuestCompletedReward(string questName, int rewardGold)
{
_totalGold += rewardGold;
GD.Print($"[奖励] 获得 {rewardGold} 金币,总计: {_totalGold}");
}
private void OnQuestCompletedNotify(string questName, int rewardGold)
{
GD.Print($"[通知] 恭喜完成任务:{questName}!");
}
}GDScript
extends Node
# 自定义信号:任务完成,携带任务名和奖励金币数
signal quest_completed(quest_name: String, reward_gold: int)
var _total_gold: int = 0
func _ready() -> void:
# 多个系统监听同一个信号
quest_completed.connect(_on_quest_completed_log)
quest_completed.connect(_on_quest_completed_reward)
quest_completed.connect(_on_quest_completed_notify)
# 完成几个任务
complete_quest("消灭史莱姆", 50)
complete_quest("收集草药", 30)
complete_quest("击败Boss", 200)
print("所有任务完成,总计获得金币: %d" % _total_gold)
# 运行结果:
# [日志] 任务完成: 消灭史莱姆,奖励 50 金币
# [奖励] 获得 50 金币,总计: 50
# [通知] 恭喜完成任务:消灭史莱姆!
# [日志] 任务完成: 收集草药,奖励 30 金币
# [奖励] 获得 30 金币,总计: 80
# [通知] 恭喜完成任务:收集草药!
# [日志] 任务完成: 击败Boss,奖励 200 金币
# [奖励] 获得 200 金币,总计: 280
# [通知] 恭喜完成任务:击败Boss!
# 所有任务完成,总计获得金币: 280
func complete_quest(quest_name: String, reward_gold: int) -> void:
# 触发信号,所有监听者都会收到通知
emit_signal("quest_completed", quest_name, reward_gold)
func _on_quest_completed_log(quest_name: String, reward_gold: int) -> void:
print("[日志] 任务完成: %s,奖励 %d 金币" % [quest_name, reward_gold])
func _on_quest_completed_reward(quest_name: String, reward_gold: int) -> void:
_total_gold += reward_gold
print("[奖励] 获得 %d 金币,总计: %d" % [reward_gold, _total_gold])
func _on_quest_completed_notify(quest_name: String, reward_gold: int) -> void:
print("[通知] 恭喜完成任务:%s!" % quest_name)注意事项
- 信号触发是同步的:
emit_signal调用后,所有连接的处理方法会立即依次执行,然后emit_signal才会返回。这意味着如果你的处理方法里写了耗时逻辑,会阻塞后续代码的执行。 - 不检查是否有监听者:即使没有任何方法连接到这个信号,
emit_signal也不会报错,只是什么都不会发生。这就像对着空房间喊话——没人回应,但也不会出错。 - 带参数的信号类型要匹配:如果你声明的信号带有参数(如
HealthChanged(int newHealth)),触发时传入的参数类型必须匹配。C# 中如果类型不匹配会编译报错,GDScript 中会在运行时报错。 - C# 中用 SignalName 触发:在 C# 中,推荐使用
EmitSignal(SignalName.XXX)的方式触发信号,而不是手动写字符串。SignalName是 Godot 自动生成的常量,可以避免拼写错误。 - Godot 内置信号无需手动触发:像
Button.Pressed、Timer.Timeout、Area2D.BodyEntered这些内置信号由引擎自动触发,你不需要手动调用emit_signal。只有自定义信号才需要手动触发。 - C# 差异:C# 中方法名用 PascalCase(
EmitSignal),GDScript 中用 snake_case(emit_signal)。
