summer-engine 1.3.1 → 1.4.0
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/.claude-plugin/plugin.json +59 -0
- package/.codex-plugin/plugin.json +38 -0
- package/.cursor-plugin/plugin.json +42 -0
- package/.mcp.json +8 -0
- package/.opencode/plugins/summer.js +49 -0
- package/CHANGELOG.md +67 -0
- package/README.md +13 -0
- package/dist/lib/skills-registry.d.ts +80 -0
- package/dist/lib/skills-registry.js +172 -0
- package/gemini-extension.json +21 -0
- package/hooks/hooks-cursor.json +21 -0
- package/hooks/hooks.json +31 -0
- package/hooks/pre-commit-doctor.sh +123 -0
- package/hooks/run-hook.cmd +23 -0
- package/hooks/session-start.sh +120 -0
- package/marketplace.json +24 -0
- package/package.json +13 -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/peer-to-peer-multiplayer.md +99 -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 +8 -0
- package/skills/audio/audio-direction/SKILL.md +258 -0
- package/skills/catalog.yaml +82 -0
- package/skills/character-controllers/fps-controller/SKILL.md +27 -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/peer-to-peer-multiplayer/SKILL.md +378 -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 +20 -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 +10 -0
- package/skills/scene-and-project/scene-composition/SKILL.md +8 -0
- package/skills/ui-and-ux/ui-basics/SKILL.md +28 -0
- package/skills/visual-effects/vfx/SKILL.md +280 -0
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: vfx
|
|
3
|
+
description: Use when the user says the game "feels flat", "needs juice", "lacks impact", or asks for particles, hit feedback, screen shake, camera juice, muzzle flashes, explosions, or pickup effects. Walks the user through a guided 30-minute polish pass that adds visible feedback to combat, movement, pickups, damage, and casting. Trigger on "vfx", "juice", "feels flat", "polish", "particles", "screen shake", "hit flash", "feedback".
|
|
4
|
+
license: MIT
|
|
5
|
+
compatibility: [Cursor, Claude Code, Windsurf, Codex]
|
|
6
|
+
category: visual-effects
|
|
7
|
+
user-invocable: true
|
|
8
|
+
allowed-tools: Read Grep Edit Write summer_get_scene_tree summer_inspect_node summer_inspect_resource summer_add_node summer_set_prop summer_set_resource_property summer_connect_signal summer_save_scene summer_get_script_errors summer_play summer_stop
|
|
9
|
+
paths: ["**/*.gd", "**/*.tscn", "**/*.tres"]
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# /vfx — Visible Polish in 30 Minutes
|
|
13
|
+
|
|
14
|
+
A game with bad VFX feels like a tech demo. A game with good VFX feels alive even before the mechanics are deep. This skill is a guided polish pass: you ask the user *what feels flat*, then deliver one or two tight recipes that add immediately-visible feedback. No vague "add particles". Specific node, specific values, specific signal hookup, verified result.
|
|
15
|
+
|
|
16
|
+
## Steps
|
|
17
|
+
|
|
18
|
+
### 1. Ask which thing feels flat
|
|
19
|
+
|
|
20
|
+
Open with one focused question. Wait for the answer.
|
|
21
|
+
|
|
22
|
+
> What feels flat? Pick one to start: combat hits / movement / pickups / damage taken / explosions / spell casts / something else.
|
|
23
|
+
|
|
24
|
+
If the user says "all of it", anchor on **combat hits** — it gives the loudest perceived improvement per minute spent.
|
|
25
|
+
|
|
26
|
+
### 2. Read the current scene state
|
|
27
|
+
|
|
28
|
+
Before adding anything, see what's there.
|
|
29
|
+
|
|
30
|
+
**Preferred (Summer MCP):**
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
summer_get_scene_tree
|
|
34
|
+
summer_inspect_node "./World/Player" # or whichever node owns the action
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
You're looking for: where the trigger lives (e.g. `body_entered` signal on a Hurtbox), whether a Camera3D exists for shake, whether the target has a MeshInstance3D for a flash material override.
|
|
38
|
+
|
|
39
|
+
**Fallback (no MCP):** ask the user to paste the relevant `.tscn` snippet or describe the parent of the actor.
|
|
40
|
+
|
|
41
|
+
### 3. Pick the recipe and propose it explicitly
|
|
42
|
+
|
|
43
|
+
Each recipe is a tight bundle of (node + property + signal + script). Don't mix recipes — add one, see it land, stack later.
|
|
44
|
+
|
|
45
|
+
> I want to add a hit flash + 0.04s camera shake bound to the `damaged` signal on `./World/Enemy`. About 6 lines of GDScript and one Camera3D shake node. May I proceed?
|
|
46
|
+
|
|
47
|
+
### 4. Apply the recipe
|
|
48
|
+
|
|
49
|
+
See **Recipes** below. Each is end-to-end: node tree, property values, signal hookup, GDScript stub.
|
|
50
|
+
|
|
51
|
+
### 5. Verify
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
summer_save_scene
|
|
55
|
+
summer_get_script_errors
|
|
56
|
+
summer_play
|
|
57
|
+
# user reproduces the trigger
|
|
58
|
+
summer_stop
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
If it doesn't read on screen, the values are wrong (flash too dim, shake too short, particle lifetime too long). Tune one knob at a time.
|
|
62
|
+
|
|
63
|
+
## Recipes
|
|
64
|
+
|
|
65
|
+
### Recipe A — Hit flash (mesh albedo flicker)
|
|
66
|
+
|
|
67
|
+
**Goal:** when an enemy takes damage, the mesh flashes white for 80 ms. Single biggest-bang-for-buck VFX. Read instantly even at 60 fps.
|
|
68
|
+
|
|
69
|
+
**Approach:** swap to a flash material via `material_override`, then back. Use Tween for the timing.
|
|
70
|
+
|
|
71
|
+
**MCP path:**
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
summer_add_node(parent="./World/Enemy", type="Node", name="HitFlash")
|
|
75
|
+
# attach script (see below) — script holds reference to MeshInstance3D and a flash StandardMaterial3D
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Script (`scripts/hit_flash.gd`):
|
|
79
|
+
|
|
80
|
+
```gdscript
|
|
81
|
+
class_name HitFlash
|
|
82
|
+
extends Node
|
|
83
|
+
|
|
84
|
+
@export var mesh: MeshInstance3D
|
|
85
|
+
@export var flash_color: Color = Color(1.0, 1.0, 1.0)
|
|
86
|
+
@export var duration: float = 0.08
|
|
87
|
+
|
|
88
|
+
var _flash_mat: StandardMaterial3D
|
|
89
|
+
var _orig_mat: Material
|
|
90
|
+
|
|
91
|
+
func _ready() -> void:
|
|
92
|
+
if mesh == null:
|
|
93
|
+
push_warning("HitFlash: mesh export not assigned")
|
|
94
|
+
return
|
|
95
|
+
_orig_mat = mesh.get_surface_override_material(0)
|
|
96
|
+
_flash_mat = StandardMaterial3D.new()
|
|
97
|
+
_flash_mat.albedo_color = flash_color
|
|
98
|
+
_flash_mat.emission_enabled = true
|
|
99
|
+
_flash_mat.emission = flash_color
|
|
100
|
+
_flash_mat.emission_energy_multiplier = 2.0
|
|
101
|
+
|
|
102
|
+
func flash() -> void:
|
|
103
|
+
if mesh == null:
|
|
104
|
+
return
|
|
105
|
+
mesh.set_surface_override_material(0, _flash_mat)
|
|
106
|
+
var tween := create_tween()
|
|
107
|
+
tween.tween_interval(duration)
|
|
108
|
+
tween.tween_callback(func(): mesh.set_surface_override_material(0, _orig_mat))
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Wire the trigger:
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
summer_connect_signal(from="./World/Enemy", signal="damaged", to="./World/Enemy/HitFlash", method="flash")
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**Tunable knobs:** `duration` (0.06–0.12 sec — under 0.06 reads as a flicker, over 0.12 looks slow), `emission_energy_multiplier` (1.5–3.0).
|
|
118
|
+
|
|
119
|
+
> CRITICAL: do NOT inline the StandardMaterial3D as a sub_resource via `summer_set_prop`. Build it in script (as above) or save it to `materials/flash.tres` and load. Inline sub_resources break `summer_set_resource_property` silently — see `_shared/mcp-tools-reference.md` § "Trap".
|
|
120
|
+
|
|
121
|
+
### Recipe B — Screen shake (Camera3D wobble)
|
|
122
|
+
|
|
123
|
+
**Goal:** punchy 40–80 ms camera shake on impact. Ties hits to the player's body.
|
|
124
|
+
|
|
125
|
+
**Approach:** attach a shake script to the existing Camera3D. Don't spawn a new camera; reuse the player's.
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
summer_inspect_node "./World/Player/Camera3D" # confirm the path
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Script (`scripts/camera_shake.gd`):
|
|
132
|
+
|
|
133
|
+
```gdscript
|
|
134
|
+
class_name CameraShake
|
|
135
|
+
extends Camera3D
|
|
136
|
+
|
|
137
|
+
@export var trauma_decay: float = 5.0
|
|
138
|
+
@export var max_offset: Vector2 = Vector2(0.15, 0.15)
|
|
139
|
+
@export var max_roll: float = 0.05
|
|
140
|
+
|
|
141
|
+
var _trauma: float = 0.0
|
|
142
|
+
var _base_position: Vector3
|
|
143
|
+
|
|
144
|
+
func _ready() -> void:
|
|
145
|
+
_base_position = position
|
|
146
|
+
|
|
147
|
+
func add_trauma(amount: float) -> void:
|
|
148
|
+
_trauma = clamp(_trauma + amount, 0.0, 1.0)
|
|
149
|
+
|
|
150
|
+
func _process(delta: float) -> void:
|
|
151
|
+
if _trauma <= 0.0:
|
|
152
|
+
position = _base_position
|
|
153
|
+
rotation.z = 0.0
|
|
154
|
+
return
|
|
155
|
+
var shake := _trauma * _trauma # quadratic — feels punchy, not noisy
|
|
156
|
+
position = _base_position + Vector3(
|
|
157
|
+
randf_range(-max_offset.x, max_offset.x) * shake,
|
|
158
|
+
randf_range(-max_offset.y, max_offset.y) * shake,
|
|
159
|
+
0.0
|
|
160
|
+
)
|
|
161
|
+
rotation.z = randf_range(-max_roll, max_roll) * shake
|
|
162
|
+
_trauma = max(_trauma - trauma_decay * delta, 0.0)
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Trigger from the player or any damage handler:
|
|
166
|
+
|
|
167
|
+
```gdscript
|
|
168
|
+
$Camera3D.add_trauma(0.5) # 0.3 light hit, 0.5 normal, 0.8 heavy
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**Tunable knobs:** `max_offset` (0.05 subtle, 0.2 punchy, 0.4 cinematic), `trauma_decay` (3 long, 5 standard, 8 snappy). Quadratic falloff is the trick — linear feels twitchy.
|
|
172
|
+
|
|
173
|
+
### Recipe C — Pickup sparkle (GPUParticles3D one-shot)
|
|
174
|
+
|
|
175
|
+
**Goal:** when a pickup is collected, emit a quick burst of particles where it disappeared.
|
|
176
|
+
|
|
177
|
+
**Approach:** GPUParticles3D set to `one_shot = true`, `emitting = true` triggered from script. Particle material **must** be a standalone `.tres` (inline sub_resource trap).
|
|
178
|
+
|
|
179
|
+
```
|
|
180
|
+
summer_add_node(parent="./World/Pickup", type="GPUParticles3D", name="Sparkle")
|
|
181
|
+
summer_set_prop(path="./World/Pickup/Sparkle", key="one_shot", value="true")
|
|
182
|
+
summer_set_prop(path="./World/Pickup/Sparkle", key="amount", value="24")
|
|
183
|
+
summer_set_prop(path="./World/Pickup/Sparkle", key="lifetime", value="0.5")
|
|
184
|
+
summer_set_prop(path="./World/Pickup/Sparkle", key="explosiveness", value="0.95")
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
For the `process_material`, save a `ParticleProcessMaterial.tres` first (see `asset-pipeline/asset-strategy/SKILL.md` for the pattern), then:
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
summer_set_prop(path="./World/Pickup/Sparkle", key="process_material", value="res://materials/sparkle_particles.tres")
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
In the pickup's collection handler:
|
|
194
|
+
|
|
195
|
+
```gdscript
|
|
196
|
+
func _on_picked_up() -> void:
|
|
197
|
+
$Sparkle.restart()
|
|
198
|
+
$Sparkle.emitting = true
|
|
199
|
+
$Mesh.visible = false
|
|
200
|
+
await get_tree().create_timer(0.6).timeout
|
|
201
|
+
queue_free()
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
`explosiveness = 0.95` is the difference between a continuous trickle and a satisfying *poof*. `0.0` = constant emission, `1.0` = all particles spawn at frame zero.
|
|
205
|
+
|
|
206
|
+
### Recipe D — Muzzle flash (light + sprite + sound)
|
|
207
|
+
|
|
208
|
+
**Goal:** when a weapon fires, a brief OmniLight3D flash + Sprite3D billboard + audio cue. Sells the shot more than the projectile does.
|
|
209
|
+
|
|
210
|
+
```
|
|
211
|
+
summer_add_node(parent="./World/Player/Weapon", type="OmniLight3D", name="MuzzleLight")
|
|
212
|
+
summer_set_prop(path="./World/Player/Weapon/MuzzleLight", key="light_color", value="Color(1.0, 0.85, 0.4)")
|
|
213
|
+
summer_set_prop(path="./World/Player/Weapon/MuzzleLight", key="light_energy", value="0.0")
|
|
214
|
+
summer_set_prop(path="./World/Player/Weapon/MuzzleLight", key="omni_range", value="2.5")
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Trigger from the fire function:
|
|
218
|
+
|
|
219
|
+
```gdscript
|
|
220
|
+
func fire() -> void:
|
|
221
|
+
var light: OmniLight3D = $MuzzleLight
|
|
222
|
+
light.light_energy = 4.0
|
|
223
|
+
var tween := create_tween()
|
|
224
|
+
tween.tween_property(light, "light_energy", 0.0, 0.06)
|
|
225
|
+
$MuzzleSound.play()
|
|
226
|
+
$Camera3D.add_trauma(0.3) # if camera shake recipe applied
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Do NOT keep the light always-on at low energy — that costs a real-time light slot. Tween from 4.0 to 0.0 over 60 ms.
|
|
230
|
+
|
|
231
|
+
### Recipe E — Explosion (5-element stack)
|
|
232
|
+
|
|
233
|
+
**Goal:** one Big Boom, all senses. Stack five small effects on the same trigger.
|
|
234
|
+
|
|
235
|
+
1. **Light flash:** OmniLight3D, `light_energy` 0 → 8 → 0 over 200 ms
|
|
236
|
+
2. **Particles:** GPUParticles3D one-shot, `amount` 64, `lifetime` 0.8, `explosiveness` 1.0
|
|
237
|
+
3. **Camera shake:** `add_trauma(0.8)`
|
|
238
|
+
4. **Audio:** AudioStreamPlayer3D, layered explosion
|
|
239
|
+
5. **Hit-stop:** `Engine.time_scale = 0.05` for 60 ms, then 1.0
|
|
240
|
+
|
|
241
|
+
Hit-stop is the secret weapon. 60 ms of 0.05 time scale = the explosion lands. Use sparingly; once per "big" event. Reset with:
|
|
242
|
+
|
|
243
|
+
```gdscript
|
|
244
|
+
Engine.time_scale = 0.05
|
|
245
|
+
await get_tree().create_timer(0.06).timeout
|
|
246
|
+
Engine.time_scale = 1.0
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## Anti-patterns
|
|
250
|
+
|
|
251
|
+
| Don't | Do | Why |
|
|
252
|
+
|---|---|---|
|
|
253
|
+
| Inline sub_resource for ParticleProcessMaterial via `summer_set_prop` | Save standalone `.tres`, reference by path | `summer_set_resource_property` silently fails on inline sub_resources |
|
|
254
|
+
| Hit flash for 0.3 sec | Hit flash for 0.06–0.12 sec | Anything over ~120 ms reads as a "stay" not a flash |
|
|
255
|
+
| Camera shake with linear falloff | Quadratic falloff (`_trauma * _trauma`) | Linear feels jittery; quadratic feels weighty |
|
|
256
|
+
| `OmniLight3D` always on at low energy | Tween from off → on → off in 60–80 ms | Real-time lights have hard count limits per frame |
|
|
257
|
+
| GPUParticles3D `one_shot = false` for pickups | `one_shot = true`, `restart()` to fire | Otherwise particles trickle continuously and look wrong |
|
|
258
|
+
| Tween every property at once | Tween one or two; let the others snap | Over-animated polish reads as noise |
|
|
259
|
+
| `Engine.time_scale` < 0.1 for > 100 ms | `time_scale = 0.05` for 40–80 ms | Longer hit-stop feels broken, not satisfying |
|
|
260
|
+
| Particles with `lifetime > 1.0` for hits | `lifetime = 0.3–0.6` | Hit feedback should resolve before the next swing |
|
|
261
|
+
|
|
262
|
+
## Collaborative protocol
|
|
263
|
+
|
|
264
|
+
This skill writes scripts and adds nodes. Always ask before each recipe is applied. Group related writes into one ask: "I'm about to add HitFlash + CameraShake + connect 2 signals. OK?" See `_shared/collaborative-protocol.md`.
|
|
265
|
+
|
|
266
|
+
## Want a working starter?
|
|
267
|
+
|
|
268
|
+
No template — this is a workflow that bolts onto any existing scene. For a project that already has these recipes pre-wired, see `template-3d-fps` (hit flash + muzzle flash) and `template-3d-platformer` (pickup sparkle).
|
|
269
|
+
|
|
270
|
+
## See also
|
|
271
|
+
|
|
272
|
+
- `_shared/mcp-tools-reference.md` — full MCP tool list, especially the inline-sub_resource trap
|
|
273
|
+
- `_shared/godot-version.md` — Godot 4.5 API notes
|
|
274
|
+
- `_shared/collaborative-protocol.md` — "May I write" pattern
|
|
275
|
+
- `_shared/gd-style.md` — typed GDScript conventions
|
|
276
|
+
- `post-processing/screen-shake/SKILL.md` — deeper camera-shake techniques (Perlin noise, trauma stacks)
|
|
277
|
+
- `visual-effects/gpuparticles-3d-basics/SKILL.md` — particle setup details
|
|
278
|
+
- `visual-effects/hit-impact-flashes/SKILL.md` — flash material variations
|
|
279
|
+
- `visual-effects/muzzle-flashes/SKILL.md` — muzzle flash deep dive
|
|
280
|
+
- `audio/sfx-pipeline/SKILL.md` — pairing audio with VFX
|