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 objectsignal_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 objectnode_signal: Node signal namebus_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_id3. 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
- Decoupling: Separate UI logic from game logic
- Centralized Management: All events handled uniformly through SignalBus
- Flexibility: Can dynamically add and remove bridges
- Debug Friendly: Can monitor all events through SignalBus's debug functionality
- 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.