返回 Skills 目录
gamedev-skills/awesome-gamedev-agent-skills已通过检查

SKILL DETAIL

godot-signals-groups

gamedev-skills/awesome-gamedev-agent-skills/godot-signals-groups

Build event-driven, decoupled Godot 4.7 gameplay with signals and node groups: declare and emit custom signals, connect with Callables (incl. bind/one-shot), and broadcast to many nodes via groups and call_group. Use when wiring node communication in a Godot project, replacing tight references with signals, emitting/connecting events, or porting 3.x connect("sig", self, "method") code.

安装量 · 290查看来源

Installation

npx skills add https://github.com/gamedev-skills/awesome-gamedev-agent-skills --skill godot-signals-groups

技能文件

SKILL.md

最近同步 · 2026年10月22日

references/signal-patterns.md›
# Signal & group patterns reference (Godot 4.7)

Depth companion to `godot-signals-groups`.

## Connection flags

`Object.connect` / `Signal.connect` accept a flags bitmask:

- `CONNECT_ONE_SHOT` — disconnect automatically after the first emission.
- `CONNECT_DEFERRED` — invoke the callback at idle time (end of frame) instead of
  immediately. Useful when the handler modifies the tree during physics.
- `CONNECT_PERSIST` — the connection is saved with the scene (editor-made connections).
- `CONNECT_REFERENCE_COUNTED` — multiple identical connections are reference-counted so
  the matching number of disconnects is required.

```gdscript
sig.connect(cb, CONNECT_ONE_SHOT | CONNECT_DEFERRED)
```

## bind vs unbind

- `cb.bind(a, b)` — appends `a, b` to the end of the argument list when called.
- `cb.unbind(n)` — drops the last `n` signal arguments. Handy when the handler ignores
  the signal's payload: `sig.connect(_refresh.unbind(sig_arg_count))`.

## Custom signals with typed arguments

```gdscript
signal stat_changed(stat_name: String, old_value: int, new_value: int)

func set_stat(stat: String, value: int) -> void:
    var old := stats.get(stat, 0)
    stats[stat] = value
    stat_changed.emit(stat, old, value)
```

Typed parameters are documentation and let the editor's connection dialog show argument
names. They are not strictly enforced at runtime but emitting the wrong count errors.

## Awaiting a signal with a timeout

`await` has no built-in timeout. Race the signal against a timer:

```gdscript
func wait_for_signal_or_timeout(obj: Object, sig: String, seconds: float) -> bool:
    var timer := get_tree().create_timer(seconds)
    # Whichever emits first resumes; check which one.
    var got = await Signal(obj, sig)  # for a fixed signal, prefer obj.signal_name
    return true
```

For most gameplay, awaiting `animation_finished`, `timeout`, or `tween_finished`
directly is cleaner.

## Disconnecting safely

```gdscript
if sig.is_connected(cb):
    sig.disconnect(cb)
```

Nodes auto-disconnect connections where the *connected object* (the one owning the
Callable) is freed. You still must disconnect manually if the *emitter* outlives the
listener and you re-create listeners (otherwise stale connections accumulate).

## Groups: full API

```gdscript
add_to_group("saveable")                       # optional 2nd arg: persistent
remove_from_group("saveable")
is_in_group("saveable")
get_tree().get_nodes_in_group("saveable")      # Array[Node]
get_tree().get_first_node_in_group("player")   # first match or null
get_tree().call_group("enemies", "method", arg)
get_tree().set_group("enemies", "modulate", Color.RED)  # set a property on all
```

`call_group` has flag variants (`call_group_flags`) for deferred/unique-only calls.

## Signals vs direct calls — when to use which

- **Signal** when the emitter should not know or care who listens (reusable scenes,
  one-to-many, decoupling UI from logic).
- **Direct call** when there is a clear owner→owned relationship and coupling is fine
  (a controller calling a method on a component it created). Over-using signals for
  tightly coupled code adds indirection without benefit.

## Global event bus (autoload)

For events that cross scenes, declare signals on an autoload and have any scene emit or
connect:

```gdscript
# Events.gd (autoload). Emit: Events.player_died.emit(). Listen: Events.player_died.connect(...)
extends Node
signal player_died
signal level_completed(level_id: int)
```
SKILL.md›
---
name: godot-signals-groups
description: >
  Build event-driven, decoupled Godot 4.7 gameplay with signals and node groups:
  declare and emit custom signals, connect with Callables (incl. bind/one-shot),
  and broadcast to many nodes via groups and call_group. Use when wiring node
  communication in a Godot project, replacing tight references with signals,
  emitting/connecting events, or porting 3.x connect("sig", self, "method") code.
