Use when Godot code has tight coupling via get_node(), get_parent(), or direct references creating brittle dependencies. Detects coupling patterns and transforms to signal-based communication. Components become independent, testable, and reusable. Preserves exact behavior while improving architecture.
Scanned 2/12/2026
Install via CLI
openskills install majiayu000/claude-skill-registry---
name: godot-add-signals
version: 3.0.0
displayName: Replace Coupling with Signals
description: >
Use when Godot code has tight coupling via get_node(), get_parent(), or direct
references creating brittle dependencies. Detects coupling patterns and transforms
to signal-based communication. Components become independent, testable, and reusable.
Preserves exact behavior while improving architecture.
author: Asreonn
license: MIT
category: game-development
type: tool
difficulty: intermediate
audience: [developers]
keywords:
- godot
- signals
- decoupling
- get-node
- get-parent
- architecture
- gdscript
- loose-coupling
platforms: [macos, linux, windows]
repository: https://github.com/asreonn/godot-superpowers
homepage: https://github.com/asreonn/godot-superpowers#readme
permissions:
filesystem:
read: [".gd", ".tscn"]
write: [".gd", ".tscn"]
git: true
behavior:
auto_rollback: true
validation: true
git_commits: true
outputs: "Signal definitions, signal connections, decoupled components, git commits"
requirements: "Git repository, Godot 4.x"
execution: "Fully automatic with behavior preservation"
integration: "Part of godot-refactor orchestrator, works with godot-split-scripts"
---
# Replace Coupling with Signals
## Core Principle
**Components communicate via signals, not direct references.** Coupling makes code brittle and hard to test.
## What This Skill Does
Finds patterns like:
```gdscript
# player.gd
func take_damage(amount):
health -= amount
get_node("../UI/HealthBar").update(health) # TIGHT COUPLING
get_parent().get_node("ScoreManager").reduce_score() # FRAGILE PATH
```
Transforms to:
```gdscript
# player.gd
signal health_changed(new_health)
func take_damage(amount):
health -= amount
health_changed.emit(health) # SIGNAL - NO COUPLING
```
```gdscript
# main.gd (or scene setup)
func _ready():
player.health_changed.connect(ui.health_bar.update)
player.health_changed.connect(score_manager.on_player_damaged)
```
## Detection Patterns
Identifies:
- `get_node("../path")` - Upward path navigation
- `get_parent().something` - Parent dependencies
- `find_child("name")` - Runtime searches
- `has_method()` - Tight interface coupling
- Direct owner references creating circular dependencies
## When to Use
### You're Refactoring Legacy Code
Old code has spaghetti dependencies via get_node chains.
### You're Building Testable Systems
Want to test components in isolation without full scene tree.
### You're Creating Reusable Components
Components should work in different contexts without modification.
### You're Experiencing Brittle Code
Changes to scene structure break unrelated functionality.
## Process
1. **Scan** - Find all get_node(), get_parent(), find_child() usage
2. **Analyze** - Identify what information flows between nodes
3. **Define Signals** - Create signal definitions for communication
4. **Replace** - Convert direct calls to signal emissions
5. **Connect** - Wire signals in appropriate orchestration points
6. **Validate** - Ensure behavior preserved exactly
7. **Commit** - Git commit per coupling removal
## Example Transformation
**Before (Tight Coupling):**
```gdscript
# enemy.gd
extends CharacterBody2D
func die():
var player = get_node("../Player") # FRAGILE PATH
player.add_score(100)
var audio = get_parent().get_node("AudioManager") # TIGHT COUPLING
audio.play_sound("enemy_death")
queue_free()
```
**After (Signal-Based):**
```gdscript
# enemy.gd
extends CharacterBody2D
signal died(score_value: int)
signal death_sound_requested(sound_name: String)
func die():
died.emit(100)
death_sound_requested.emit("enemy_death")
queue_free()
```
```gdscript
# main.gd (orchestrator)
func _ready():
for enemy in enemies:
enemy.died.connect(player.add_score)
enemy.death_sound_requested.connect(audio_manager.play_sound)
```
## Signal Patterns
### One-to-Many
One emitter, multiple listeners (health changed → update UI + save game + achievement check).
### Event Broadcasting
Announce something happened without knowing who cares.
### Request-Response
Request service without knowing provider (request_ammo → whoever handles ammo).
### State Change Notification
Notify when state changes (entered_water, jumped, landed).
## What Gets Created
- Signal definitions with typed parameters
- Signal emission at appropriate points
- Signal connections in orchestrator scripts
- Documentation of signal purpose and parameters
- Git commits documenting each decoupling
## Smart Analysis
**Identifies coupling types:**
- **Structural coupling** - Depends on scene tree structure
- **Interface coupling** - Calls specific methods on other nodes
- **Data coupling** - Accesses data from other nodes
**Chooses appropriate solution:**
- Signals for events and notifications
- Dependency injection for services
- Scene composition for reusable components
## Integration
Works with:
- **godot-split-scripts** - Split first, then decouple
- **godot-extract-to-scenes** - Extract scenes, then add signals
- **godot-refactor** (orchestrator) - Runs as part of full refactoring
## Safety
- Behavior preserved exactly
- Signal connections tested automatically
- Rollback on validation failure
- Original coupling preserved in git history
## When NOT to Use
Don't add signals if:
- Parent-child relationship is intentional design
- Component genuinely needs specific parent type
- Coupling is within same responsibility boundary
- Adding signals makes code more complex, not simpler
Examples of valid coupling:
- UI button calling parent dialog's close method
- Child node accessing parent's exported properties
- Component accessing owner's public API
## Signal Naming Conventions
**Events (past tense):**
- `health_changed`
- `item_collected`
- `enemy_died`
**Requests (imperative):**
- `damage_requested`
- `play_sound`
- `spawn_particle`
**State Changes:**
- `entered_state`
- `exited_state`
- `state_changed`
## Benefits
- **Testability** - Test components without full scene tree
- **Reusability** - Components work in different contexts
- **Flexibility** - Add/remove listeners without changing emitter
- **Maintainability** - Changes don't break distant code
- **Clarity** - Signal connections show system architecture
## Common Transformations
| Before (Coupled) | After (Signal-Based) |
|------------------|---------------------|
| `get_node("../Player").damage()` | `damage_dealt.emit()` → player listens |
| `get_parent().update_ui()` | `ui_update_requested.emit()` → UI listens |
| `owner.score += 10` | `score_changed.emit(10)` → owner listens |
| `find_child("Camera").shake()` | `screen_shake_requested.emit()` → camera listens |
No comments yet. Be the first to comment!