summer-engine 1.3.1 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +67 -0
- package/package.json +5 -3
- package/skills/_meta/skill-test/SKILL.md +1 -1
- package/skills/_shared/summer-folder.md +44 -0
- package/skills/_tests/specs/art-direction.md +115 -0
- package/skills/_tests/specs/audio-direction.md +123 -0
- package/skills/_tests/specs/brainstorm-game.md +99 -0
- package/skills/_tests/specs/design-level.md +113 -0
- package/skills/_tests/specs/design-mechanic.md +112 -0
- package/skills/_tests/specs/design-npc.md +112 -0
- package/skills/_tests/specs/export-and-ship.md +114 -0
- package/skills/_tests/specs/fps-controller.md +5 -5
- package/skills/_tests/specs/setup-multiplayer.md +106 -0
- package/skills/_tests/specs/tune-performance.md +104 -0
- package/skills/_tests/specs/vfx.md +98 -0
- package/skills/ai-and-npcs/design-npc/SKILL.md +343 -0
- package/skills/audio/audio-direction/SKILL.md +258 -0
- package/skills/deployment/export-and-ship/SKILL.md +300 -0
- package/skills/gameplay-mechanics/design-mechanic/SKILL.md +265 -0
- package/skills/level-design/design-level/SKILL.md +266 -0
- package/skills/multiplayer-and-networking/setup-multiplayer/SKILL.md +328 -0
- package/skills/performance/tune-performance/SKILL.md +219 -0
- package/skills/rendering-and-lighting/art-direction/SKILL.md +284 -0
- package/skills/scene-and-project/brainstorm-game/SKILL.md +210 -0
- package/skills/visual-effects/vfx/SKILL.md +280 -0
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: design-npc
|
|
3
|
+
description: Use when the user wants to design an enemy, NPC, boss, companion, civilian, or wave-spawned mob's behavior. Walks perception, decision logic, escalation states, animation/audio tells, and defeat handling — outputs a state-machine GDScript stub plus the recommended node tree. Trigger on "enemy", "NPC", "boss", "companion", "AI", "behavior", "mob", "perception", "design enemy".
|
|
4
|
+
license: MIT
|
|
5
|
+
compatibility: [Cursor, Claude Code, Windsurf, Codex]
|
|
6
|
+
category: ai-and-npcs
|
|
7
|
+
user-invocable: true
|
|
8
|
+
allowed-tools: Read Grep Edit Write summer_get_scene_tree summer_inspect_node summer_add_node summer_set_prop summer_set_resource_property summer_connect_signal summer_save_scene summer_get_script_errors summer_input_map_bind
|
|
9
|
+
paths: ["**/*.gd", "**/*.tscn"]
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# /design-npc — Design an NPC's Behavior End-to-End
|
|
13
|
+
|
|
14
|
+
## Overview
|
|
15
|
+
|
|
16
|
+
A good NPC is legible: the player can read its mood, predict its next move, and feel the consequence of theirs. This skill walks the design from the inside out — what does the NPC perceive, how does it decide, what tells signal each state, and how does it die or leave the scene. Output: a state-machine GDScript stub plus the scene tree.
|
|
17
|
+
|
|
18
|
+
**Core principle:** behavior is what the player *sees*, not what the code does. Every state needs a tell.
|
|
19
|
+
|
|
20
|
+
## Steps
|
|
21
|
+
|
|
22
|
+
### 1. Pick the NPC archetype
|
|
23
|
+
|
|
24
|
+
Open with the question. Wait.
|
|
25
|
+
|
|
26
|
+
> What's the NPC for? Pick the closest:
|
|
27
|
+
> - **Basic enemy** (melee/ranged grunt, dies in 1–3 hits)
|
|
28
|
+
> - **Boss** (named, scripted phases, 30–120 sec fight)
|
|
29
|
+
> - **Quest-giver** (talks, gives objectives, doesn't fight)
|
|
30
|
+
> - **Friendly companion** (follows player, helps in combat)
|
|
31
|
+
> - **Civilian / ambient** (reacts to events, no combat AI)
|
|
32
|
+
> - **Wave-spawned mob** (cheap, many at once, simple behavior)
|
|
33
|
+
|
|
34
|
+
Each archetype maps to a different complexity budget:
|
|
35
|
+
|
|
36
|
+
| Archetype | States | Perception | Decision logic | Tells per state |
|
|
37
|
+
|---|---|---|---|---|
|
|
38
|
+
| Basic enemy | 4 (idle / alert / attack / dead) | Sight cone | State machine | Animation + 1 sound |
|
|
39
|
+
| Boss | 6–10 (per phase) | Sight + hearing + scripted | Behavior tree or hand-coded FSM | Full anim suite + VO |
|
|
40
|
+
| Quest-giver | 2 (idle / talking) | Trigger area | None / dialogue tree | Idle anim + greet |
|
|
41
|
+
| Companion | 5 (follow / combat / wait / heal / dead) | Sight + group awareness | State machine + utility scoring | Anim + voice |
|
|
42
|
+
| Civilian | 3 (wander / panic / dead) | Hearing (loud events) | Simple FSM | Anim + scream |
|
|
43
|
+
| Wave mob | 2 (charge / dead) | Direct line to player | None | Anim + impact |
|
|
44
|
+
|
|
45
|
+
### 2. Sketch the four design pillars
|
|
46
|
+
|
|
47
|
+
Walk them in order. Don't skip.
|
|
48
|
+
|
|
49
|
+
#### a) Perception — what does this NPC *sense*?
|
|
50
|
+
|
|
51
|
+
Pick the minimum. Over-perception = unfair AI. Common sensors:
|
|
52
|
+
|
|
53
|
+
| Sensor | Node | Cost | Use case |
|
|
54
|
+
|---|---|---|---|
|
|
55
|
+
| Sight cone | RayCast3D + dot product check | Cheap | Standard enemy |
|
|
56
|
+
| Sphere proximity | Area3D with sphere shape | Cheap | "Felt your presence" / aggro range |
|
|
57
|
+
| Hearing event | Signal from world events (gunshot, footstep) | Cheapest (event-driven) | Alert nearby NPCs |
|
|
58
|
+
| Group awareness | Shared signal bus (autoload) | Cheap | Pack tactics |
|
|
59
|
+
| Scripted scripted (boss) | Manual state changes | Free | Phase transitions |
|
|
60
|
+
|
|
61
|
+
Recommended setup for a basic enemy:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
Enemy (CharacterBody3D)
|
|
65
|
+
├── MeshInstance3D
|
|
66
|
+
├── CollisionShape3D
|
|
67
|
+
├── Vision (Area3D) # sphere shape, radius = sight range
|
|
68
|
+
│ └── CollisionShape3D
|
|
69
|
+
└── Sight (RayCast3D) # confirms line-of-sight when target in Vision area
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The two-step (Area3D for proximity, RayCast3D for line-of-sight) is the right default. Don't raycast every frame to every potential target — first filter by Area3D overlap, then raycast only the candidates.
|
|
73
|
+
|
|
74
|
+
#### b) Decision logic — state machine vs behavior tree
|
|
75
|
+
|
|
76
|
+
**Strong recommendation:** start with a state machine. Always.
|
|
77
|
+
|
|
78
|
+
| | State machine | Behavior tree |
|
|
79
|
+
|---|---|---|
|
|
80
|
+
| Best for | < 10 states, scripted bosses, basic enemies | Reactive complex AI, branching priorities |
|
|
81
|
+
| Author cost | Low — read top to bottom | High — tree structure has its own learning curve |
|
|
82
|
+
| Debug | Print current state | Tree visualizer needed |
|
|
83
|
+
| Common failure | State explosion at ~15 states | Premature complexity |
|
|
84
|
+
|
|
85
|
+
Use a behavior tree when:
|
|
86
|
+
- You have 10+ states with priority interactions.
|
|
87
|
+
- You want designers (not coders) editing logic.
|
|
88
|
+
- The NPC has simultaneous goals (hunt + survive + cover).
|
|
89
|
+
|
|
90
|
+
For everything else: FSM. See `scripting-patterns/state-machine-patterns/SKILL.md`.
|
|
91
|
+
|
|
92
|
+
#### c) Escalation — tells per state
|
|
93
|
+
|
|
94
|
+
Every state must have at least one *tell* the player can read. List them up front:
|
|
95
|
+
|
|
96
|
+
| State | Animation tell | Audio tell | VFX tell |
|
|
97
|
+
|---|---|---|---|
|
|
98
|
+
| Idle | Breathing loop, occasional look | Ambient (snore, hum) | None |
|
|
99
|
+
| Alert | Head turn toward target, posture change | "Huh?" / radio chatter | Eye glow / aggro line |
|
|
100
|
+
| Aggressive | Combat pose, weapon raised | Battle cry | Muzzle flash / windup glow |
|
|
101
|
+
| Attacking | Strike anim with telegraph | Swing/shoot sound | Particle trail |
|
|
102
|
+
| Hurt | Stagger anim | Pain sound | Hit flash (see `vfx`) |
|
|
103
|
+
| Defeated | Death anim + ragdoll | Death sound | Loot drop / dissolve |
|
|
104
|
+
|
|
105
|
+
**Telegraph is non-negotiable for boss/heavy attacks:** 0.3–0.7 sec of windup the player can react to. Faster than 0.3 sec = "unfair"; slower than 0.7 sec = "boring".
|
|
106
|
+
|
|
107
|
+
#### d) Defeat handling — what happens when it dies?
|
|
108
|
+
|
|
109
|
+
Plan it. "It just disappears" is bad. Choices:
|
|
110
|
+
|
|
111
|
+
- **Death animation** plays, body stays for N sec, then `queue_free`.
|
|
112
|
+
- **Ragdoll**: switch CollisionShape from KinematicBody to RigidBody. Adds weight to the kill.
|
|
113
|
+
- **Loot drop**: spawn a pickup at death position.
|
|
114
|
+
- **Cleanup**: remove from group, disconnect signals, free children. Otherwise leak.
|
|
115
|
+
- **Score / quest hook**: emit a `died` signal the gameplay layer listens for.
|
|
116
|
+
|
|
117
|
+
### 3. Read the scene to find an attach point
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
summer_get_scene_tree
|
|
121
|
+
summer_inspect_node "./World"
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Identify where the NPC will live (a `Enemies` parent, or directly under World). Confirm the player exists for line-of-sight.
|
|
125
|
+
|
|
126
|
+
### 4. Propose the bundle
|
|
127
|
+
|
|
128
|
+
> I'm about to design a basic patrol-and-chase enemy. Scene tree: Enemy (CharacterBody3D) + Vision Area3D + Sight RayCast3D + CollisionShape3D + MeshInstance3D. Script `scripts/enemy_ai.gd` with 4 states (idle / alert / attack / dead). Sight cone radius 12m, line-of-sight raycast. Telegraph: 0.5 sec windup before attack. Drop loot on death. May I create the scene + script?
|
|
129
|
+
|
|
130
|
+
### 5. Build it
|
|
131
|
+
|
|
132
|
+
**Preferred (Summer MCP):**
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
summer_add_node(parent="./World/Enemies", type="CharacterBody3D", name="Enemy")
|
|
136
|
+
summer_add_node(parent="./World/Enemies/Enemy", type="CollisionShape3D", name="Body")
|
|
137
|
+
summer_add_node(parent="./World/Enemies/Enemy", type="MeshInstance3D", name="Mesh")
|
|
138
|
+
summer_add_node(parent="./World/Enemies/Enemy", type="Area3D", name="Vision")
|
|
139
|
+
summer_add_node(parent="./World/Enemies/Enemy/Vision", type="CollisionShape3D", name="VisionShape")
|
|
140
|
+
summer_add_node(parent="./World/Enemies/Enemy", type="RayCast3D", name="Sight")
|
|
141
|
+
summer_set_prop(path="./World/Enemies/Enemy/Sight", key="enabled", value="true")
|
|
142
|
+
summer_set_prop(path="./World/Enemies/Enemy/Sight", key="target_position", value="Vector3(0, 0, -12)")
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Set the vision shape to a SphereShape3D with `radius = 12`. Save as standalone `.tres` (do NOT inline sub_resource — see `_shared/mcp-tools-reference.md` § "Trap"):
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
# Save shape resource externally, then:
|
|
149
|
+
summer_set_prop(path="./World/Enemies/Enemy/Vision/VisionShape", key="shape", value="res://shapes/vision_sphere.tres")
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Connect the area signal:
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
summer_connect_signal(from="./World/Enemies/Enemy/Vision", signal="body_entered", to="./World/Enemies/Enemy", method="_on_target_seen")
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**Fallback (no MCP):** write the scene as `.tscn` text. Ask user to paste their existing scene first, then propose a unified diff.
|
|
159
|
+
|
|
160
|
+
### 6. Drop the script
|
|
161
|
+
|
|
162
|
+
`scripts/enemy_ai.gd` (FSM stub):
|
|
163
|
+
|
|
164
|
+
```gdscript
|
|
165
|
+
class_name EnemyAI
|
|
166
|
+
extends CharacterBody3D
|
|
167
|
+
|
|
168
|
+
enum State { IDLE, ALERT, ATTACK, DEAD }
|
|
169
|
+
|
|
170
|
+
@export var move_speed: float = 3.5
|
|
171
|
+
@export var attack_range: float = 2.0
|
|
172
|
+
@export var attack_telegraph: float = 0.5
|
|
173
|
+
@export var attack_damage: int = 10
|
|
174
|
+
@export var max_health: int = 30
|
|
175
|
+
|
|
176
|
+
@onready var sight: RayCast3D = $Sight
|
|
177
|
+
@onready var vision: Area3D = $Vision
|
|
178
|
+
@onready var anim: AnimationPlayer = $AnimationPlayer
|
|
179
|
+
|
|
180
|
+
signal died(position: Vector3)
|
|
181
|
+
|
|
182
|
+
var _state: State = State.IDLE
|
|
183
|
+
var _target: Node3D = null
|
|
184
|
+
var _health: int = 30
|
|
185
|
+
|
|
186
|
+
func _ready() -> void:
|
|
187
|
+
_health = max_health
|
|
188
|
+
vision.body_entered.connect(_on_body_entered)
|
|
189
|
+
|
|
190
|
+
func _physics_process(delta: float) -> void:
|
|
191
|
+
match _state:
|
|
192
|
+
State.IDLE:
|
|
193
|
+
_process_idle(delta)
|
|
194
|
+
State.ALERT:
|
|
195
|
+
_process_alert(delta)
|
|
196
|
+
State.ATTACK:
|
|
197
|
+
_process_attack(delta)
|
|
198
|
+
State.DEAD:
|
|
199
|
+
pass
|
|
200
|
+
move_and_slide()
|
|
201
|
+
|
|
202
|
+
func _process_idle(_delta: float) -> void:
|
|
203
|
+
velocity = Vector3.ZERO
|
|
204
|
+
# transition handled by _on_body_entered
|
|
205
|
+
|
|
206
|
+
func _process_alert(_delta: float) -> void:
|
|
207
|
+
if _target == null:
|
|
208
|
+
_change_state(State.IDLE)
|
|
209
|
+
return
|
|
210
|
+
if not _has_line_of_sight(_target):
|
|
211
|
+
_change_state(State.IDLE)
|
|
212
|
+
_target = null
|
|
213
|
+
return
|
|
214
|
+
var dir := (_target.global_position - global_position).normalized()
|
|
215
|
+
velocity = dir * move_speed
|
|
216
|
+
look_at(_target.global_position, Vector3.UP)
|
|
217
|
+
if global_position.distance_squared_to(_target.global_position) < attack_range * attack_range:
|
|
218
|
+
_change_state(State.ATTACK)
|
|
219
|
+
|
|
220
|
+
func _process_attack(_delta: float) -> void:
|
|
221
|
+
velocity = Vector3.ZERO
|
|
222
|
+
# attack handled in _change_state via async telegraph
|
|
223
|
+
|
|
224
|
+
func _change_state(new_state: State) -> void:
|
|
225
|
+
if _state == new_state:
|
|
226
|
+
return
|
|
227
|
+
_state = new_state
|
|
228
|
+
match new_state:
|
|
229
|
+
State.IDLE:
|
|
230
|
+
anim.play("idle")
|
|
231
|
+
State.ALERT:
|
|
232
|
+
anim.play("alert")
|
|
233
|
+
State.ATTACK:
|
|
234
|
+
_execute_attack()
|
|
235
|
+
State.DEAD:
|
|
236
|
+
anim.play("death")
|
|
237
|
+
died.emit(global_position)
|
|
238
|
+
await get_tree().create_timer(2.0).timeout
|
|
239
|
+
queue_free()
|
|
240
|
+
|
|
241
|
+
func _execute_attack() -> void:
|
|
242
|
+
anim.play("attack_windup")
|
|
243
|
+
await get_tree().create_timer(attack_telegraph).timeout
|
|
244
|
+
if _state != State.ATTACK or _target == null:
|
|
245
|
+
return
|
|
246
|
+
if global_position.distance_squared_to(_target.global_position) < (attack_range * 1.2) ** 2:
|
|
247
|
+
if _target.has_method("take_damage"):
|
|
248
|
+
_target.take_damage(attack_damage)
|
|
249
|
+
anim.play("attack_recover")
|
|
250
|
+
await anim.animation_finished
|
|
251
|
+
_change_state(State.ALERT)
|
|
252
|
+
|
|
253
|
+
func _has_line_of_sight(target: Node3D) -> bool:
|
|
254
|
+
sight.target_position = sight.to_local(target.global_position)
|
|
255
|
+
sight.force_raycast_update()
|
|
256
|
+
if not sight.is_colliding():
|
|
257
|
+
return true
|
|
258
|
+
return sight.get_collider() == target
|
|
259
|
+
|
|
260
|
+
func _on_body_entered(body: Node3D) -> void:
|
|
261
|
+
if body.is_in_group("player") and _state == State.IDLE:
|
|
262
|
+
_target = body
|
|
263
|
+
_change_state(State.ALERT)
|
|
264
|
+
|
|
265
|
+
func take_damage(amount: int) -> void:
|
|
266
|
+
if _state == State.DEAD:
|
|
267
|
+
return
|
|
268
|
+
_health -= amount
|
|
269
|
+
if _health <= 0:
|
|
270
|
+
_change_state(State.DEAD)
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Key design notes embedded above:
|
|
274
|
+
- Telegraph (`attack_telegraph`) is a parameter — designers can tune feel.
|
|
275
|
+
- `take_damage` is the public hook the player's weapon calls.
|
|
276
|
+
- `died` signal is the gameplay-layer hook (loot, score, quest progress).
|
|
277
|
+
- `distance_squared_to` for comparisons (no sqrt cost).
|
|
278
|
+
- LOS check uses `force_raycast_update` for one-shot precision.
|
|
279
|
+
|
|
280
|
+
### 7. Verify
|
|
281
|
+
|
|
282
|
+
```
|
|
283
|
+
summer_save_scene
|
|
284
|
+
summer_get_script_errors
|
|
285
|
+
summer_play
|
|
286
|
+
# user tests
|
|
287
|
+
summer_stop
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
If the enemy doesn't aggro, common causes:
|
|
291
|
+
- Player isn't in `player` group.
|
|
292
|
+
- Vision Area3D's sphere shape is inline sub_resource (silent fail).
|
|
293
|
+
- Sight RayCast3D `enabled = false` or `collide_with_areas = true` blocking on its own Vision area.
|
|
294
|
+
|
|
295
|
+
## Common mistakes
|
|
296
|
+
|
|
297
|
+
| Don't | Do | Why |
|
|
298
|
+
|---|---|---|
|
|
299
|
+
| 360° perception | Sight cone or sphere with raycast LOS | "Sees through walls" feels unfair |
|
|
300
|
+
| Instant attack | 0.3–0.7 sec telegraph | Player needs to read the swing |
|
|
301
|
+
| Behavior tree for a grunt | State machine with 4 states | BT overhead unjustified at low complexity |
|
|
302
|
+
| Per-frame `get_tree().get_first_node_in_group("player")` | Cache target on aggro | Tree walks aren't free |
|
|
303
|
+
| `distance_to` in `_physics_process` | `distance_squared_to` for comparisons | Square root costs at scale |
|
|
304
|
+
| Same animation in all states | Distinct animations per state | The state machine is invisible; animations make it visible |
|
|
305
|
+
| `queue_free()` instantly on death | Death anim → 2 sec → free | Instant despawn breaks immersion + skips loot drops |
|
|
306
|
+
| Inline SphereShape3D for Vision Area3D | Standalone `.tres` | Inline sub_resources break SetResourceProperty |
|
|
307
|
+
| 100+ enemies with full FSM | Wave-mob archetype (2 states, no LOS) | Cheap mobs need cheap brains |
|
|
308
|
+
| No `died` signal | Emit on death | Gameplay layer needs the hook |
|
|
309
|
+
|
|
310
|
+
## Anti-patterns
|
|
311
|
+
|
|
312
|
+
- **"AI that dodges everything."** Enemies that 100% dodge feel cheap. Random reaction time (50–250 ms) reads as skill, not aimbot.
|
|
313
|
+
- **Boss with 12 unique mechanics.** Cap at 4–6 distinct mechanics across phases. Players can't track more.
|
|
314
|
+
- **Companion that walks through doors.** NavigationAgent3D with proper navmesh. Don't fake it with raw `move_toward`.
|
|
315
|
+
- **Civilian that screams forever.** Add a calm-down state on a 5–10 sec timer.
|
|
316
|
+
|
|
317
|
+
## Collaborative protocol
|
|
318
|
+
|
|
319
|
+
This skill writes scenes and scripts. Always ask before each step. Group related writes into one ask. See `_shared/collaborative-protocol.md`.
|
|
320
|
+
|
|
321
|
+
## Want a working starter?
|
|
322
|
+
|
|
323
|
+
For a fully-rigged enemy with anim controller + loot drop:
|
|
324
|
+
|
|
325
|
+
→ **template-id**: `template-3d-fps` — ships with a sample patrol enemy
|
|
326
|
+
→ **Repo**: https://github.com/SummerEngine/FPS-template-Summer-Engine
|
|
327
|
+
|
|
328
|
+
For higher-complexity AI (boss with phases, behavior trees), defer to `ai-and-npcs/behavior-trees/SKILL.md`.
|
|
329
|
+
|
|
330
|
+
## See also
|
|
331
|
+
|
|
332
|
+
- `_shared/mcp-tools-reference.md` — full MCP tool list
|
|
333
|
+
- `_shared/godot-version.md` — Godot 4.5 API notes
|
|
334
|
+
- `_shared/collaborative-protocol.md` — "May I write" pattern
|
|
335
|
+
- `_shared/gd-style.md` — typed GDScript conventions
|
|
336
|
+
- `ai-and-npcs/state-machine-npc/SKILL.md` — FSM pattern deep dive
|
|
337
|
+
- `ai-and-npcs/behavior-trees/SKILL.md` — when complexity demands a BT
|
|
338
|
+
- `ai-and-npcs/perception-sight-and-hearing/SKILL.md` — sensor patterns
|
|
339
|
+
- `ai-and-npcs/navmesh-pathfinding/SKILL.md` — NavigationAgent3D for movement
|
|
340
|
+
- `ai-and-npcs/llm-driven-dialogue/SKILL.md` — Summer's wedge for talking NPCs
|
|
341
|
+
- `ai-and-npcs/boss-patterns/SKILL.md` — phase transitions, mechanics
|
|
342
|
+
- `visual-effects/vfx/SKILL.md` — hit flash and death VFX recipes
|
|
343
|
+
- `scripting-patterns/state-machine-patterns/SKILL.md` — generic FSM patterns
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: audio-direction
|
|
3
|
+
description: Use when defining the sonic identity — music style, instruments, SFX vocabulary, dynamic music plan. Outputs an audio bible at `.summer/audio-bible.md`. Trigger on "audio direction", "audio bible", "music style", "sound design", "what should it sound like", "dynamic music".
|
|
4
|
+
license: MIT
|
|
5
|
+
compatibility: [Cursor, Claude Code, Windsurf, Codex]
|
|
6
|
+
category: audio
|
|
7
|
+
user-invocable: true
|
|
8
|
+
allowed-tools: Read Grep Glob Write Edit summer_get_project_context summer_get_scene_tree summer_inspect_node summer_add_node summer_project_setting summer_generate_audio
|
|
9
|
+
paths: [".summer/**", "project.godot", "**/*.tscn", "audio/**"]
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# /audio-direction — Define the Sonic Identity
|
|
13
|
+
|
|
14
|
+
## Overview
|
|
15
|
+
|
|
16
|
+
Most indie games sound like a single library used flat. The same generic "swoosh" for menu, jump, pickup, hit. That's not audio direction — that's audio absence. This skill builds an audio bible that names the music style, the SFX vocabulary, the dynamic music plan, and the spatial / mix rules. Writes to `.summer/audio-bible.md` and configures the audio bus layout in the project so the rest of the work has a real foundation.
|
|
17
|
+
|
|
18
|
+
**Core principle:** SFX needs a *vocabulary*, not a *library*. Eight named SFX classes with consistent character beat 80 random library hits.
|
|
19
|
+
|
|
20
|
+
## Steps
|
|
21
|
+
|
|
22
|
+
### 1. Open with three references
|
|
23
|
+
|
|
24
|
+
Ask exactly this:
|
|
25
|
+
|
|
26
|
+
> Three reference tracks, games, or films for the music. What instruments matter? And what's the SFX vibe — chunky, clean, synthetic, or organic?
|
|
27
|
+
|
|
28
|
+
Three is the minimum. One or two collapses into a clone. References are the highest-signal input — listen carefully.
|
|
29
|
+
|
|
30
|
+
If they freeze, offer four axes (not titles):
|
|
31
|
+
|
|
32
|
+
> Pick the closest: a) orchestral + cinematic (Hollow Knight, Journey), b) lo-fi electronic + ambient (Disco Elysium, Outer Wilds), c) chiptune / synth (Hyper Light Drifter, Celeste), d) acoustic / folk (Sable, A Short Hike).
|
|
33
|
+
|
|
34
|
+
### 2. Read the brief and art bible if they exist
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
Read .summer/GameSoul.md
|
|
38
|
+
Read .summer/art-bible.md
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Audio must rhyme with the visuals. Quote it back:
|
|
42
|
+
|
|
43
|
+
> Art bible says "soft, hand-warmed, golden-hour, painterly". That rhymes with acoustic / folk / lo-fi over orchestral or aggressive synth. Confirming.
|
|
44
|
+
|
|
45
|
+
If the brief says "horror", that constrains the music style harder than the user's references will. Name the constraint.
|
|
46
|
+
|
|
47
|
+
### 3. Pin the music style
|
|
48
|
+
|
|
49
|
+
Force one primary style. Sub-styles can blend, but a primary is required.
|
|
50
|
+
|
|
51
|
+
| Style | Anchor instruments | Pairs with | Avoid |
|
|
52
|
+
|---|---|---|---|
|
|
53
|
+
| Orchestral cinematic | Strings, brass, choir | Story-led, fantasy | Casual / cozy |
|
|
54
|
+
| Acoustic / folk | Guitar, piano, hand percussion | Cozy, narrative | Action / horror |
|
|
55
|
+
| Lo-fi electronic | Vintage synth pad, tape sat, light beat | Atmospheric, indie | Combat-heavy |
|
|
56
|
+
| Chiptune / synthwave | 8-bit / 16-bit chips, FM synth | Retro, platformer | Realism |
|
|
57
|
+
| Industrial / metal | Distorted bass, percussion-led | Horror, action | Cozy |
|
|
58
|
+
| Ambient drone | Pads, no drums, slow movement | Walking sim, horror | Action |
|
|
59
|
+
| Jazz / fusion | Live instruments, improv | Stylish, narrative | Generic action |
|
|
60
|
+
| Tracker / hyperpop | Glitchy, dense, modern | Esoteric, indie | Mass appeal |
|
|
61
|
+
|
|
62
|
+
Ask:
|
|
63
|
+
|
|
64
|
+
> One primary style. Which?
|
|
65
|
+
|
|
66
|
+
### 4. Define tempo + key range
|
|
67
|
+
|
|
68
|
+
Concrete numbers, not "varies".
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
Tempo range: 80–110 BPM (slow side; combat would push 120+)
|
|
72
|
+
Key center: D minor (warm but melancholic)
|
|
73
|
+
Modulation rule: combat tracks shift to D dorian, calm tracks to D natural minor
|
|
74
|
+
Time signature: 4/4 default; 6/8 for emotional cues
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 5. Build the SFX vocabulary
|
|
78
|
+
|
|
79
|
+
Eight named classes. Each class has a *character*, not a single sound.
|
|
80
|
+
|
|
81
|
+
| Class | Character | Examples in this game | DON'T |
|
|
82
|
+
|---|---|---|---|
|
|
83
|
+
| **UI** | Soft, woody, short (60-100ms) | Menu hover, button click, tab switch | No bell tones, no synth blips |
|
|
84
|
+
| **Pickup positive** | Warm, ascending interval (major third), 200-400ms | Coin, key, heal | No pure sine "ding" |
|
|
85
|
+
| **Pickup neutral** | Brief tactile, no melodic component | Note, breadcrumb, lore item | No ascending pitch (reads as reward) |
|
|
86
|
+
| **Negative / damage** | Punchy low-mid, slight detune | Player hit, enemy hit, fall damage | No long reverb tail |
|
|
87
|
+
| **Movement / footstep** | Material-aware (wood / stone / grass) | Walk, run, jump, land | No same sample for all surfaces |
|
|
88
|
+
| **Attack / action** | Front-loaded transient, short body | Swing, parry, dash | No layered orchestral hit |
|
|
89
|
+
| **Ambient layer** | Continuous, low presence | Wind, room tone, distant waves | No looping melody |
|
|
90
|
+
| **Stinger / event** | Distinctive, rare, melodic | Boss intro, level clear, story beat | No reuse for normal events |
|
|
91
|
+
|
|
92
|
+
Ask:
|
|
93
|
+
|
|
94
|
+
> Eight classes above. Adjust any character / examples / DON'Ts?
|
|
95
|
+
|
|
96
|
+
### 6. Define the dynamic music plan
|
|
97
|
+
|
|
98
|
+
Static music = one track loops forever in a level. Boring. Dynamic music = layers swell with player state. Pick a model.
|
|
99
|
+
|
|
100
|
+
| Model | How it works | Cost | Best for |
|
|
101
|
+
|---|---|---|---|
|
|
102
|
+
| **Layered stems** | One bar / multiple instrument layers; combat adds drum stem, calm removes it | Medium (need stems authored) | Most games |
|
|
103
|
+
| **Vertical re-orchestration** | Same melody, different instrumentation per state | High (composer-led) | Story-led / boss-heavy |
|
|
104
|
+
| **Horizontal sequencing** | Different tracks for different states, crossfade | Low (just author 3-5 full tracks) | Vertical slice scope |
|
|
105
|
+
| **Procedural / generative** | Generate from rules in real-time | High dev cost | Special-case |
|
|
106
|
+
|
|
107
|
+
Pick one. State the state machine:
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
States:
|
|
111
|
+
CALM — exploration, low intensity
|
|
112
|
+
TENSION — enemies aware but not engaged
|
|
113
|
+
COMBAT — engaged
|
|
114
|
+
VICTORY — encounter cleared (4-second sting → return to CALM)
|
|
115
|
+
|
|
116
|
+
Transitions:
|
|
117
|
+
CALM → TENSION when an enemy detects the player (2s crossfade)
|
|
118
|
+
TENSION → COMBAT when first damage exchanged (instant drum stem)
|
|
119
|
+
COMBAT → VICTORY when last enemy dies (drum stem cuts, brass swell)
|
|
120
|
+
VICTORY → CALM after 4s
|
|
121
|
+
|
|
122
|
+
Rules:
|
|
123
|
+
No state stays > 90s without variation (loop with bar-1 randomization)
|
|
124
|
+
Volume ducking: dialogue active → music -6dB
|
|
125
|
+
Boss music is its own track; ignores normal state machine
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### 7. Define the spatial + mix rules
|
|
129
|
+
|
|
130
|
+
| Rule | Value |
|
|
131
|
+
|---|---|
|
|
132
|
+
| 3D attenuation curve | Inverse-square (default), max distance 25m |
|
|
133
|
+
| Reverb buses | 1 small (interior), 1 large (cave / hall), 1 outdoor |
|
|
134
|
+
| HRTF for player | ON (headphone-friendly directional cues) |
|
|
135
|
+
| Master ceiling | -1 dBTP (true peak, no clipping on consoles) |
|
|
136
|
+
| Music vs SFX vs UI mix | -8 dB / 0 dB / -3 dB (SFX is loudest) |
|
|
137
|
+
| Footstep rolloff | Cuts at 4m for player; cuts at 10m for NPC (player needs them, distant NPCs are noise) |
|
|
138
|
+
| Compression | Light bus compression on master (2:1, slow attack, 100ms release) |
|
|
139
|
+
|
|
140
|
+
### 8. Configure the audio bus layout
|
|
141
|
+
|
|
142
|
+
Real audio in Godot needs a bus layout. Default Godot has Master only — configure the buses now.
|
|
143
|
+
|
|
144
|
+
**Preferred (Summer MCP):** Buses are typically in `default_bus_layout.tres`. Modify project setting:
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
summer_project_setting(key="audio/buses/default_bus_layout", value="res://audio/default_bus_layout.tres")
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Ask the user:
|
|
151
|
+
|
|
152
|
+
> May I create `audio/default_bus_layout.tres` with these buses: Master / Music / SFX / UI / Voice / Ambient / Reverb_Small / Reverb_Large / Reverb_Outdoor?
|
|
153
|
+
|
|
154
|
+
On yes, write the `.tres` directly via `Write` (it's plain Godot resource format the host can produce):
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
Write audio/default_bus_layout.tres
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**Fallback (no MCP):** Print the `.tres` for the user to paste, plus the `project.godot` line.
|
|
161
|
+
|
|
162
|
+
### 9. Save the audio bible
|
|
163
|
+
|
|
164
|
+
Always write the bible. Path: `.summer/audio-bible.md`.
|
|
165
|
+
|
|
166
|
+
```markdown
|
|
167
|
+
# Audio Bible — <Game Name>
|
|
168
|
+
|
|
169
|
+
**References:** <3 specific tracks / games / films>
|
|
170
|
+
**Style:** <one of the 8 above>
|
|
171
|
+
**Tempo / key:** 80–110 BPM, D minor, 4/4
|
|
172
|
+
|
|
173
|
+
## SFX Vocabulary
|
|
174
|
+
| Class | Character | Examples | DON'T |
|
|
175
|
+
|---|---|---|---|
|
|
176
|
+
| UI | Soft, woody, 60-100ms | hover, click | bell tones |
|
|
177
|
+
| Pickup positive | Warm major-third, 200-400ms | coin, heal | pure sine |
|
|
178
|
+
| ... | ... | ... | ... |
|
|
179
|
+
|
|
180
|
+
## Dynamic Music Plan
|
|
181
|
+
**Model:** Layered stems
|
|
182
|
+
**States:** CALM / TENSION / COMBAT / VICTORY
|
|
183
|
+
**Transitions:** ...
|
|
184
|
+
**Rules:** ...
|
|
185
|
+
|
|
186
|
+
## Spatial + Mix
|
|
187
|
+
- 3D attenuation: inverse-square, 25m
|
|
188
|
+
- Reverb buses: small / large / outdoor
|
|
189
|
+
- HRTF: ON
|
|
190
|
+
- Master ceiling: -1 dBTP
|
|
191
|
+
- Mix bus: Music -8 dB, SFX 0 dB, UI -3 dB
|
|
192
|
+
- Footstep rolloff: 4m player / 10m NPC
|
|
193
|
+
|
|
194
|
+
## Bus Layout
|
|
195
|
+
Master → Music, SFX (→ Reverb_Small / Large / Outdoor), UI, Voice, Ambient
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Ask:
|
|
199
|
+
|
|
200
|
+
> May I create `.summer/audio-bible.md` with this bible?
|
|
201
|
+
|
|
202
|
+
### 10. Optional — generate first SFX seeds
|
|
203
|
+
|
|
204
|
+
If the user wants to start generating, propose 3-5 SFX seeds aligned with the vocabulary. These are starting points, not finals.
|
|
205
|
+
|
|
206
|
+
```
|
|
207
|
+
summer_generate_audio(prompt="soft woody UI click, 80ms, no reverb, neutral pitch", duration=0.1)
|
|
208
|
+
summer_generate_audio(prompt="warm major-third coin pickup, 300ms, light reverb", duration=0.3)
|
|
209
|
+
summer_generate_audio(prompt="punchy low-mid player damage hit, slight detune, 200ms, no tail", duration=0.2)
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Ask first:
|
|
213
|
+
|
|
214
|
+
> May I generate first-pass SFX seeds for UI, pickup, and damage classes? They're starting points, not finals.
|
|
215
|
+
|
|
216
|
+
### 11. Route to the next step
|
|
217
|
+
|
|
218
|
+
End with:
|
|
219
|
+
|
|
220
|
+
> Bible saved. Next:
|
|
221
|
+
> - `/summer:design-mechanic` to wire SFX cues into specific mechanics.
|
|
222
|
+
> - `/summer:vfx` if you haven't paired audio with visual effect cues yet.
|
|
223
|
+
> - Author or generate the calm music track first; it's the longest-running and sets the room tone.
|
|
224
|
+
|
|
225
|
+
## Common mistakes
|
|
226
|
+
|
|
227
|
+
| Mistake | Fix |
|
|
228
|
+
|---|---|
|
|
229
|
+
| Single reference for music | Three minimum. Listen for the *common* element across them. |
|
|
230
|
+
| "Library" instead of "vocabulary" | A bag of free SFX with no shared character feels random. Eight named classes with character notes. |
|
|
231
|
+
| Same SFX for all events | If pickup-coin and pickup-key sound identical, the player learns nothing from audio. |
|
|
232
|
+
| No dynamic music plan | Static loops bore in 90s. Pick a model (layered / vertical / horizontal / procedural). |
|
|
233
|
+
| Music louder than SFX | Players need SFX for gameplay; music is mood. Default mix is -8 dB music vs 0 dB SFX. |
|
|
234
|
+
| No reverb buses | "Send" architecture (rooms route to a small/large/outdoor reverb bus) is mandatory or your mix sounds dry-then-wet inconsistently. |
|
|
235
|
+
| Pure sine "ding" for pickup | Reads as cheap mobile game. Use a major-third interval with character. |
|
|
236
|
+
| Same footstep sample for all surfaces | Material-aware footsteps are 80% of the perceived audio quality. |
|
|
237
|
+
| No master ceiling / true peak | Audio clips on console builds without `-1 dBTP` ceiling. Always set it. |
|
|
238
|
+
| Calling SetResourceProperty on inline sub_resources | Silent fail. See `_shared/mcp-tools-reference.md` § Trap. |
|
|
239
|
+
|
|
240
|
+
## Collaborative protocol
|
|
241
|
+
|
|
242
|
+
This skill writes one bible (`.summer/audio-bible.md`), creates a bus layout (`audio/default_bus_layout.tres`), modifies `project.godot`, and may generate audio (metered). Always ask before writing or generating. See `_shared/collaborative-protocol.md`.
|
|
243
|
+
|
|
244
|
+
## Want a working starter?
|
|
245
|
+
|
|
246
|
+
No template — this is a workflow that produces the bible the rest of the project obeys. After the bible exists, the bus layout is set up and any subsequent audio work (footstep system, dynamic music) reads from it.
|
|
247
|
+
|
|
248
|
+
## See also
|
|
249
|
+
|
|
250
|
+
- `_shared/collaborative-protocol.md`
|
|
251
|
+
- `_shared/godot-version.md` — Godot 4.5 audio API notes
|
|
252
|
+
- `_shared/mcp-tools-reference.md`
|
|
253
|
+
- `scene-and-project/brainstorm-game/SKILL.md` — produces the brief that anchors the bible
|
|
254
|
+
- `rendering-and-lighting/art-direction/SKILL.md` — the visual counterpart; audio must rhyme with it
|
|
255
|
+
- `audio/audio-bus-setup/SKILL.md` (NEXT) — implements the bus layout in detail
|
|
256
|
+
- `audio/3d-spatial-audio/SKILL.md` (NEXT) — spatial attenuation patterns
|
|
257
|
+
- `audio/footstep-systems/SKILL.md` (NEXT) — material-aware footstep impl
|
|
258
|
+
- `audio/dynamic-music-stingers/SKILL.md` (LATER) — implements the dynamic music plan
|