godot-signal-architecture

安装量: 65
排名: #11612

安装

npx skills add https://github.com/thedivergentai/gd-agentic-skills --skill godot-signal-architecture
Signal Architecture
Signal Up/Call Down pattern, typed signals, and event buses define decoupled, maintainable architectures.
Available Scripts
global_event_bus.gd
Expert AutoLoad event bus with typed signals and connection management.
signal_debugger.gd
Runtime signal connection analyzer. Shows all connections in scene hierarchy.
signal_spy.gd
Testing utility for observing signal emissions with count tracking and history.
MANDATORY - For Event Bus
Read global_event_bus.gd before implementing cross-scene communication. NEVER Do in Signal Architecture NEVER create circular signal dependencies — A signals to B, B signals back to A? Infinite loops + stack overflow. Use mediator (parent OR AutoLoad) to break cycle. NEVER skip signal typing — signal moved without types? No autocomplete OR type safety. Use signal moved(direction: Vector2) for editor support. NEVER forget to disconnect signals — Node freed but signal still connected? "Attempt to call on null instance" error. Disconnect in _exit_tree() OR use CONNECT_REFERENCE_COUNTED . NEVER connect signals in _ready() for dynamic nodes — Enemy spawned after level load? Signals not connected. Connect when instantiating OR use groups + await pattern. NEVER use signals for parent→child — Parent signaling to child breaks encapsulation. CALL DOWN directly: child.method() . Reserve signals for child→parent communication. NEVER emit signals with side effects — died.emit() calls queue_free() inside? Listeners can't respond before node freed. Emit FIRST, then cleanup. NEVER use string-based signal names — connect("heath_chnaged", ...) typo = silent failure. Use direct reference: player.health_changed.connect(...) . Use Signals For: UI button presses → game logic Player death → game over screen Item collected → inventory update Enemy killed → score update Cross-scene communication via AutoLoad Use Direct Calls For: Parent controlling child behavior Accessing child properties Simple, local interactions Implementation Patterns Pattern 1: Define Typed Signals extends CharacterBody2D

✅ Good - typed signals (Godot 4.x)

signal health_changed ( new_health : int , max_health : int ) signal died ( ) signal item_collected ( item_name : String , item_type : int )

❌ Bad - untyped signals

signal health_changed signal died Pattern 2: Emit Signals on State Changes

player.gd

extends CharacterBody2D signal health_changed ( current : int , maximum : int ) signal died ( ) var health : int = 100 : set ( value ) : health = clamp ( value , 0 , max_health ) health_changed . emit ( health , max_health ) if health <= 0 : died . emit ( ) var max_health : int = 100 func take_damage ( amount : int ) -> void : health -= amount

Triggers setter, which emits signal

Pattern 3: Connect Signals in Parent

game.gd (parent)

extends Node2D @ onready var player : CharacterBody2D = $Player @ onready var ui : Control = $UI func _ready ( ) -> void :

Connect child signals

player . health_changed . connect ( _on_player_health_changed ) player . died . connect ( _on_player_died ) func _on_player_health_changed ( current : int , maximum : int ) -> void :

Call down to UI

ui . update_health_bar ( current , maximum ) func _on_player_died ( ) -> void :

Orchestrate game over

ui . show_game_over ( ) get_tree ( ) . paused = true Pattern 4: Global Signals via AutoLoad For cross-scene communication:

events.gd (AutoLoad)

extends Node signal level_completed ( level_number : int ) signal player_spawned ( player : Node2D ) signal boss_defeated ( boss_name : String )

Any script can emit:

Events . level_completed . emit ( 3 )

Any script can listen:

Events . level_completed . connect ( _on_level_completed ) Advanced Patterns Pattern 5: Signal Chains

enemy.gd

signal died ( score_value : int ) func _on_health_depleted ( ) -> void : died . emit ( 100 ) queue_free ( )

combat_manager.gd

func _ready ( ) -> void : for enemy in get_tree ( ) . get_nodes_in_group ( "enemies" ) : enemy . died . connect ( _on_enemy_died ) func _on_enemy_died ( score_value : int ) -> void : GameManager . add_score ( score_value ) Events . enemy_killed . emit ( ) Pattern 6: One-Shot Connections For single-use signal connections:

Connect with CONNECT_ONE_SHOT flag

timer . timeout . connect ( _on_timer_timeout , CONNECT_ONE_SHOT ) func _on_timer_timeout ( ) -> void : print ( "This only fires once" )

Connection automatically removed

Pattern 7: Custom Signal Arguments

item.gd

signal picked_up ( item_data : Dictionary ) func _on_player_enter ( ) -> void : picked_up . emit ( { "name" : item_name , "type" : item_type , "value" : item_value , "icon" : item_icon } )

inventory.gd

func _on_item_picked_up ( item_data : Dictionary ) -> void : add_item ( item_data . name , item_data . type , item_data . value ) Best Practices 1. Descriptive Signal Names

✅ Good

signal button_pressed ( ) signal enemy_defeated ( enemy_type : String ) signal animation_finished ( animation_name : String )

❌ Bad

signal pressed ( ) signal done ( ) signal finished ( ) 2. Avoid Circular Dependencies

❌ BAD: A signals to B, B signals back to A

A.gd

signal data_requested func _ready ( ) : B . data_ready . connect ( _on_data_ready ) data_requested . emit ( )

B.gd

signal data_ready func _ready ( ) : A . data_requested . connect ( _on_data_requested )

✅ GOOD: Use a mediator (parent or AutoLoad)

Parent.gd

func _ready ( ) : A . data_requested . connect ( _on_A_data_requested ) B . data_ready . connect ( _on_B_data_ready ) 3. Disconnect Signals When Nodes Are Freed func _ready ( ) -> void : player . died . connect ( _on_player_died ) func _exit_tree ( ) -> void : if player and player . died . is_connected ( _on_player_died ) : player . died . disconnect ( _on_player_died ) Or use automatic cleanup:

Signal auto-disconnects when this node is freed

player . died . connect ( _on_player_died , CONNECT_REFERENCE_COUNTED ) 4. Group Related Signals

✅ Good organization

Combat signals

signal health_changed ( current : int , max : int ) signal died ( ) signal respawned ( )

Movement signals

signal jumped ( ) signal landed ( ) signal direction_changed ( direction : Vector2 )

Inventory signals

signal
item_added
(
item
:
Dictionary
)
signal
item_removed
(
item
:
Dictionary
)
signal
inventory_full
(
)
Testing Signals
func
test_health_signal
(
)
->
void
:
var
signal_emitted
:=
false
var
received_health
:=
0
player
.
health_changed
.
connect
(
func
(
current
:
int
,
_max
:
int
)
:
signal_emitted
=
true
received_health
=
current
)
player
.
health
=
50
assert
(
signal_emitted
,
"Signal was not emitted"
)
assert
(
received_health
==
50
,
"Health value incorrect"
)
Common Gotchas
Issue
Signal not firing
Check
Is the signal spelled correctly when connecting?
Check
Is the emitting code path actually being executed?
Check
Use
print()
before
emit()
to verify
Issue
Signal firing multiple times
Cause
Multiple connections to the same signal
Solution
Check connections or use
CONNECT_ONE_SHOT
Issue
"Attempt to call function on a null instance"
Cause
Node was freed but signal still connected
Solution
Disconnect in _exit_tree() or use CONNECT_REFERENCE_COUNTED Reference Godot Docs: Signals Best Practices: Signals Up, Calls Down Related Master Skill: godot-master
返回排行榜