Skip to content

Signal Bridging

Overview

Signal bridging functionality allows connecting Godot node's native signals to the global signal bus, enabling centralized event management. This is very useful for decoupling UI logic, game logic, and system logic.

Basic Bridging Example

Complete Example Code

gdscript
@warning_ignore_start("static_called_on_instance")
extends Node

# Native signal bridging example
# Demonstrates how to bridge Godot node's native signals to SignalBus
@onready var bus = SignalBus.get_singleton()
var bridge_ids = []

func _ready():
	bus.set_debug_enabled(false)
	print("\n=== Native Signal Bridging Example ===\n")
	bus.bridge_signal($Button, "pressed")
	bus.relay_node_signal($Button2, "pressed", "my_pressed")
	
	bus.subscribe("pressed", func():
		print("Button was clicked! (via SignalBus)")
	)
	bus.subscribe("my_pressed", func():
		print("Renamed bridge button was clicked! (via SignalBus)")
	)

Bridging Methods

1. bridge_signal()

Directly bridge node signals to SignalBus, keeping the original signal name.

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

Parameters:

  • node: Node object
  • signal_name: Node signal name

Return Value:

  • int64_t: Bridge ID for subsequent management

Example:

gdscript
# Bridge button's pressed signal to SignalBus
bus.bridge_signal($Button, "pressed")

# Subscribe to bridged signal
bus.subscribe("pressed", func():
	print("Button was clicked! (via SignalBus)")
)

2. relay_node_signal()

Forward node signals to different SignalBus signal names.

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

Parameters:

  • node: Node object
  • node_signal: Node signal name
  • bus_signal: SignalBus signal name

Return Value:

  • int64_t: Bridge ID

Example:

gdscript
# Forward button's pressed signal to my_pressed signal
bus.relay_node_signal($Button2, "pressed", "my_pressed")

# Subscribe to forwarded signal
bus.subscribe("my_pressed", func():
	print("Renamed bridge button was clicked! (via SignalBus)")
)

Bridge Management

Bridge ID Storage

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():
	# Clean up all bridges
	for bridge_id in bridge_ids:
		bus.unbridge_signal(bridge_id)

Bridge Removal

gdscript
# Remove specific bridge
var success = bus.unbridge_signal(bridge_id)
if success:
	print("Bridge removed successfully")
else:
	print("Bridge removal failed")

Practical Application Scenarios

1. Centralized UI Event Management

gdscript
# UIManager.gd
extends Node

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

func _ready():
	setup_ui_bridges()
	
	# Subscribe to all UI events
	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():
	# Main menu buttons
	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")
	
	# Settings interface buttons
	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("Menu opened")
	show_main_menu()

func _on_settings_changed():
	print("Settings changed")
	apply_settings()

func _on_game_paused():
	print("Game paused")
	toggle_pause()

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

2. Game Event Bridging

gdscript
# GameManager.gd
extends Node

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

func _ready():
	setup_game_bridges()
	
	# Subscribe to game events
	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():
	# Player events
	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")
	
	# Enemy events (assuming enemies have died signal)
	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("Player health changed: ", health)
	update_health_ui(health)

func _on_enemy_defeated():
	print("Enemy was defeated")
	update_score()

func _on_level_completed():
	print("Level completed")
	load_next_level()

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

3. System Event Bridging

gdscript
# SystemManager.gd
extends Node

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

func _ready():
	setup_system_bridges()
	
	# Subscribe to system events
	bus.subscribe("scene_ready", _on_scene_ready)
	bus.subscribe("physics_tick", _on_physics_tick)

func setup_system_bridges():
	# Scene tree events
	var scene_ready_bridge = bus.relay_node_signal(get_tree(), "tree_changed", "scene_ready")
	
	# Physics frame events
	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("Scene ready")
	initialize_game()

func _on_physics_tick():
	# Handle physics-related game logic
	update_physics()

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

Best Practices

1. Bridge Lifecycle Management

gdscript
extends Node

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

func _ready():
	# Create bridges
	var bridge_id = bus.bridge_signal($Button, "pressed")
	bridge_ids.append(bridge_id)

func _exit_tree():
	# Clean up bridges
	for bridge_id in bridge_ids:
		bus.unbridge_signal(bridge_id)
	bridge_ids.clear()

2. Error Handling

gdscript
func safe_create_bridge(node, signal_name):
	if not node:
		push_error("Node is null")
		return -1
	
	if not node.has_signal(signal_name):
		push_error("Node doesn't have signal: " + signal_name)
		return -1
	
	var bridge_id = bus.bridge_signal(node, signal_name)
	if bridge_id == -1:
		push_error("Bridge creation failed")
		return -1
	
	return bridge_id

3. Debugging Tips

gdscript
func _ready():
	bus.set_debug_enabled(true)
	
	# Log information when creating bridges
	var bridge_id = bus.bridge_signal($Button, "pressed")
	print("Created bridge: Button.pressed -> ", bridge_id)

4. Naming Conventions

gdscript
# Use consistent bridge naming
var ui_button_bridge = bus.bridge_signal($StartButton, "pressed")
var gameplay_event_bridge = bus.relay_node_signal($Player, "died", "player_death")

5. Performance Considerations

gdscript
# Avoid frequent creation and destruction of bridges
# For temporary nodes, consider using signals directly instead of bridging

# For large numbers of similar nodes, use batch processing
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)

Advantages of Bridging

  1. Decoupling: Separate UI logic from game logic
  2. Centralized Management: All events handled uniformly through SignalBus
  3. Flexibility: Can dynamically add and remove bridges
  4. Debug Friendly: Can monitor all events through SignalBus's debug functionality
  5. Scalability: Easy to add new event handling logic

Through the signal bridging system, you can build highly decoupled, easily maintainable game architecture, clearly separating different levels of logic while maintaining system integrity and consistency.

Released under the MIT License