summer-engine 2.7.0 → 2.8.1
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 +2 -1
- package/.codex-plugin/plugin.json +3 -2
- package/.cursor-plugin/plugin.json +2 -1
- package/.opencode/INSTALL.md +1 -1
- package/.opencode/plugins/summer.js +1 -1
- package/AGENTS.md +4 -4
- package/CHANGELOG.md +28 -0
- package/GEMINI.md +5 -4
- package/README.md +58 -21
- package/assets/autopilot/README.md +51 -0
- package/assets/autopilot/autopilot.gd +222 -0
- package/assets/autopilot/probe_base.gd +262 -0
- package/assets/autopilot/run.sh +78 -0
- package/commands/gameskill.md +11 -2
- package/dist/bin/summer.js +8 -0
- package/dist/commands/config.d.ts +2 -0
- package/dist/commands/config.js +53 -0
- package/dist/commands/create.d.ts +1 -0
- package/dist/commands/create.js +38 -6
- package/dist/commands/list.js +1 -1
- package/dist/commands/login.d.ts +19 -0
- package/dist/commands/login.js +136 -37
- package/dist/commands/logout.js +3 -16
- package/dist/commands/logs.d.ts +2 -0
- package/dist/commands/logs.js +19 -0
- package/dist/commands/mcp.js +7 -2
- package/dist/commands/open.js +1 -1
- package/dist/commands/publish.d.ts +2 -0
- package/dist/commands/publish.js +20 -0
- package/dist/commands/releases.d.ts +2 -0
- package/dist/commands/releases.js +20 -0
- package/dist/commands/run.js +1 -1
- package/dist/lib/api-client.d.ts +27 -8
- package/dist/lib/api-client.js +65 -14
- package/dist/lib/auth.d.ts +40 -12
- package/dist/lib/auth.js +173 -37
- package/dist/lib/cloud/engine-bridge.d.ts +17 -4
- package/dist/lib/cloud/engine-bridge.js +32 -6
- package/dist/lib/config.d.ts +20 -0
- package/dist/lib/config.js +142 -0
- package/dist/lib/creator.d.ts +93 -0
- package/dist/lib/creator.js +393 -0
- package/dist/lib/engine.d.ts +35 -2
- package/dist/lib/engine.js +240 -6
- package/dist/lib/public-surface-paths.d.ts +13 -0
- package/dist/lib/public-surface-paths.js +42 -0
- package/dist/lib/skills-registry.d.ts +8 -0
- package/dist/lib/skills-registry.js +9 -0
- package/dist/lib/store.d.ts +14 -0
- package/dist/lib/store.js +145 -0
- package/dist/lib/summer-compatibility.d.ts +6 -0
- package/dist/lib/summer-compatibility.js +22 -0
- package/dist/mcp/server.d.ts +8 -1
- package/dist/mcp/server.js +25 -4
- package/dist/mcp/tools/creator-tools.d.ts +2 -0
- package/dist/mcp/tools/creator-tools.js +105 -0
- package/dist/mcp/tools/debug-tools.d.ts +12 -0
- package/dist/mcp/tools/debug-tools.js +100 -1
- package/dist/mcp/tools/generate-tools.js +16 -6
- package/dist/mcp/tools/project-tools.js +131 -16
- package/dist/mcp/tools/scene-tools.js +166 -66
- package/dist/mcp/tools/visual-tools.js +32 -8
- package/dist/mcp/tools/with-engine.js +51 -19
- package/hooks/pre-commit-doctor.sh +0 -0
- package/hooks/run-hook.cmd +0 -0
- package/hooks/session-start.sh +5 -5
- package/package.json +3 -1
- package/references/collaborative-protocol.md +50 -0
- package/references/gd-style.md +121 -0
- package/references/godot-version.md +51 -0
- package/references/mcp-tools-reference.md +199 -0
- package/references/public-surface-language.md +30 -0
- package/references/summer-folder.md +90 -0
- package/references/template-registry.md +73 -0
- package/skills/2d-assets/character-portrait/SKILL.md +17 -12
- package/skills/2d-assets/concept-art/SKILL.md +10 -9
- package/skills/2d-assets/pixel-art/SKILL.md +36 -20
- package/skills/2d-assets/skybox-panorama/SKILL.md +32 -37
- package/skills/2d-assets/sprite-sheet/SKILL.md +40 -35
- package/skills/2d-assets/tileable-texture/SKILL.md +28 -22
- package/skills/2d-assets/ui-graphics/SKILL.md +46 -32
- package/skills/2d-assets/use-widget-asset/SKILL.md +8 -6
- package/skills/3d-assets/character-model/SKILL.md +34 -19
- package/skills/3d-assets/environment-kit/SKILL.md +11 -5
- package/skills/3d-assets/organic-model/SKILL.md +8 -5
- package/skills/3d-assets/prop-model/SKILL.md +9 -3
- package/skills/3d-assets/vehicle-model/SKILL.md +32 -19
- package/skills/README.md +1 -1
- package/skills/ai-and-npcs/design-npc/SKILL.md +31 -13
- package/skills/animation/animation-tree/SKILL.md +39 -15
- package/skills/animation/facial-and-lipsync/SKILL.md +25 -8
- package/skills/animation/generate-motion/SKILL.md +47 -15
- package/skills/animation/procedural-animation/SKILL.md +31 -13
- package/skills/animation/retarget/SKILL.md +127 -79
- package/skills/audio/adaptive-music/SKILL.md +24 -20
- package/skills/audio/ambient-bed/SKILL.md +22 -16
- package/skills/audio/audio-direction/SKILL.md +15 -10
- package/skills/audio/music-track/SKILL.md +18 -12
- package/skills/audio/sound-effect/SKILL.md +17 -12
- package/skills/audio/voice-line/SKILL.md +42 -42
- package/skills/character-controllers/fps-controller/SKILL.md +26 -16
- package/skills/debugging/debug/SKILL.md +52 -20
- package/skills/deployment/export-and-ship/SKILL.md +105 -38
- package/skills/deployment/remote-deploy/SKILL.md +56 -23
- package/skills/gameplay-mechanics/auto-fire-targeting/SKILL.md +26 -1
- package/skills/gameplay-mechanics/design-mechanic/SKILL.md +23 -15
- package/skills/level-design/design-level/SKILL.md +22 -8
- package/skills/level-design/scene-to-level/SKILL.md +13 -7
- package/skills/multiplayer-and-networking/host-authoritative-state/SKILL.md +8 -8
- package/skills/multiplayer-and-networking/peer-to-peer-multiplayer/SKILL.md +2 -7
- package/skills/multiplayer-and-networking/setup-multiplayer/SKILL.md +26 -18
- package/skills/performance/tune-performance/SKILL.md +51 -32
- package/skills/rendering-and-lighting/3d-lighting/SKILL.md +34 -5
- package/skills/rendering-and-lighting/art-direction/SKILL.md +6 -8
- package/skills/scene-and-project/brainstorm-game/SKILL.md +3 -5
- package/skills/scene-and-project/make-game/SKILL.md +2 -1
- package/skills/scene-and-project/play/SKILL.md +10 -4
- package/skills/scene-and-project/scene-composition/SKILL.md +21 -8
- package/skills/scene-and-project/summer-cloud/SKILL.md +1 -1
- package/skills/ui-and-ux/ui-basics/SKILL.md +16 -7
- package/skills/video/animated-loop/SKILL.md +63 -39
- package/skills/video/cinematic-cutscene/SKILL.md +66 -30
- package/skills/video/trailer-shot/SKILL.md +30 -21
- package/skills/visual-effects/_building-blocks/additive-billboard-particles.md +25 -16
- package/skills/visual-effects/_building-blocks/trauma-shake-snippet.md +5 -3
- package/skills/visual-effects/game-feel/SKILL.md +51 -33
- package/skills/visual-effects/recipes/dissolve/SKILL.md +51 -9
- package/skills/visual-effects/recipes/fire/SKILL.md +136 -49
- package/skills/visual-effects/recipes/hit-spark/SKILL.md +45 -8
- package/skills/visual-effects/recipes/lightning/SKILL.md +59 -27
- package/skills/visual-effects/recipes/magic-glow/SKILL.md +59 -26
- package/skills/visual-effects/recipes/muzzle-flash/SKILL.md +68 -25
- package/skills/visual-effects/recipes/smoke/SKILL.md +89 -28
- package/skills/visual-effects/recipes/water-ripple/SKILL.md +83 -23
- package/skills/workflow/brainstorming/SKILL.md +1 -1
- package/skills/workflow/brainstorming/scripts/start-server.sh +0 -0
- package/skills/workflow/brainstorming/scripts/stop-server.sh +0 -0
- package/skills/workflow/debugging-game-feel/SKILL.md +29 -9
- package/skills/workflow/diagnosing-perf-regressions/SKILL.md +46 -21
- package/skills/workflow/gameskill/SKILL.md +11 -2
- package/skills/workflow/headless-scripting/SKILL.md +407 -0
- package/skills/workflow/investigating-bugs/SKILL.md +4 -2
- package/skills/workflow/investigating-bugs/find-polluter.sh +0 -0
- package/skills/workflow/mcpupdate/SKILL.md +3 -3
- package/skills/workflow/playtesting-a-feature/SKILL.md +94 -21
- package/skills/workflow/skill-create/SKILL.md +7 -7
- package/skills/workflow/skill-test/SKILL.md +3 -3
- package/skills/workflow/using-summer/SKILL.md +12 -2
- package/skills/workflow/verification-before-completion/SKILL.md +2 -0
- package/skills/workflow/writing-plans/SKILL.md +7 -7
- package/skills/workflow/writing-skills/SKILL.md +7 -5
- package/skills/workflow/writing-skills/render-graphs.js +0 -0
- package/skills/workflow/writing-skills/testing-skills-with-subagents.md +1 -1
|
@@ -1,28 +1,37 @@
|
|
|
1
1
|
# Additive Billboard Particles — Canonical Material Settings
|
|
2
2
|
|
|
3
|
-
Reusable
|
|
3
|
+
Reusable material configuration for additive particle billboards (motes, sparks, glow, ash). Used by `magic-glow`, optionally substitutable in `hit-spark` and `fire` if a custom shader is overkill.
|
|
4
|
+
|
|
5
|
+
`BaseMaterial3D` is abstract — `BaseMaterial3D.new()` is a parse error ("Native class
|
|
6
|
+
"BaseMaterial3D" cannot be constructed as it is abstract"). Instantiate
|
|
7
|
+
`StandardMaterial3D`; the `BaseMaterial3D.*` enum constants below are still the right
|
|
8
|
+
way to name the values.
|
|
4
9
|
|
|
5
10
|
## Settings
|
|
6
11
|
|
|
7
|
-
```
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
```gdscript
|
|
13
|
+
var bm := StandardMaterial3D.new()
|
|
14
|
+
bm.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
|
15
|
+
bm.blend_mode = BaseMaterial3D.BLEND_MODE_ADD
|
|
16
|
+
bm.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
|
17
|
+
bm.billboard_mode = BaseMaterial3D.BILLBOARD_PARTICLES
|
|
18
|
+
bm.emission_enabled = true
|
|
19
|
+
bm.emission_energy_multiplier = 3.0 # 1.0–5.0 typical; bump for HDR bloom
|
|
20
|
+
bm.disable_receive_shadows = true
|
|
21
|
+
bm.shadow_to_opacity = false
|
|
22
|
+
bm.no_depth_test = false # true only if particles must read on TOP of opaque geometry
|
|
23
|
+
bm.billboard_keep_scale = true # BILLBOARD_PARTICLES drops per-particle scale without this
|
|
24
|
+
bm.albedo_color = Color(R, G, B, 1.0)
|
|
25
|
+
bm.emission = Color(R, G, B) # usually same as albedo for single-color motes
|
|
19
26
|
```
|
|
20
27
|
|
|
21
28
|
## When to use this vs a custom shader
|
|
22
29
|
|
|
23
|
-
- **Use this `
|
|
24
|
-
- **
|
|
30
|
+
- **Use this `StandardMaterial3D` config** when the effect is just a tinted soft-circle mote (magic glow, sparkles, dust). Less code, faster iteration, free Godot-managed billboard math.
|
|
31
|
+
- **Per-particle color over age does NOT need a custom shader.** `ParticleProcessMaterial` has `color_ramp`, `color_initial_ramp`, `alpha_curve` and `emission_curve` (all verified present in 4.6) — those drive the particle's `COLOR` from age and feed straight into a plain `StandardMaterial3D`. Reach for a ramp before reaching for a shader.
|
|
32
|
+
- **Use a custom `ShaderMaterial`** when you need per-fragment shape masks (muzzle flash star burst), noise-distorted UVs (fire, smoke), or anything that varies *within* the quad. Those are what `StandardMaterial3D` genuinely can't express.
|
|
33
|
+
- If a custom shader does need the particle age, note that the age lives in `INSTANCE_CUSTOM.y` (normalized by `INSTANCE_CUSTOM.w`, the lifetime), that `INSTANCE_CUSTOM.x` is the rotation angle — not the age — and that `INSTANCE_CUSTOM` is **vertex-stage only**. Pass it to `fragment()` through a `varying`.
|
|
25
34
|
|
|
26
35
|
## Performance note
|
|
27
36
|
|
|
28
|
-
Additive overdraw is fillrate-bound.
|
|
37
|
+
Additive overdraw is fillrate-bound. `draw_pass_1` is a `Mesh`, so the size dial is on the mesh: keep `QuadMesh.size` at or under `Vector2(0.2, 0.2)` for motes/sparks. Larger quads at full additive intensity destroy mobile fillrate budgets fast.
|
|
@@ -22,8 +22,7 @@ func _process(delta: float) -> void:
|
|
|
22
22
|
if camera == null:
|
|
23
23
|
return
|
|
24
24
|
if trauma > 0.0:
|
|
25
|
-
var s
|
|
26
|
-
var t = Time.get_ticks_msec() * 0.01
|
|
25
|
+
var s := trauma * trauma # perceived intensity grows quadratically
|
|
27
26
|
camera.h_offset = max_offset.x * s * (randf() * 2.0 - 1.0)
|
|
28
27
|
camera.v_offset = max_offset.y * s * (randf() * 2.0 - 1.0)
|
|
29
28
|
camera.rotation.x += max_pitch * s * (randf() * 2.0 - 1.0) * 0.1
|
|
@@ -37,7 +36,10 @@ func add_trauma(amount: float) -> void:
|
|
|
37
36
|
trauma = clamp(trauma + amount, 0.0, 1.0)
|
|
38
37
|
```
|
|
39
38
|
|
|
40
|
-
Register `CameraShake` as autoload in Project Settings.
|
|
39
|
+
Register `CameraShake` as autoload in Project Settings. An autoload is a node at
|
|
40
|
+
`/root/CameraShake`, not an engine singleton — reach it with
|
|
41
|
+
`get_node_or_null("/root/CameraShake")`. `Engine.has_singleton("CameraShake")` is
|
|
42
|
+
always `false` for an autoload (measured), so never gate the call on it.
|
|
41
43
|
|
|
42
44
|
## Usage
|
|
43
45
|
|
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: game-feel
|
|
3
|
-
description: Use when
|
|
3
|
+
description: Use when a Summer game "feels flat", "lacks impact", "needs juice", or "needs punch", or the user asks for hit feedback, screen shake, camera shake, audio ducking, or general game feel. Walks through Summer Engine's hit-flash, trauma camera shake, and audio-ducking stack, wired so one hit fires all three. Trigger on "vfx", "juice", "punch", "feels flat", "game feel", "hit flash", "screen shake", "camera shake", "ducking".
|
|
4
4
|
license: MIT
|
|
5
5
|
compatibility: [Cursor, Claude Code, Windsurf, Codex]
|
|
6
6
|
category: visual-effects
|
|
7
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
|
|
8
|
+
allowed-tools: Read Grep Edit Write summer_write_file summer_read_file summer_get_scene_tree summer_inspect_node summer_inspect_resource summer_add_node summer_set_prop summer_set_resource_property summer_connect_signal summer_project_setting summer_save_scene summer_get_script_errors summer_get_debugger_errors summer_play summer_stop
|
|
9
9
|
paths: ["**/*.gd", "**/*.tscn", "**/*.tres"]
|
|
10
10
|
---
|
|
11
11
|
|
|
12
12
|
# /vfx — The Trio That Makes Games Feel Punchy
|
|
13
13
|
|
|
14
|
-
Particles
|
|
14
|
+
Particles do not make a game feel good. Screen shake alone does not either.
|
|
15
|
+
What makes a hit land is three systems firing on the same frame: a sub-frame
|
|
16
|
+
**hit flash** on the body that got hit, **trauma-based camera shake** with
|
|
17
|
+
quadratic falloff so big hits saturate, and **audio ducking** that briefly
|
|
18
|
+
squashes Music/Ambient so the impact SFX punches through. This skill installs
|
|
19
|
+
the canonical Summer Engine stack as three small systems wired to one signal.
|
|
15
20
|
|
|
16
21
|
## Step 1 — Ask what feels flat
|
|
17
22
|
|
|
@@ -62,13 +67,15 @@ World/
|
|
|
62
67
|
HitFlash (Node) # holds the script, no transform
|
|
63
68
|
```
|
|
64
69
|
|
|
65
|
-
**MCP path
|
|
70
|
+
**MCP path** (every scene-mutating call takes an explicit `scenePath`):
|
|
66
71
|
|
|
67
72
|
```
|
|
68
|
-
summer_add_node(parent="./World/Enemy", type="Node", name="HitFlash")
|
|
73
|
+
summer_add_node(scenePath="res://levels/world.tscn", parent="./World/Enemy", type="Node", name="HitFlash")
|
|
74
|
+
summer_set_prop(scenePath="res://levels/world.tscn", path="./World/Enemy/HitFlash", key="script", value="res://scripts/vfx/hit_flash.gd")
|
|
69
75
|
```
|
|
70
76
|
|
|
71
|
-
Then write `scripts/vfx/hit_flash.gd
|
|
77
|
+
Then write `scripts/vfx/hit_flash.gd` (via `summer_write_file`, which requires exactly
|
|
78
|
+
one of `create_only` or `expected_sha256`):
|
|
72
79
|
|
|
73
80
|
```gdscript
|
|
74
81
|
class_name HitFlash
|
|
@@ -110,7 +117,7 @@ func flash() -> void:
|
|
|
110
117
|
|
|
111
118
|
**Tunable knobs:** `duration` 0.06–0.12s — under 0.06 reads as a flicker, over 0.12 looks slow. `emission_energy_multiplier` 1.5–3.0.
|
|
112
119
|
|
|
113
|
-
> CRITICAL: do NOT inline a `StandardMaterial3D` as a `sub_resource` via `summer_set_prop`. Build it in script (as above) or save it to `materials/flash.tres`. Inline sub_resources break `summer_set_resource_property` silently — see
|
|
120
|
+
> CRITICAL: do NOT inline a `StandardMaterial3D` as a `sub_resource` via `summer_set_prop`. Build it in script (as above) or save it to `materials/flash.tres`. Inline sub_resources break `summer_set_resource_property` silently — see `../../../references/mcp-tools-reference.md` § "Trap".
|
|
114
121
|
|
|
115
122
|
## Step 5 — Install Section 2 (Trauma Camera Shake)
|
|
116
123
|
|
|
@@ -121,8 +128,8 @@ func flash() -> void:
|
|
|
121
128
|
**Node tree:** the script attaches to the existing `./World/Player/Camera3D`. No new nodes.
|
|
122
129
|
|
|
123
130
|
```
|
|
124
|
-
summer_inspect_node
|
|
125
|
-
|
|
131
|
+
summer_inspect_node(path="./World/Player/Camera3D")
|
|
132
|
+
summer_set_prop(scenePath="res://levels/world.tscn", path="./World/Player/Camera3D", key="script", value="res://scripts/vfx/camera_shake.gd")
|
|
126
133
|
```
|
|
127
134
|
|
|
128
135
|
`scripts/vfx/camera_shake.gd`:
|
|
@@ -138,6 +145,11 @@ extends Camera3D
|
|
|
138
145
|
var _trauma: float = 0.0
|
|
139
146
|
var _shake_offset: Vector3 = Vector3.ZERO
|
|
140
147
|
|
|
148
|
+
func _ready() -> void:
|
|
149
|
+
# Node has no `groups` property, so there is nothing to set from the outside.
|
|
150
|
+
# Joining the group here is how any actor finds this camera later.
|
|
151
|
+
add_to_group(&"player_camera")
|
|
152
|
+
|
|
141
153
|
func add_trauma(amount: float) -> void:
|
|
142
154
|
_trauma = clamp(_trauma + amount, 0.0, 1.0)
|
|
143
155
|
|
|
@@ -236,12 +248,9 @@ func duck(amount: float, duration: float = 0.5) -> void:
|
|
|
236
248
|
var drop_db := -24.0 * amount * weight
|
|
237
249
|
var target_db := base_db + drop_db
|
|
238
250
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
# NOTE: AudioServer doesn't expose bus volume as an animatable
|
|
243
|
-
# property — use a method tween instead. See actual call below.
|
|
244
|
-
# (The simpler, real implementation:)
|
|
251
|
+
# AudioServer exposes bus volume through a method, not an animatable
|
|
252
|
+
# property, so tween_method is the only form that works here.
|
|
253
|
+
# `tween_property(AudioServer, "bus_volume_db", ...)` errors at runtime.
|
|
245
254
|
_tween.tween_method(_set_bus_db.bind(idx), base_db, target_db, attack)\
|
|
246
255
|
.set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
|
|
247
256
|
_tween.tween_method(_set_bus_db.bind(idx), target_db, base_db, release)\
|
|
@@ -255,9 +264,12 @@ func _set_bus_db(idx: int, db: float) -> void:
|
|
|
255
264
|
Register it as an autoload:
|
|
256
265
|
|
|
257
266
|
```
|
|
258
|
-
|
|
267
|
+
summer_project_setting(...) # autoload/AudioDucker = "*res://scripts/vfx/audio_ducker.gd"
|
|
259
268
|
```
|
|
260
269
|
|
|
270
|
+
(Equivalently, by hand: Project Settings → Autoload → Add `scripts/vfx/audio_ducker.gd`
|
|
271
|
+
as `AudioDucker`.)
|
|
272
|
+
|
|
261
273
|
**Calling it:**
|
|
262
274
|
|
|
263
275
|
```gdscript
|
|
@@ -290,25 +302,27 @@ func _on_damaged(amount: float) -> void:
|
|
|
290
302
|
# 1. Hit flash on this body.
|
|
291
303
|
_hit_flash.flash()
|
|
292
304
|
# 2. Camera shake on the player's camera, scaled by hit weight.
|
|
293
|
-
|
|
305
|
+
# clampf(), not clamp(): clamp() returns Variant, and inferring a variable from
|
|
306
|
+
# a Variant is an error under Godot's default warning settings.
|
|
307
|
+
var trauma := clampf(0.2 + amount * 0.05, 0.2, 0.9)
|
|
294
308
|
if _player_camera:
|
|
295
309
|
_player_camera.add_trauma(trauma)
|
|
296
310
|
# 3. Audio duck, scaled by hit weight.
|
|
297
|
-
var duck_amount :=
|
|
311
|
+
var duck_amount := clampf(0.3 + amount * 0.04, 0.3, 1.0)
|
|
298
312
|
AudioDucker.duck(duck_amount, 0.45)
|
|
299
313
|
```
|
|
300
314
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
```
|
|
315
|
+
The camera joins `player_camera` from its own `_ready()` (Step 5). `Node` has no
|
|
316
|
+
`groups` property, so `summer_set_prop(key="groups", ...)` cannot do this — and
|
|
317
|
+
`summer_set_prop` only accepts a string, number, or boolean anyway, never an array.
|
|
318
|
+
The two ways that do work are the `add_to_group()` call in `_ready()` or a `groups =
|
|
319
|
+
["player_camera"]` line written into the `.tscn` node header.
|
|
307
320
|
|
|
308
|
-
Or wire the signal explicitly via MCP if you prefer
|
|
321
|
+
Or wire the signal explicitly via MCP if you prefer (`emitter`/`receiver`, not
|
|
322
|
+
`from`/`to`, and `scenePath` is required):
|
|
309
323
|
|
|
310
324
|
```
|
|
311
|
-
summer_connect_signal(
|
|
325
|
+
summer_connect_signal(scenePath="res://levels/world.tscn", emitter="./World/Enemy", signal="damaged", receiver="./World/Enemy/HitFlash", method="flash")
|
|
312
326
|
```
|
|
313
327
|
|
|
314
328
|
## Step 8 — Scaling intensity per hit weight
|
|
@@ -328,10 +342,11 @@ Because trauma is squared inside the camera, the *felt* difference between 0.35
|
|
|
328
342
|
## Step 9 — Verify
|
|
329
343
|
|
|
330
344
|
```
|
|
331
|
-
summer_save_scene
|
|
332
|
-
summer_get_script_errors
|
|
345
|
+
summer_save_scene(scenePath="res://levels/world.tscn")
|
|
346
|
+
summer_get_script_errors # all three scripts parsed?
|
|
333
347
|
summer_play
|
|
334
348
|
# user reproduces a hit
|
|
349
|
+
summer_get_debugger_errors # runtime errors from the tweens / bus lookups
|
|
335
350
|
summer_stop
|
|
336
351
|
```
|
|
337
352
|
|
|
@@ -355,10 +370,12 @@ Tune one knob at a time:
|
|
|
355
370
|
| Equal pitch/yaw shake | `yaw_factor ≤ 0.5` | Eyes are sensitive to vertical motion; equal axes feel mechanical |
|
|
356
371
|
| `Engine.time_scale` for every hit | Reserve hit-stop for big events only (boss hits, deaths) | Time-scale dips on every hit feel laggy, not impactful |
|
|
357
372
|
| Inline `StandardMaterial3D` sub_resource via `summer_set_prop` | Build in script or save standalone `.tres` | Inline sub_resources silently break `summer_set_resource_property` |
|
|
373
|
+
| `summer_set_prop(key="groups", value='["player_camera"]')` | `add_to_group(&"player_camera")` in `_ready()`, or a `groups = [...]` line in the `.tscn` | `Node` has no `groups` property, and `summer_set_prop` takes only string/number/boolean — never an array |
|
|
374
|
+
| `BaseMaterial3D.new()` | `StandardMaterial3D.new()` | `BaseMaterial3D` is abstract; constructing it is a parse error that kills the whole script |
|
|
358
375
|
|
|
359
376
|
## Collaborative protocol
|
|
360
377
|
|
|
361
|
-
This skill writes 3 GDScript files and adds 1 node + 1 autoload entry. Always ask before each section is applied. Group related writes into one ask: "I'm about to add HitFlash + CameraShake + AudioDucker autoload, wire one signal. OK?" See
|
|
378
|
+
This skill writes 3 GDScript files and adds 1 node + 1 autoload entry. Always ask before each section is applied. Group related writes into one ask: "I'm about to add HitFlash + CameraShake + AudioDucker autoload, wire one signal. OK?" See `../../../references/collaborative-protocol.md`.
|
|
362
379
|
|
|
363
380
|
## When NOT to use this skill
|
|
364
381
|
|
|
@@ -369,10 +386,11 @@ This skill writes 3 GDScript files and adds 1 node + 1 autoload entry. Always as
|
|
|
369
386
|
|
|
370
387
|
## See also
|
|
371
388
|
|
|
372
|
-
-
|
|
373
|
-
-
|
|
374
|
-
-
|
|
375
|
-
-
|
|
389
|
+
- `../../../references/mcp-tools-reference.md` — full MCP tool list, especially the inline-sub_resource trap
|
|
390
|
+
- `../../../references/godot-version.md` — Summer compatibility and
|
|
391
|
+
version-sensitive API notes
|
|
392
|
+
- `../../../references/collaborative-protocol.md` — "May I write" pattern
|
|
393
|
+
- `../../../references/gd-style.md` — typed GDScript conventions
|
|
376
394
|
- `audio/audio-direction/SKILL.md` — bus layout setup (prerequisite for ducking)
|
|
377
395
|
- `post-processing/screen-shake/SKILL.md` — deeper trauma variants (Perlin noise, stacked sources)
|
|
378
396
|
- `visual-effects/gpuparticles-3d-basics/SKILL.md` — particles to layer on top of the trio
|
|
@@ -5,7 +5,7 @@ license: MIT
|
|
|
5
5
|
compatibility: [Cursor, Claude Code, Windsurf, Codex]
|
|
6
6
|
category: visual-effects
|
|
7
7
|
user-invocable: true
|
|
8
|
-
allowed-tools: Read Write Edit
|
|
8
|
+
allowed-tools: Read Write Edit summer_write_file summer_read_file summer_inspect_node summer_inspect_resource summer_get_script_errors summer_get_debugger_errors summer_play summer_stop
|
|
9
9
|
paths: ["**/*.tscn", "**/*.gd", "**/*.gdshader", "addons/vfx/**"]
|
|
10
10
|
---
|
|
11
11
|
|
|
@@ -36,12 +36,17 @@ The dissolve shader samples 3D noise per fragment and clips pixels whose noise v
|
|
|
36
36
|
### 1. Files to create
|
|
37
37
|
|
|
38
38
|
```
|
|
39
|
+
addons/vfx/_building-blocks/noise-3d-fbm.gdshaderinc # copy from this skill FIRST
|
|
39
40
|
addons/vfx/dissolve/dissolve.gdshader
|
|
40
41
|
addons/vfx/dissolve/dissolve_controller.gd
|
|
41
42
|
```
|
|
42
43
|
|
|
43
44
|
No `.tscn` needed — this overrides materials on existing meshes.
|
|
44
45
|
|
|
46
|
+
`dissolve.gdshader` `#include`s the FBM noise file. A missing include is a hard
|
|
47
|
+
compile error, so copy `_building-blocks/noise-3d-fbm.gdshaderinc` from this skill
|
|
48
|
+
into `res://addons/vfx/_building-blocks/` before writing the shader.
|
|
49
|
+
|
|
45
50
|
### 2. Shader code
|
|
46
51
|
|
|
47
52
|
`addons/vfx/dissolve/dissolve.gdshader`:
|
|
@@ -68,8 +73,16 @@ uniform vec3 noise_offset = vec3(0.0);
|
|
|
68
73
|
uniform bool use_object_space = true;
|
|
69
74
|
|
|
70
75
|
void fragment() {
|
|
71
|
-
//
|
|
72
|
-
|
|
76
|
+
// In fragment(), VERTEX is in VIEW space — not world, not object. Undo the view
|
|
77
|
+
// transform first, then the model transform, or neither branch samples where it
|
|
78
|
+
// claims to (`inverse(MODEL_MATRIX) * vec4(VERTEX, 1.0)` alone is object-space
|
|
79
|
+
// noise driven by camera-relative coordinates, which slides with the camera).
|
|
80
|
+
vec3 wpos = (INV_VIEW_MATRIX * vec4(VERTEX, 1.0)).xyz;
|
|
81
|
+
vec3 opos = (inverse(MODEL_MATRIX) * vec4(wpos, 1.0)).xyz;
|
|
82
|
+
|
|
83
|
+
// Object space keeps the dissolve pattern attached to the mesh; world space
|
|
84
|
+
// keeps it fixed in the level and lets the mesh move through it.
|
|
85
|
+
vec3 npos = (use_object_space ? opos : wpos) * noise_scale + noise_offset;
|
|
73
86
|
float n = fbm3(npos);
|
|
74
87
|
|
|
75
88
|
// Discard everything below the threshold.
|
|
@@ -204,7 +217,15 @@ No new nodes added. The recipe overrides the `material_override` on every `MeshI
|
|
|
204
217
|
|
|
205
218
|
### 5. Wire it in (MCP calls)
|
|
206
219
|
|
|
207
|
-
This recipe is
|
|
220
|
+
This recipe is script-driven — no scene mutation. Write the two files with
|
|
221
|
+
`summer_write_file` (one of `create_only` or `expected_sha256` is required), plus the
|
|
222
|
+
`.gdshaderinc`, then call from gameplay code.
|
|
223
|
+
|
|
224
|
+
```
|
|
225
|
+
summer_write_file(path="res://addons/vfx/_building-blocks/noise-3d-fbm.gdshaderinc", content="<from this skill>", create_only=true)
|
|
226
|
+
summer_write_file(path="res://addons/vfx/dissolve/dissolve.gdshader", content="<section 2>", create_only=true)
|
|
227
|
+
summer_write_file(path="res://addons/vfx/dissolve/dissolve_controller.gd", content="<section 3>", create_only=true)
|
|
228
|
+
```
|
|
208
229
|
|
|
209
230
|
```gdscript
|
|
210
231
|
# When the enemy dies:
|
|
@@ -225,6 +246,20 @@ DissolveController.dissolve_object(target, 1.2, Color(1, 0.55, 0.1), 6.0, false)
|
|
|
225
246
|
DissolveController.materialize_object(target, 0.8)
|
|
226
247
|
```
|
|
227
248
|
|
|
249
|
+
### 5b. Verify
|
|
250
|
+
|
|
251
|
+
Both files are compiled code. Check them before claiming the dissolve works.
|
|
252
|
+
|
|
253
|
+
```
|
|
254
|
+
summer_get_script_errors # dissolve_controller.gd parsed?
|
|
255
|
+
summer_play
|
|
256
|
+
summer_get_debugger_errors # shader compile errors surface here at runtime
|
|
257
|
+
summer_stop
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
A missing `noise-3d-fbm.gdshaderinc` fails the whole shader, and the mesh then
|
|
261
|
+
renders with the fallback material rather than dissolving.
|
|
262
|
+
|
|
228
263
|
### 6. Parameters to tune
|
|
229
264
|
|
|
230
265
|
| Parameter | Range | Effect |
|
|
@@ -234,7 +269,7 @@ DissolveController.materialize_object(target, 0.8)
|
|
|
234
269
|
| `edge_color` | Color | rim glow tint; orange = fire, blue = magic, green = poison, white = holy |
|
|
235
270
|
| `edge_emission` | 0.0–16.0 | rim bloom strength; needs Bloom in WorldEnvironment |
|
|
236
271
|
| `noise_scale` | 0.5–12.0 | small = big patches dissolving; large = fine pixel-grain dust |
|
|
237
|
-
| `use_object_space` | bool | true = pattern attached to mesh; false = pattern stays in world (cool for moving objects) |
|
|
272
|
+
| `use_object_space` | bool | true = pattern attached to mesh; false = pattern stays in world (cool for moving objects). Both branches derive their position from `INV_VIEW_MATRIX * vec4(VERTEX, 1.0)`, because `VERTEX` in `fragment()` is view space |
|
|
238
273
|
|
|
239
274
|
## Cookbook — named variants
|
|
240
275
|
|
|
@@ -304,13 +339,15 @@ noise_scale = 4.0
|
|
|
304
339
|
- **Dissolving a particle system (e.g., torch flame).** The shader is `spatial`, not `particles`. Stop the particles, don't try to dissolve them.
|
|
305
340
|
- **Forgetting to seed `noise_offset` per instance.** Two enemies dissolving at the same frame use identical patterns → they look like clones. The included controller randomizes per-instance.
|
|
306
341
|
- **Using world-space noise on a moving target.** The dissolve pattern slides over the surface as the mesh moves. Use `use_object_space = true` (default).
|
|
342
|
+
- **Treating `VERTEX` in `fragment()` as world space.** It is view space. Sampling noise from it directly makes the pattern slide as the *camera* moves, and `inverse(MODEL_MATRIX) * vec4(VERTEX, 1.0)` is not object space either. Go through `INV_VIEW_MATRIX` first, as section 2 does.
|
|
307
343
|
- **Skipping the texture inheritance.** The mesh dissolves but goes white because `base_color` defaults to white and no texture is set. The controller pulls from the original `BaseMaterial3D`; if your mesh uses a custom shader, manually wire the textures.
|
|
308
344
|
- **Dissolving a `Skeleton3D` directly.** It's not a mesh — recurse into `MeshInstance3D` children. The controller's `_collect_meshes` does this.
|
|
309
345
|
|
|
310
346
|
## Performance notes
|
|
311
347
|
|
|
312
|
-
- Per fragment: one FBM noise call + one branch
|
|
313
|
-
- Multiple meshes on a character (head, body, weapon, cape): each gets its own override
|
|
348
|
+
- Per fragment: one FBM noise call (4 octaves, 8 hashes each) + one branch + two 4×4 inversions. Sub-millisecond per dissolving object at 1080p covering 10% of the screen — an order-of-magnitude expectation, not a measurement. Profile if you need a number.
|
|
349
|
+
- Multiple meshes on a character (head, body, weapon, cape): each gets its own override, and the cost scales with the number of materials. Still fine for a handful.
|
|
350
|
+
- `inverse(MODEL_MATRIX)` runs per fragment. If you are dissolving a large screen-filling mesh and profiling says this hurts, hoist the object-space position into a `varying` computed in `vertex()`.
|
|
314
351
|
- `discard` defeats early-Z. For a screen full of dissolving enemies, consider `cull_back` (already enabled) and avoid stacking 20+ dissolves at once.
|
|
315
352
|
- Mobile: drop `noise_scale` to a value the FBM can compute in 1–2 octaves, or replace the `fbm3` include with a single-octave noise.
|
|
316
353
|
|
|
@@ -324,11 +361,16 @@ noise_scale = 4.0
|
|
|
324
361
|
|
|
325
362
|
## Fallback (no MCP)
|
|
326
363
|
|
|
327
|
-
|
|
364
|
+
Section 5 is fully automatable — `summer_write_file` writes all three files and
|
|
365
|
+
`summer_get_script_errors` proves they parsed. Do not hand these steps to the user
|
|
366
|
+
when the MCP tools are available.
|
|
367
|
+
|
|
368
|
+
Without the MCP connection there is no engine to drive, so the user does it themselves:
|
|
328
369
|
|
|
329
|
-
1. Create `addons/vfx/dissolve/` and write the
|
|
370
|
+
1. Create `addons/vfx/_building-blocks/` and `addons/vfx/dissolve/` and write the three files above.
|
|
330
371
|
2. From any gameplay script, call `DissolveController.dissolve_object(target_node)`.
|
|
331
372
|
3. The controller handles material override, texture inheritance, tween, and freeing.
|
|
373
|
+
4. Check the Errors dock — a missing `.gdshaderinc` fails the whole shader.
|
|
332
374
|
|
|
333
375
|
## Handoff
|
|
334
376
|
|