summer-engine 1.3.0 → 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/README.md +7 -2
- package/dist/bin/summer.js +24 -1
- package/dist/commands/doctor.js +44 -20
- package/dist/commands/setup.js +77 -6
- package/dist/commands/skills.js +5 -3
- package/dist/lib/format.d.ts +23 -0
- package/dist/lib/format.js +60 -0
- package/dist/lib/format.test.d.ts +1 -0
- package/dist/lib/format.test.js +36 -0
- package/dist/lib/skills-registry.d.ts +16 -0
- package/dist/lib/skills-registry.js +35 -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/debug.md +94 -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/play.md +88 -0
- 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/asset-pipeline/asset-strategy/SKILL.md +1 -0
- package/skills/audio/audio-direction/SKILL.md +258 -0
- package/skills/catalog.yaml +13 -3
- package/skills/character-controllers/fps-controller/SKILL.md +1 -0
- package/skills/debugging/debug/SKILL.md +137 -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/3d-lighting/SKILL.md +1 -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/scene-and-project/make-game/SKILL.md +1 -0
- package/skills/scene-and-project/play/SKILL.md +108 -0
- package/skills/scene-and-project/scene-composition/SKILL.md +1 -0
- package/skills/scripting-patterns/gdscript-patterns/SKILL.md +1 -0
- package/skills/ui-and-ux/ui-basics/SKILL.md +1 -0
- package/skills/visual-effects/vfx/SKILL.md +280 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Skill Spec: /vfx
|
|
2
|
+
|
|
3
|
+
## Fixture
|
|
4
|
+
|
|
5
|
+
- Godot 4.5 / Summer Engine project, main scene with `World/Player` (CharacterBody3D + Camera3D) and `World/Enemy` (CharacterBody3D + MeshInstance3D + a `damaged` signal on a script).
|
|
6
|
+
- Summer MCP tools available (engine running on localhost:6550).
|
|
7
|
+
- Host file tools available (Read, Edit, Write).
|
|
8
|
+
|
|
9
|
+
## Case 1: Happy Path — combat hits feel flat
|
|
10
|
+
|
|
11
|
+
**Input:** "My combat feels flat. Add some juice."
|
|
12
|
+
|
|
13
|
+
**Expected behavior:**
|
|
14
|
+
|
|
15
|
+
1. Skill asks one focused question: "What feels flat? combat hits / movement / pickups / damage / explosions / spell casts."
|
|
16
|
+
2. User says "combat hits".
|
|
17
|
+
3. Skill calls `summer_get_scene_tree`, then `summer_inspect_node "./World/Enemy"` to confirm a MeshInstance3D and a `damaged` signal exist.
|
|
18
|
+
4. Skill proposes hit flash + camera shake bundle: "I'm about to add HitFlash on `./World/Enemy`, attach `scripts/hit_flash.gd`, attach `scripts/camera_shake.gd` to `./World/Player/Camera3D`, and connect the `damaged` signal. OK?"
|
|
19
|
+
5. After user OK:
|
|
20
|
+
- `summer_add_node(parent="./World/Enemy", type="Node", name="HitFlash")`
|
|
21
|
+
- `Write scripts/hit_flash.gd`
|
|
22
|
+
- `Write scripts/camera_shake.gd`
|
|
23
|
+
- `summer_connect_signal(from="./World/Enemy", signal="damaged", to="./World/Enemy/HitFlash", method="flash")`
|
|
24
|
+
- `summer_save_scene`
|
|
25
|
+
- `summer_get_script_errors`
|
|
26
|
+
|
|
27
|
+
**Assertions:**
|
|
28
|
+
|
|
29
|
+
- [ ] Skill asks the "what feels flat?" question before any tool call.
|
|
30
|
+
- [ ] Skill reads the scene tree and inspects the relevant node before adding nodes.
|
|
31
|
+
- [ ] Skill explicitly states the bundle of changes and asks "May I" / "OK?" before mutation.
|
|
32
|
+
- [ ] HitFlash script uses Tween + `set_surface_override_material`, NOT a polled timer in `_process`.
|
|
33
|
+
- [ ] CameraShake uses quadratic falloff (`_trauma * _trauma`), NOT linear.
|
|
34
|
+
- [ ] Skill never calls `summer_set_resource_property` on an inline sub_resource (e.g. ParticleProcessMaterial).
|
|
35
|
+
- [ ] Skill ends with `summer_save_scene` then `summer_get_script_errors`.
|
|
36
|
+
- [ ] Final hit flash duration is 0.06–0.12 sec (NOT 0.3 sec).
|
|
37
|
+
|
|
38
|
+
## Case 2: Failure Path — no Camera3D in scene
|
|
39
|
+
|
|
40
|
+
**Fixture:** `World/Player` exists but has no Camera3D child. User asks for screen shake.
|
|
41
|
+
|
|
42
|
+
**Input:** "Add screen shake when I take damage."
|
|
43
|
+
|
|
44
|
+
**Expected behavior:**
|
|
45
|
+
|
|
46
|
+
- Skill calls `summer_get_scene_tree` and `summer_inspect_node "./World/Player"`, sees no Camera3D.
|
|
47
|
+
- Skill does NOT silently spawn a Camera3D — it asks: "I don't see a Camera3D under `./World/Player`. Should I add one (and risk conflicting with an existing camera elsewhere), or do you have a camera node I should target?"
|
|
48
|
+
- Waits for the answer.
|
|
49
|
+
|
|
50
|
+
**Assertions:**
|
|
51
|
+
|
|
52
|
+
- [ ] Skill detects the missing Camera3D and stops to ask.
|
|
53
|
+
- [ ] Skill does NOT add a Camera3D unilaterally.
|
|
54
|
+
- [ ] Skill names a concrete next-step path (point to existing camera OR add new one explicitly).
|
|
55
|
+
|
|
56
|
+
## Case 3: Edge Case — user wants "all the juice"
|
|
57
|
+
|
|
58
|
+
**Fixture:** Same as Case 1.
|
|
59
|
+
|
|
60
|
+
**Input:** "Add all the VFX. Make it feel like Hades."
|
|
61
|
+
|
|
62
|
+
**Expected behavior:**
|
|
63
|
+
|
|
64
|
+
- Skill resists the temptation to stack 5 recipes at once.
|
|
65
|
+
- Anchors on combat hits first (per skill's own guidance: "loudest perceived improvement per minute spent").
|
|
66
|
+
- Says: "I'd rather land hit-flash + screen-shake first so we can see them work, then stack pickups/explosions next. Sound good?"
|
|
67
|
+
- Does NOT propose a single bundle of 5 recipes.
|
|
68
|
+
|
|
69
|
+
**Assertions:**
|
|
70
|
+
|
|
71
|
+
- [ ] Skill explicitly chooses ONE recipe to start with, not all.
|
|
72
|
+
- [ ] Skill names "combat hits" as the anchor.
|
|
73
|
+
- [ ] Skill defers other recipes to a follow-up pass.
|
|
74
|
+
|
|
75
|
+
## Case 4: No Summer MCP — file-edit fallback
|
|
76
|
+
|
|
77
|
+
**Fixture:** Same as Case 1, but Summer MCP is unavailable (Cursor in a project where the engine is not connected).
|
|
78
|
+
|
|
79
|
+
**Input:** "Add a hit flash to my enemy."
|
|
80
|
+
|
|
81
|
+
**Expected behavior:**
|
|
82
|
+
|
|
83
|
+
- Skill detects MCP unavailable.
|
|
84
|
+
- Asks the user to paste the relevant `.tscn` snippet (the Enemy node + its children) so the skill can write the right node-path references.
|
|
85
|
+
- Writes `scripts/hit_flash.gd` directly via host file tools.
|
|
86
|
+
- Provides a `.tscn` patch (or step-by-step manual editor instructions) for adding the HitFlash node and connecting the signal — does NOT blindly call `summer_*` tools.
|
|
87
|
+
- Still asks "May I write `scripts/hit_flash.gd`?" before each file write.
|
|
88
|
+
|
|
89
|
+
**Assertions:**
|
|
90
|
+
|
|
91
|
+
- [ ] Skill identifies MCP is unavailable and adapts.
|
|
92
|
+
- [ ] Generated GDScript is syntactically valid Godot 4 GDScript with type hints.
|
|
93
|
+
- [ ] Skill asks the user to paste the `.tscn` snippet for accurate node paths.
|
|
94
|
+
- [ ] Skill still applies the "May I write …" protocol on file writes.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
This spec runs via `/skill-test vfx spec` (see `_meta/skill-test/SKILL.md`).
|
|
@@ -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
|
|
@@ -4,6 +4,7 @@ description: Decision guide for how to create different game asset types — gen
|
|
|
4
4
|
license: MIT
|
|
5
5
|
compatibility: [Cursor, Claude Code, Windsurf, Codex]
|
|
6
6
|
category: asset-pipeline
|
|
7
|
+
user-invocable: false
|
|
7
8
|
allowed-tools: Read Grep summer_search_assets summer_import_asset summer_generate_image summer_generate_3d summer_generate_audio summer_import_from_url
|
|
8
9
|
---
|
|
9
10
|
|