Skip to content

信号桥接

概述

信号桥接功能允许将 Godot 节点的原生信号连接到全局信号总线,实现集中化的事件管理。这对于解耦 UI 逻辑、游戏逻辑和系统逻辑非常有用。

基础桥接示例

完整示例代码

gdscript
@warning_ignore_start("static_called_on_instance")
extends Node

# 原生信号桥接示例
# 演示如何将 Godot 节点的原生信号桥接到 SignalBus
@onready var bus = SignalBus.get_singleton()
var bridge_ids = []

func _ready():
	bus.set_debug_enabled(false)
	print("\n=== 原生信号桥接示例 ===\n")
	bus.bridge_signal($Button, "pressed")
	bus.relay_node_signal($Button2, "pressed", "my_pressed")
	
	bus.subscribe("pressed", func():
		print("按钮被点击了!(通过 SignalBus)")
	)
	bus.subscribe("my_pressed", func():
		print("重命名桥接按钮被点击了!(通过 SignalBus)")
	)

桥接方法

1. bridge_signal()

将节点信号直接桥接到 SignalBus,保持原始信号名称。

gdscript
var bridge_id = bus.bridge_signal(node: Node, signal_name: String)

参数:

  • node: 节点对象
  • signal_name: 节点信号名称

返回值:

  • int64_t: 桥接ID,用于后续管理

示例:

gdscript
# 将按钮的 pressed 信号桥接到 SignalBus
bus.bridge_signal($Button, "pressed")

# 订阅桥接后的信号
bus.subscribe("pressed", func():
	print("按钮被点击了!(通过 SignalBus)")
)

2. relay_node_signal()

将节点信号转发为不同的 SignalBus 信号名称。

gdscript
var bridge_id = bus.relay_node_signal(node: Node, node_signal: String, bus_signal: String)

参数:

  • node: 节点对象
  • node_signal: 节点信号名称
  • bus_signal: SignalBus 信号名称

返回值:

  • int64_t: 桥接ID

示例:

gdscript
# 将按钮的 pressed 信号转发为 my_pressed 信号
bus.relay_node_signal($Button2, "pressed", "my_pressed")

# 订阅转发后的信号
bus.subscribe("my_pressed", func():
	print("重命名桥接按钮被点击了!(通过 SignalBus)")
)

桥接管理

桥接ID存储

gdscript
var bridge_ids = []

func _ready():
	var bridge1 = bus.bridge_signal($Button, "pressed")
	var bridge2 = bus.relay_node_signal($Button2, "pressed", "my_pressed")
	
	bridge_ids.append_array([bridge1, bridge2])

func _exit_tree():
	# 清理所有桥接
	for bridge_id in bridge_ids:
		bus.unbridge_signal(bridge_id)

桥接移除

gdscript
# 移除特定桥接
var success = bus.unbridge_signal(bridge_id)
if success:
	print("桥接移除成功")
else:
	print("桥接移除失败")

实际应用场景

1. UI 事件集中管理

gdscript
# UIManager.gd
extends Node

@onready var bus = SignalBus.get_singleton()
var ui_bridges = []

func _ready():
	setup_ui_bridges()
	
	# 订阅所有UI事件
	bus.subscribe("menu_opened", _on_menu_opened)
	bus.subscribe("settings_changed", _on_settings_changed)
	bus.subscribe("game_paused", _on_game_paused)

func setup_ui_bridges():
	# 主菜单按钮
	var start_bridge = bus.relay_node_signal($StartButton, "pressed", "game_started")
	var settings_bridge = bus.relay_node_signal($SettingsButton, "pressed", "settings_opened")
	var quit_bridge = bus.relay_node_signal($QuitButton, "pressed", "game_quit")
	
	# 设置界面按钮
	var apply_bridge = bus.relay_node_signal($ApplyButton, "pressed", "settings_applied")
	var cancel_bridge = bus.relay_node_signal($CancelButton, "pressed", "settings_cancelled")
	
	ui_bridges.append_array([start_bridge, settings_bridge, quit_bridge, apply_bridge, cancel_bridge])

func _on_menu_opened():
	print("菜单打开")
	show_main_menu()

func _on_settings_changed():
	print("设置已更改")
	apply_settings()

func _on_game_paused():
	print("游戏暂停")
	toggle_pause()

func _exit_tree():
	for bridge_id in ui_bridges:
		bus.unbridge_signal(bridge_id)

2. 游戏事件桥接

gdscript
# GameManager.gd
extends Node

@onready var bus = SignalBus.get_singleton()
var game_bridges = []

