godot-signal-architecture
Expert blueprint for signal-driven architecture using "Signal Up, Call Down" pattern for loose coupling. Covers typed signals, signal chains, one-shot connections, and AutoLoad event buses. Use when implementing event systems OR decoupling nodes. Keywords signal, emit, connect, CONNECT_ONE_SHOT, CONNECT_REFERENCE_COUNTED, event bus, AutoLoad, decoupling.
What this skill does
# Signal Architecture
Signal Up/Call Down pattern, typed signals, and event buses define decoupled, maintainable architectures.
## Available Scripts
### [signal_up_call_down_pattern.gd](scripts/signal_up_call_down_pattern.gd)
Clean implementation of decoupled hierarchy communication: children signal up, parents call down.
### [global_signal_bus_router.gd](scripts/global_signal_bus_router.gd)
Expert AutoLoad event bus for system-level event routing (Achievements, UI, Saving).
### [callable_bind_context.gd](scripts/callable_bind_context.gd)
Injecting extra static context into signal callbacks using `Callable.bind()`.
### [unbind_unwanted_args.gd](scripts/unbind_unwanted_args.gd)
Cleaning up function signatures by discarding unneeded signal arguments with `unbind()`.
### [await_signal_sequencing.gd](scripts/await_signal_sequencing.gd)
Replacing messy timers and state flags with linear, readable `await` signal sequences.
### [safe_dynamic_connections.gd](scripts/safe_dynamic_connections.gd)
Verifying connection state using `is_connected()` to prevent runtime multi-connection errors.
### [disconnect_ghost_signals.gd](scripts/disconnect_ghost_signals.gd)
Crucial memory management pattern for disconnecting signals when switching tracking targets.
### [one_shot_deferred_connections.gd](scripts/one_shot_deferred_connections.gd)
Using `CONNECT_ONE_SHOT` and `CONNECT_DEFERRED` for self-cleaning and physics-safe callbacks.
### [track_signal_emitter_source.gd](scripts/track_signal_emitter_source.gd)
Identifying which node fired a shared signal using `CONNECT_APPEND_SOURCE_OBJECT`.
### [complex_signal_sequencer.gd](scripts/complex_signal_sequencer.gd)
Managing multi-step asynchronous loading and transitions using sequential signal awaits.
## NEVER Do in Signal Architecture
-**NEVER use the legacy string-based `Object.connect()`** — Typos result in silent failures. Always use `signal.connect(_callback)` for compile-time validation [1].
- **NEVER use signals to dictate behavior top-down** — Signals are past-tense events (e.g., "died"). Use direct method calls for commands (e.g., "kill") [2].
- **NEVER connect a signal twice to the same Callable** — This throws an `ERR_INVALID_PARAMETER` at runtime unless using the `Object.CONNECT_REFERENCE_COUNTED` flag to stack connections [3, 4].
- **NEVER use a Global Signal Bus for local data** — Pollutes global state and makes debugging harder. Use local connections for scene-specific logic [4].
- **NEVER assume callbacks must accept all signal arguments** — Use `unbind()` to drop unwanted parameters and keep your API clean [5].
- **NEVER create circular signal dependencies** — A signals B, B signals back to A? Use a mediator (parent or AutoLoad) to break the loop [26].
- **NEVER skip signal typing** — `signal moved` without types lacks editor support. Always use `signal moved(dir: Vector2)` [27].
- **NEVER forget to disconnect dynamic signals** — Ghost connections cause "call on null instance" errors. Disconnect in `_exit_tree()` or use `bind_node()` [28].
- **NEVER emit signals with immediate side effects on the emitter** — If `died.emit()` calls `queue_free()`, listeners might fail to respond. Emit first [31].
- **NEVER use signals for high-frequency data streams** — Sending 1000+ signals/second (like per-particle updates) is inefficient. Use shared arrays or direct buffers.
---
**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
```gdscript
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
```gdscript
# 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
```gdscript
# 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:
```gdscript
# 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
```gdscript
# 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:
```gdscript
# 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
```gdscript
# 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
```gdscript
# ✅ 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
```gdscript
# ❌ 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
Godot automatically disconnects signals when a node or object is freed [6]. However, there is a **CRITICAL EXCEPTION**:
- **Capturing Lambdas**: If a lambda captures a local variable (e.g., `func(): print(x)`), Godot cannot automatically disconnect it. You MUST manually disconnect it in `_exit_tree()` or a cleanup method to prevent crashes [8, 9].
```gdscript
var my_lambda: Callable
func _ready() -> void:
var x = 10
my_lambda = func(): print(x) # Capturing lambda
player.died.connect(my_lambda)
func _exit_tree() -> void:
```
**Or use automatic cleanup:**
```gdscript
#Related in General
modeling-omnistudio-epc-catalog
IncludedSalesforce Industries CME EPC product-modeling skill for Product2-based catalog creation. Use when creating EPC products, configuring product attributes, building offer bundles with Product Child Items, or reviewing EPC DataPack JSON metadata for product catalog changes. TRIGGER when: user creates or updates Product2 EPC records, AttributeAssignment payloads, AttributeMetadata/AttributeDefaultValues, Offer bundles, or ProductChildItem relationships. DO NOT TRIGGER when: designing OmniScripts/FlexCards/Integration Procedures (use building-omnistudio-omniscript, building-omnistudio-flexcard, or building-omnistudio-integration-procedure), implementing Apex business logic (use generating-apex), or troubleshooting deployment pipelines (use deploying-metadata).
relationship-science-coach
IncludedUse this skill for direct, practical adult relationship coaching: couples conflict, repair, trust, marriage, dating, flirting, attachment patterns, emotional connection, sex, desire differences, eroticism, kink negotiation, affection, love languages, breakups, and long-term passion. Draw on Gottman, EFT and Hold Me Tight, attachment science, modern sex research, Perel, Nagoski, Kerner, Schnarch, Love and Stosny, and flexible love-language tools. Be concrete and low-hedge. Redirect only for imminent danger, abuse, coercive control, minors, non-consent, self-harm, stalking, or medical/legal/psychiatric decisions.
building-sf-integrations
IncludedSalesforce integration architecture and runtime plumbing with 120-point scoring. Use this skill to set up Named Credentials, External Credentials, External Services, REST/SOAP callout patterns, Platform Events, and Change Data Capture. TRIGGER when: user sets up Named Credentials, External Services, REST/SOAP callouts, Platform Events, CDC, or touches .namedCredential-meta.xml files. DO NOT TRIGGER when: Connected App/OAuth config (use configuring-connected-apps), Apex-only logic (use generating-apex), or data import/export (use handling-sf-data).
venue-templates
IncludedAccess comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
let-fate-decide
IncludedDraws the 12 Houses of the Zodiac Tarot spread to inject entropy into planning when prompts are vague, ambiguous, or casually delegated. Interprets the spread to guide next steps. Use when the user says 'let fate decide', 'YOLO', 'whatever', 'idk', or other nonchalant phrases, makes Yu-Gi-Oh references, or when you are about to arbitrarily pick between multiple reasonable approaches. Prefer over ask-questions-if-underspecified when the user's tone is casual or playful rather than precision-seeking.
net-ops
IncludedCross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.