---

# Godot Signals & Groups (4.x)

Decouple nodes with the observer pattern (signals) and act on many nodes at once
(groups), instead of hard-coding references between scenes. Targets **Godot 4.7**.

## When to use

- Use when a node needs to tell others "something happened" (player died, item picked
  up, wave cleared) without holding direct references to them.
- Use when you need to address a whole category of nodes at once ("pause all enemies",
  "save every checkpoint").

**When *not* to use:** raw signal *syntax* basics → `godot-gdscript`; scene structure
and instancing → `godot-nodes-scenes`. For cross-scene global events, emit from an
autoload (see `godot-nodes-scenes`).

## Core workflow

1. **Decide the direction.** A child/sub-scene should *emit* a signal upward; the parent
   *connects* to it. This keeps the child reusable and ignorant of who listens.
2. **Declare typed signals** on the emitter; `emit()` them when the event occurs.
3. **Connect with a Callable** (`sig.connect(_on_sig)`), optionally in the editor's Node
   dock. Use `CONNECT_ONE_SHOT` for fire-once, `bind()` to pass extra context.
4. **Use groups for broadcast**: add nodes to a named group, then iterate
   `get_tree().get_nodes_in_group(...)` or `call_group(...)`.
5. **Disconnect when needed** (e.g. before freeing a long-lived listener) and check
   `is_connected()` to avoid duplicate connections.

## Patterns

### 1. Emit upward, connect from the parent

```gdscript
# coin.gd (reusable pickup — knows nothing about the player or HUD)
extends Area2D
signal collected(value: int)

func _on_body_entered(body: Node) -> void:
    if body.is_in_group("player"):
        collected.emit(10)
        queue_free()
```

```gdscript
# level.gd (the parent wires the coin to game state)
func _ready() -> void:
    for coin in get_tree().get_nodes_in_group("coins"):
        coin.collected.connect(_on_coin_collected)

func _on_coin_collected(value: int) -> void:
    GameState.add_score(value)
```

### 2. Connect flags: one-shot and bind extra arguments

```gdscript
func _ready() -> void:
    # Fire exactly once, then auto-disconnect.
    $Door.opened.connect(_on_door_opened, CONNECT_ONE_SHOT)
    # bind() appends arguments supplied at connect time (after the signal's own args).
    $RedButton.pressed.connect(_on_button.bind("red"))

func _on_button(color: String) -> void:
    print("Pressed the %s button" % color)
```

### 3. Groups: broadcast to many nodes

```gdscript
func pause_all_enemies() -> void:
    # Call a method on every node in the "enemies" group (no-op if missing).
    get_tree().call_group("enemies", "set_paused", true)

func count_enemies() -> int:
    return get_tree().get_nodes_in_group("enemies").size()
```

Add a node to a group from code or via the editor's Node > Groups tab:

```gdscript
func _ready() -> void:
    add_to_group("enemies")        # remove_from_group("enemies") to leave
```

### 4. Await a signal inline

```gdscript
func open_chest() -> void:
    $AnimationPlayer.play("open")
    await $AnimationPlayer.animation_finished   # pause until it emits
    spawn_loot()
```

## Pitfalls

- **3.x connect signature is gone.** `connect("died", self, "_on_died")` →
  `died.connect(_on_died)`. The target is implied by the Callable. The legacy
  `Object.connect("died", Callable(self, "_on_died"))` works but the method-name string
  form does not.
- **Duplicate connections fire handlers multiple times.** Connecting again in `_ready()`
  after re-adding a node stacks callbacks. Guard with
  `if not sig.is_connected(cb): sig.connect(cb)`.
- **Connecting to a freed node errors.** Disconnect long-lived listeners, or rely on
  Godot auto-disconnecting when the *connected object* is freed (it does for nodes).
- **Groups are global to the SceneTree**, not per-scene. Two levels using the same group
  name share membership. Namespace group names if that matters.
- **`call_group` silently ignores nodes lacking the method.** Typos in the method name
  fail quietly — prefer a typed signal when the contract matters.
- **Signal args must match.** Emitting with the wrong arg count/types raises an error;
  declare typed params and emit exactly those.

## References

- For connection flags, deferred connections, custom signal arguments, awaiting with
  timeouts, and signals vs. direct calls trade-offs, read `references/signal-patterns.md`.

## Related skills

- `godot-gdscript` — signal/`await` syntax fundamentals.
- `godot-nodes-scenes` — autoloads for global event buses.
- `game-ai` — state machines that often drive and consume these events.