func _ready():
	setup_game_bridges()
	
	# 订阅游戏事件
	bus.subscribe("player_health_changed", _on_player_health_changed)
	bus.subscribe("enemy_defeated", _on_enemy_defeated)
	bus.subscribe("level_completed", _on_level_completed)

func setup_game_bridges():
	# 玩家事件
	var player_health_bridge = bus.relay_node_signal($Player, "health_changed", "player_health_changed")
	var player_died_bridge = bus.relay_node_signal($Player, "died", "player_died")
	
	# 敌人事件(假设敌人有 died 信号)
	var enemies = get_tree().get_nodes_in_group("enemies")
	for enemy in enemies:
		var enemy_bridge = bus.relay_node_signal(enemy, "died", "enemy_defeated")
		game_bridges.append(enemy_bridge)
	
	game_bridges.append_array([player_health_bridge, player_died_bridge])

func _on_player_health_changed(health):
	print("玩家生命值变化: ", health)
	update_health_ui(health)

func _on_enemy_defeated():
	print("敌人被击败")
	update_score()

func _on_level_completed():
	print("关卡完成")
	load_next_level()

func _exit_tree():
	for bridge_id in game_bridges:
		bus.unbridge_signal(bridge_id)

3. 系统事件桥接

gdscript
# SystemManager.gd
extends Node

@onready var bus = SignalBus.get_singleton()
var system_bridges = []

func _ready():
	setup_system_bridges()
	
	# 订阅系统事件
	bus.subscribe("scene_ready", _on_scene_ready)
	bus.subscribe("physics_tick", _on_physics_tick)

func setup_system_bridges():
	# 场景树事件
	var scene_ready_bridge = bus.relay_node_signal(get_tree(), "tree_changed", "scene_ready")
	
	# 物理帧事件
	var physics_bridge = bus.relay_node_signal(get_tree(), "physics_frame", "physics_tick")
	
	system_bridges.append_array([scene_ready_bridge, physics_bridge])

func _on_scene_ready():
	print("场景准备就绪")
	initialize_game()

func _on_physics_tick():
	# 处理物理相关的游戏逻辑
	update_physics()

func _exit_tree():
	for bridge_id in system_bridges:
		bus.unbridge_signal(bridge_id)

最佳实践

1. 桥接生命周期管理

gdscript
extends Node

@onready var bus = SignalBus.get_singleton()
var bridge_ids = []

func _ready():
	# 创建桥接
	var bridge_id = bus.bridge_signal($Button, "pressed")
	bridge_ids.append(bridge_id)

func _exit_tree():
	# 清理桥接
	for bridge_id in bridge_ids:
		bus.unbridge_signal(bridge_id)
	bridge_ids.clear()

2. 错误处理

gdscript
func safe_create_bridge(node, signal_name):
	if not node:
		push_error("节点为空")
		return -1
	
	if not node.has_signal(signal_name):
		push_error("节点不存在信号: " + signal_name)
		return -1
	
	var bridge_id = bus.bridge_signal(node, signal_name)
	if bridge_id == -1:
		push_error("桥接创建失败")
		return -1
	
	return bridge_id

3. 调试技巧

gdscript
func _ready():
	bus.set_debug_enabled(true)
	
	# 创建桥接时记录信息
	var bridge_id = bus.bridge_signal($Button, "pressed")
	print("创建桥接: Button.pressed -> ", bridge_id)

4. 命名约定

gdscript
# 使用一致的桥接命名
var ui_button_bridge = bus.bridge_signal($StartButton, "pressed")
var gameplay_event_bridge = bus.relay_node_signal($Player, "died", "player_death")

5. 性能考虑

gdscript
# 避免频繁创建和销毁桥接
# 对于临时节点,考虑直接使用信号而不是桥接

# 对于大量相似节点,使用批量处理
func setup_enemy_bridges(enemies):
	for enemy in enemies:
		var bridge_id = bus.relay_node_signal(enemy, "died", "enemy_defeated")
		bridge_ids.append(bridge_id)

桥接的优势

  1. 解耦: 将UI逻辑与游戏逻辑分离
  2. 集中管理: 所有事件通过SignalBus统一处理
  3. 灵活性: 可以动态添加和移除桥接
  4. 调试友好: 可以通过SignalBus的调试功能监控所有事件
  5. 扩展性: 易于添加新的事件处理逻辑

通过信号桥接系统,你可以构建高度解耦、易于维护的游戏架构,将不同层次的逻辑清晰地分离,同时保持系统的整体性和一致性。

基于 MIT 许可发布