summer-engine 2.7.0 → 2.8.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 +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 +10 -0
- package/GEMINI.md +5 -4
- package/README.md +58 -19
- 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 +123 -36
- 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 +4 -2
- package/dist/lib/api-client.js +28 -4
- 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/generate-tools.js +16 -6
- package/dist/mcp/tools/project-tools.js +12 -7
- package/dist/mcp/tools/scene-tools.js +6 -6
- package/dist/mcp/tools/visual-tools.js +4 -3
- 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
|
@@ -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 summer_add_node summer_set_prop summer_set_resource_property summer_inspect_node summer_save_scene
|
|
8
|
+
allowed-tools: Read Write Edit summer_write_file summer_read_file summer_create_scene summer_add_node summer_set_prop summer_set_resource_property summer_inspect_node summer_save_scene summer_get_script_errors summer_get_debugger_errors summer_play summer_stop
|
|
9
9
|
paths: ["**/*.tscn", "**/*.gd", "**/*.gdshader", "addons/vfx/**"]
|
|
10
10
|
---
|
|
11
11
|
|
|
@@ -85,6 +85,7 @@ var _motes: GPUParticles3D
|
|
|
85
85
|
var _source_mesh: MeshInstance3D
|
|
86
86
|
var _source_mat: BaseMaterial3D
|
|
87
87
|
var _t: float = 0.0
|
|
88
|
+
var _intensity: float = 1.0
|
|
88
89
|
|
|
89
90
|
func _ready() -> void:
|
|
90
91
|
_build_light()
|
|
@@ -127,7 +128,9 @@ func _build_motes() -> void:
|
|
|
127
128
|
|
|
128
129
|
var mesh := QuadMesh.new()
|
|
129
130
|
mesh.size = Vector2.ONE
|
|
130
|
-
|
|
131
|
+
# BaseMaterial3D is abstract ("Native class "BaseMaterial3D" cannot be
|
|
132
|
+
# constructed as it is abstract"); StandardMaterial3D is the concrete subclass.
|
|
133
|
+
var bm := StandardMaterial3D.new()
|
|
131
134
|
bm.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
|
|
132
135
|
bm.blend_mode = BaseMaterial3D.BLEND_MODE_ADD
|
|
133
136
|
bm.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
|
@@ -160,14 +163,17 @@ func _process(delta: float) -> void:
|
|
|
160
163
|
phase = pulse_easing.sample(phase)
|
|
161
164
|
|
|
162
165
|
if _light:
|
|
163
|
-
_light.light_energy = light_energy_base + light_energy_amplitude * phase
|
|
166
|
+
_light.light_energy = (light_energy_base + light_energy_amplitude * phase) * _intensity
|
|
164
167
|
if _source_mat:
|
|
165
|
-
_source_mat.emission_energy_multiplier = source_emission_base + source_emission_amplitude * phase
|
|
168
|
+
_source_mat.emission_energy_multiplier = (source_emission_base + source_emission_amplitude * phase) * _intensity
|
|
166
169
|
|
|
167
170
|
func set_intensity(scale: float) -> void:
|
|
168
171
|
## Scale all visible cues 0..1+ (e.g., charge-up: tween from 0 to 1 over a second).
|
|
169
|
-
light_energy_base
|
|
170
|
-
light_energy_amplitude *= scale
|
|
172
|
+
## Held as a separate factor rather than multiplied into light_energy_base /
|
|
173
|
+
## light_energy_amplitude: a multiplying version (`light_energy_base *= scale`)
|
|
174
|
+
## pins both fields to 0.0 forever after set_intensity(0.0), so the charge-up
|
|
175
|
+
## tween from 0 to 1 never brings the glow back.
|
|
176
|
+
_intensity = scale
|
|
171
177
|
if _motes:
|
|
172
178
|
_motes.amount_ratio = scale
|
|
173
179
|
|
|
@@ -191,26 +197,40 @@ Node3D ("MagicGlow") [script: magic_glow.gd]
|
|
|
191
197
|
|
|
192
198
|
For an enchanted sword:
|
|
193
199
|
|
|
200
|
+
Every scene-mutating call takes an explicit `scenePath`. `summer_set_prop` uses `key`
|
|
201
|
+
(not `property`) and only accepts a string, number, or boolean.
|
|
202
|
+
|
|
194
203
|
```
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
summer_set_prop(path="./Player/Hand/Sword/Glow",
|
|
199
|
-
summer_set_prop(path="./Player/Hand/Sword/Glow",
|
|
200
|
-
summer_set_prop(path="./Player/Hand/Sword/Glow",
|
|
201
|
-
summer_set_prop(path="./Player/Hand/Sword/Glow",
|
|
202
|
-
summer_set_prop(path="./Player/Hand/Sword/Glow",
|
|
203
|
-
|
|
204
|
+
summer_write_file(path="res://addons/vfx/magic-glow/magic_glow.gd", content="<section 2>", create_only=true)
|
|
205
|
+
|
|
206
|
+
summer_add_node(scenePath="res://main.tscn", parent="./Player/Hand/Sword", type="Node3D", name="Glow")
|
|
207
|
+
summer_set_prop(scenePath="res://main.tscn", path="./Player/Hand/Sword/Glow", key="script", value="res://addons/vfx/magic-glow/magic_glow.gd")
|
|
208
|
+
summer_set_prop(scenePath="res://main.tscn", path="./Player/Hand/Sword/Glow", key="glow_color", value="Color(0.55, 0.85, 1.0)")
|
|
209
|
+
summer_set_prop(scenePath="res://main.tscn", path="./Player/Hand/Sword/Glow", key="light_energy_base", value=1.2)
|
|
210
|
+
summer_set_prop(scenePath="res://main.tscn", path="./Player/Hand/Sword/Glow", key="light_energy_amplitude", value=0.6)
|
|
211
|
+
summer_set_prop(scenePath="res://main.tscn", path="./Player/Hand/Sword/Glow", key="mote_count", value=16)
|
|
212
|
+
summer_set_prop(scenePath="res://main.tscn", path="./Player/Hand/Sword/Glow", key="mote_radius", value=0.20)
|
|
213
|
+
summer_set_prop(scenePath="res://main.tscn", path="./Player/Hand/Sword/Glow", key="pulse_source_mesh", value="../Blade")
|
|
214
|
+
summer_save_scene(scenePath="res://main.tscn")
|
|
204
215
|
```
|
|
205
216
|
|
|
206
217
|
For a soul gem on a pedestal:
|
|
207
218
|
|
|
208
219
|
```
|
|
209
|
-
summer_add_node(parent="./World/Pedestal/SoulGem", type="Node3D", name="Glow")
|
|
210
|
-
summer_set_prop(path="./World/Pedestal/SoulGem/Glow",
|
|
211
|
-
summer_set_prop(path="./World/Pedestal/SoulGem/Glow",
|
|
212
|
-
summer_set_prop(path="./World/Pedestal/SoulGem/Glow",
|
|
213
|
-
summer_set_prop(path="./World/Pedestal/SoulGem/Glow",
|
|
220
|
+
summer_add_node(scenePath="res://main.tscn", parent="./World/Pedestal/SoulGem", type="Node3D", name="Glow")
|
|
221
|
+
summer_set_prop(scenePath="res://main.tscn", path="./World/Pedestal/SoulGem/Glow", key="glow_color", value="Color(0.85, 0.45, 1.0)")
|
|
222
|
+
summer_set_prop(scenePath="res://main.tscn", path="./World/Pedestal/SoulGem/Glow", key="pulse_hz", value=0.4)
|
|
223
|
+
summer_set_prop(scenePath="res://main.tscn", path="./World/Pedestal/SoulGem/Glow", key="mote_count", value=32)
|
|
224
|
+
summer_set_prop(scenePath="res://main.tscn", path="./World/Pedestal/SoulGem/Glow", key="mote_radius", value=0.30)
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Then verify — this recipe is one script, so a parse error is the whole effect:
|
|
228
|
+
|
|
229
|
+
```
|
|
230
|
+
summer_get_script_errors # magic_glow.gd parsed?
|
|
231
|
+
summer_play
|
|
232
|
+
summer_get_debugger_errors
|
|
233
|
+
summer_stop
|
|
214
234
|
```
|
|
215
235
|
|
|
216
236
|
For a charge-up before casting:
|
|
@@ -313,11 +333,12 @@ mote_drift_up = 0.0
|
|
|
313
333
|
|
|
314
334
|
### charge-up (paired with lightning/muzzle-flash)
|
|
315
335
|
|
|
316
|
-
|
|
336
|
+
Author the **fully-charged** values here; `set_intensity(0.0 → 1.0)` scales them at
|
|
337
|
+
runtime. Leaving `light_energy_base` at 0.0 means the fully-charged glow is also 0.0.
|
|
317
338
|
|
|
318
339
|
```
|
|
319
340
|
glow_color = Color(0.55, 0.75, 1.0)
|
|
320
|
-
light_energy_base =
|
|
341
|
+
light_energy_base = 1.5 # fully-charged value; set_intensity scales it
|
|
321
342
|
light_energy_amplitude = 2.0
|
|
322
343
|
pulse_hz = 2.5 # buzzing
|
|
323
344
|
mote_count = 24
|
|
@@ -334,11 +355,16 @@ mote_drift_up = 0.5
|
|
|
334
355
|
- **Forgetting to enable the source mesh's emission before assigning the glow color.** The shader's emission stays black. The controller's `_resolve_source_mesh` enables it.
|
|
335
356
|
- **`pulse_hz` too high.** Above 3 Hz it strobes — accessibility issue (photosensitivity). Default 0.7 is calm.
|
|
336
357
|
- **Generating an "aura" PNG and using as a billboard sprite.** The misroute. The procedural light + motes are alive and scale to any item.
|
|
358
|
+
- **`BaseMaterial3D.new()` for the mote material.** `BaseMaterial3D` is abstract — that line is a parse error and takes the whole script down. Instantiate `StandardMaterial3D`; keep using the `BaseMaterial3D.*` enum constants.
|
|
359
|
+
- **A multiplicative `set_intensity` (`light_energy_base *= scale`).** One `set_intensity(0.0)` pins the field to 0.0 permanently, so the charge-up tween from 0 to 1 does nothing. Hold the scale in its own variable and apply it where the energy is written.
|
|
337
360
|
|
|
338
361
|
## Performance notes
|
|
339
362
|
|
|
340
|
-
- One glow = 1 light + 16–32 particles.
|
|
341
|
-
- N glowing items in a scene: budget ~12 active `OmniLight3D`s before you
|
|
363
|
+
- One glow = 1 light + 16–32 particles. Sub-millisecond — an order-of-magnitude expectation, not a measurement.
|
|
364
|
+
- N glowing items in a scene: budget ~12 active `OmniLight3D`s before you start
|
|
365
|
+
culling. The current Summer build has no `LightOccluder3D`
|
|
366
|
+
(`ClassDB.class_exists` is `false`; `LightOccluder2D` is 2D-only). Cull with
|
|
367
|
+
`distance_fade_enabled` / `distance_fade_begin` or a smaller `omni_range`.
|
|
342
368
|
- Mobile: drop `mote_count` to 8, `light_range` × 0.5, `light_shadow_enabled = false` always.
|
|
343
369
|
- LOD: in `_process`, scale `_motes.amount_ratio` and `_light.light_energy` by `clamp(1.5 - dist_to_camera / 20.0, 0.10, 1.0)`. Past 20 m, drop motes entirely; keep light.
|
|
344
370
|
|
|
@@ -352,11 +378,18 @@ mote_drift_up = 0.5
|
|
|
352
378
|
|
|
353
379
|
## Fallback (no MCP)
|
|
354
380
|
|
|
355
|
-
|
|
381
|
+
Section 4 is fully automatable — `summer_write_file` writes the controller,
|
|
382
|
+
`summer_add_node` + `summer_set_prop` attach and tune it, and `summer_create_scene` +
|
|
383
|
+
`summer_save_scene` produce `magic_glow.tscn`. Do not hand these steps to the user
|
|
384
|
+
when the MCP tools are available.
|
|
385
|
+
|
|
386
|
+
Without the MCP connection there is no engine to drive, so the user does it
|
|
387
|
+
manually in Summer Engine:
|
|
356
388
|
|
|
357
389
|
1. Create `addons/vfx/magic-glow/` with the two files above.
|
|
358
|
-
2.
|
|
390
|
+
2. Add a `Node3D` child to the glowing object, attach `magic_glow.gd`. Save as `magic_glow.tscn` if you want a scene to instantiate.
|
|
359
391
|
3. Tune the exported parameters in the inspector or via script.
|
|
392
|
+
4. Check the Errors dock before tuning anything.
|
|
360
393
|
|
|
361
394
|
## Handoff
|
|
362
395
|
|
|
@@ -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 summer_add_node summer_set_prop summer_set_resource_property summer_inspect_node summer_save_scene
|
|
8
|
+
allowed-tools: Read Write Edit summer_write_file summer_read_file summer_create_scene summer_add_node summer_set_prop summer_set_resource_property summer_connect_signal summer_inspect_node summer_save_scene summer_get_script_errors summer_get_debugger_errors summer_play summer_stop
|
|
9
9
|
paths: ["**/*.tscn", "**/*.gd", "**/*.gdshader", "addons/vfx/**"]
|
|
10
10
|
---
|
|
11
11
|
|
|
@@ -25,7 +25,9 @@ A muzzle flash is on screen for 60–100 ms. Long enough to read, too short to a
|
|
|
25
25
|
|
|
26
26
|
- The user wants a *continuous* flame (flamethrower) — use `fire`, not muzzle-flash.
|
|
27
27
|
- The user wants the impact spark on the wall the bullet hit — that's `hit-spark`.
|
|
28
|
-
- The user wants a tracer line from barrel to target — use
|
|
28
|
+
- The user wants a tracer line from barrel to target — use an `ImmediateMesh`
|
|
29
|
+
recipe, not this. The current Summer build has no `Line3D`
|
|
30
|
+
(`ClassDB.class_exists("Line3D")` is `false`).
|
|
29
31
|
- The user wants a slow-glowing spell charge-up — use `magic-glow` for the buildup, then this for the release frame.
|
|
30
32
|
|
|
31
33
|
## Recipe
|
|
@@ -55,12 +57,19 @@ uniform float ray_sharpness : hint_range(0.5, 32.0) = 8.0;
|
|
|
55
57
|
uniform float random_seed : hint_range(0.0, 6.2831) = 0.0; // rotates the star per shot
|
|
56
58
|
|
|
57
59
|
void vertex() {
|
|
60
|
+
// Billboard toward the camera. The bare mat4(INV_VIEW_MATRIX[0..2], MODEL_MATRIX[3])
|
|
61
|
+
// form discards the node's scale, so the per-shot `flash_size` randomisation in
|
|
62
|
+
// fire() would never reach the screen — normalize the basis and re-apply scale.
|
|
58
63
|
MODELVIEW_MATRIX = VIEW_MATRIX * mat4(
|
|
59
|
-
INV_VIEW_MATRIX[0],
|
|
60
|
-
INV_VIEW_MATRIX[1],
|
|
61
|
-
INV_VIEW_MATRIX[2],
|
|
62
|
-
MODEL_MATRIX[3]
|
|
63
|
-
|
|
64
|
+
normalize(INV_VIEW_MATRIX[0]),
|
|
65
|
+
normalize(INV_VIEW_MATRIX[1]),
|
|
66
|
+
normalize(INV_VIEW_MATRIX[2]),
|
|
67
|
+
MODEL_MATRIX[3]) * mat4(
|
|
68
|
+
vec4(length(MODEL_MATRIX[0].xyz), 0.0, 0.0, 0.0),
|
|
69
|
+
vec4(0.0, length(MODEL_MATRIX[1].xyz), 0.0, 0.0),
|
|
70
|
+
vec4(0.0, 0.0, length(MODEL_MATRIX[2].xyz), 0.0),
|
|
71
|
+
vec4(0.0, 0.0, 0.0, 1.0));
|
|
72
|
+
MODELVIEW_NORMAL_MATRIX = mat3(MODELVIEW_MATRIX);
|
|
64
73
|
}
|
|
65
74
|
|
|
66
75
|
void fragment() {
|
|
@@ -81,9 +90,11 @@ void fragment() {
|
|
|
81
90
|
// Mix core color (white-hot) into flash color at the very center.
|
|
82
91
|
vec3 col = mix(flash_color.rgb, core_color.rgb, pow(core, 4.0));
|
|
83
92
|
|
|
84
|
-
ALBEDO
|
|
85
|
-
|
|
86
|
-
|
|
93
|
+
// `render_mode unshaded` outputs ALBEDO and discards EMISSION entirely
|
|
94
|
+
// (scene_forward_clustered.glsl:2962 — `frag_color = vec4(albedo, alpha)`
|
|
95
|
+
// under MODE_UNSHADED), so the bloom boost has to live in ALBEDO.
|
|
96
|
+
ALBEDO = col * emission_boost * intensity;
|
|
97
|
+
ALPHA = mask * intensity;
|
|
87
98
|
}
|
|
88
99
|
```
|
|
89
100
|
|
|
@@ -98,6 +109,8 @@ extends Node3D
|
|
|
98
109
|
|
|
99
110
|
## Spawn one of these as a child of the gun's barrel tip and call `fire()` on each shot.
|
|
100
111
|
|
|
112
|
+
const SPARK_SCENE := "res://addons/vfx/hit-spark/hit_spark.tscn"
|
|
113
|
+
|
|
101
114
|
@export var flash_color: Color = Color(1.0, 0.85, 0.45)
|
|
102
115
|
@export_range(0.02, 0.30) var flash_duration: float = 0.08 ## seconds visible
|
|
103
116
|
@export_range(0.10, 4.0) var flash_size: float = 0.6 ## meters across
|
|
@@ -134,8 +147,12 @@ func _ready() -> void:
|
|
|
134
147
|
_light.shadow_enabled = false
|
|
135
148
|
add_child(_light)
|
|
136
149
|
|
|
137
|
-
|
|
138
|
-
|
|
150
|
+
# preload() resolves at PARSE time, so a runtime ResourceLoader.exists() check
|
|
151
|
+
# cannot guard it — "Parse Error: Preload file ... does not exist" takes the
|
|
152
|
+
# whole script down when hit-spark hasn't been installed. Use load().
|
|
153
|
+
if spawn_sparks and ResourceLoader.exists(SPARK_SCENE):
|
|
154
|
+
var packed: PackedScene = load(SPARK_SCENE)
|
|
155
|
+
_sparks = packed.instantiate() as GPUParticles3D
|
|
139
156
|
if _sparks:
|
|
140
157
|
_sparks.one_shot = true
|
|
141
158
|
_sparks.emitting = false
|
|
@@ -184,13 +201,19 @@ Node3D ("MuzzleFlash") [script: muzzle_flash.gd]
|
|
|
184
201
|
|
|
185
202
|
Place the node at the **barrel tip** of the weapon. If your gun model has a `BarrelTip` Marker3D, parent there directly.
|
|
186
203
|
|
|
204
|
+
Every scene-mutating call takes an explicit `scenePath`. `summer_set_prop` uses `key`
|
|
205
|
+
(not `property`) and only accepts a string, number, or boolean.
|
|
206
|
+
|
|
187
207
|
```
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
summer_set_prop(path="./Player/Weapon/BarrelTip/MuzzleFlash",
|
|
193
|
-
|
|
208
|
+
summer_write_file(path="res://addons/vfx/muzzle-flash/muzzle_flash.gdshader", content="<section 2>", create_only=true)
|
|
209
|
+
summer_write_file(path="res://addons/vfx/muzzle-flash/muzzle_flash.gd", content="<section 3>", create_only=true)
|
|
210
|
+
|
|
211
|
+
summer_add_node(scenePath="res://main.tscn", parent="./Player/Weapon/BarrelTip", type="Node3D", name="MuzzleFlash")
|
|
212
|
+
summer_set_prop(scenePath="res://main.tscn", path="./Player/Weapon/BarrelTip/MuzzleFlash", key="script", value="res://addons/vfx/muzzle-flash/muzzle_flash.gd")
|
|
213
|
+
summer_set_prop(scenePath="res://main.tscn", path="./Player/Weapon/BarrelTip/MuzzleFlash", key="flash_size", value=0.6)
|
|
214
|
+
summer_set_prop(scenePath="res://main.tscn", path="./Player/Weapon/BarrelTip/MuzzleFlash", key="flash_duration", value=0.08)
|
|
215
|
+
summer_set_prop(scenePath="res://main.tscn", path="./Player/Weapon/BarrelTip/MuzzleFlash", key="light_energy", value=4.0)
|
|
216
|
+
summer_save_scene(scenePath="res://main.tscn")
|
|
194
217
|
```
|
|
195
218
|
|
|
196
219
|
Then in your weapon script, on `fire()`:
|
|
@@ -199,10 +222,22 @@ Then in your weapon script, on `fire()`:
|
|
|
199
222
|
$BarrelTip/MuzzleFlash.fire()
|
|
200
223
|
```
|
|
201
224
|
|
|
202
|
-
Or connect
|
|
225
|
+
Or connect the weapon's `fired` signal in the scene:
|
|
226
|
+
|
|
227
|
+
```
|
|
228
|
+
summer_connect_signal(scenePath="res://main.tscn", emitter="./Player/Weapon", signal="fired", receiver="./Player/Weapon/BarrelTip/MuzzleFlash", method="fire")
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### 5b. Verify
|
|
232
|
+
|
|
233
|
+
`muzzle_flash.gd` `preload`s its own shader, so a shader that failed to write takes
|
|
234
|
+
the script down at parse time. Check before firing a shot.
|
|
203
235
|
|
|
204
|
-
```
|
|
205
|
-
|
|
236
|
+
```
|
|
237
|
+
summer_get_script_errors # muzzle_flash.gd parsed?
|
|
238
|
+
summer_play
|
|
239
|
+
summer_get_debugger_errors # shader compile errors surface here at runtime
|
|
240
|
+
summer_stop
|
|
206
241
|
```
|
|
207
242
|
|
|
208
243
|
### 6. Parameters to tune
|
|
@@ -283,11 +318,12 @@ ray_sharpness = 18.0
|
|
|
283
318
|
- **OmniLight3D with shadows enabled.** A 60 ms shadow pass costs more than the flash itself. `shadow_enabled = false`.
|
|
284
319
|
- **Flash duration > 150 ms.** Reads as a flamethrower or a stuck animation. Stay under 100 ms unless intentional.
|
|
285
320
|
- **Spawning a new MuzzleFlash each shot and freeing it.** Rapid-fire weapons leak. Reuse one anchored to the barrel and call `fire()`.
|
|
321
|
+
- **`preload`ing the optional hit-spark scene.** `preload()` resolves at parse time, so wrapping it in a runtime `ResourceLoader.exists()` check does nothing — the script fails to load entirely when hit-spark isn't installed. Use `load()` behind the check.
|
|
286
322
|
- **Generating an animated flash sprite.** That's the misroute this skill exists to prevent. The procedural shader rotates per shot for free, and you can recolor without regenerating.
|
|
287
323
|
|
|
288
324
|
## Performance notes
|
|
289
325
|
|
|
290
|
-
- One quad + one light + ~8 sparks
|
|
326
|
+
- One quad + one light + ~8 sparks is negligible — safe at any rate of fire. That is an order-of-magnitude expectation, not a measurement.
|
|
291
327
|
- The `OmniLight3D` snapping on/off per shot will trigger a shadow re-bake on lights with `shadow_enabled = true` — keep it off, always.
|
|
292
328
|
- For full-auto weapons (10+ shots/sec), consider raising `flash_duration` to 0.10 and dropping `intensity` to 0.6 so consecutive flashes blend instead of strobe.
|
|
293
329
|
|
|
@@ -297,16 +333,23 @@ ray_sharpness = 18.0
|
|
|
297
333
|
- **Weapon visible in a mirror / scope.** The additive quad will appear in reflections only if the reflection probe captures it; for scope ADS, render the flash into the scope's separate viewport.
|
|
298
334
|
- **Underwater shooting.** Tint the flash blue-green via `flash_color`, drop `light_range` to 30%.
|
|
299
335
|
- **Silenced weapon.** `flash_size = 0.10`, `light_energy = 0.5`, `flash_duration = 0.04`. Just a wink at the barrel.
|
|
300
|
-
- **Suppressed flash with a tracer.** Pair this with
|
|
336
|
+
- **Suppressed flash with a tracer.** Pair this with an `ImmediateMesh` tracer from barrel to hit point, fired in the same call.
|
|
301
337
|
|
|
302
338
|
## Fallback (no MCP)
|
|
303
339
|
|
|
304
|
-
|
|
340
|
+
Section 5 is fully automatable — `summer_write_file` writes the shader and the
|
|
341
|
+
controller, `summer_add_node` + `summer_set_prop` place it, and
|
|
342
|
+
`summer_connect_signal` wires `fired` → `fire`. Do not hand these steps to the user
|
|
343
|
+
when the MCP tools are available.
|
|
344
|
+
|
|
345
|
+
Without the MCP connection there is no engine to drive, so the user does it
|
|
346
|
+
manually in Summer Engine:
|
|
305
347
|
|
|
306
348
|
1. Create `addons/vfx/muzzle-flash/` and write the three files above.
|
|
307
|
-
2.
|
|
349
|
+
2. Add a `Node3D` child to your weapon's barrel tip Marker3D.
|
|
308
350
|
3. Attach `muzzle_flash.gd` as the script. The runtime builds the quad + light.
|
|
309
351
|
4. In your weapon's fire code: `$BarrelTip/MuzzleFlash.fire()`.
|
|
352
|
+
5. Check the Errors dock — `muzzle_flash.gd` `preload`s the shader, so a broken shader path fails the script too.
|
|
310
353
|
|
|
311
354
|
## Handoff
|
|
312
355
|
|
|
@@ -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 summer_add_node summer_set_prop summer_set_resource_property summer_inspect_node summer_save_scene
|
|
8
|
+
allowed-tools: Read Write Edit summer_write_file summer_read_file summer_create_scene summer_add_node summer_set_prop summer_set_resource_property summer_inspect_node summer_inspect_resource summer_save_scene 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,11 +36,16 @@ Smoke is the same particle pattern as fire — billboard quads emitted from a sm
|
|
|
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/smoke/smoke.gdshader
|
|
40
41
|
addons/vfx/smoke/smoke_controller.gd
|
|
41
42
|
addons/vfx/smoke/smoke.tscn
|
|
42
43
|
```
|
|
43
44
|
|
|
45
|
+
`smoke.gdshader` `#include`s the FBM noise file. A missing include is a hard compile
|
|
46
|
+
error, so copy `_building-blocks/noise-3d-fbm.gdshaderinc` from this skill into
|
|
47
|
+
`res://addons/vfx/_building-blocks/` before writing the shader.
|
|
48
|
+
|
|
44
49
|
### 2. Shader code
|
|
45
50
|
|
|
46
51
|
`addons/vfx/smoke/smoke.gdshader`:
|
|
@@ -59,20 +64,41 @@ uniform float noise_speed : hint_range(0.0, 2.0) = 0.5;
|
|
|
59
64
|
uniform float soft_edge : hint_range(0.01, 0.5) = 0.30;
|
|
60
65
|
uniform float depth_fade : hint_range(0.0, 4.0) = 1.0; // soft against geometry behind
|
|
61
66
|
|
|
67
|
+
// INSTANCE_CUSTOM is vertex-stage only, so carry what fragment() needs across.
|
|
68
|
+
varying float age;
|
|
69
|
+
varying float seed;
|
|
70
|
+
|
|
62
71
|
void vertex() {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
72
|
+
// Billboard toward the camera, keeping per-particle rotation (INSTANCE_CUSTOM.x)
|
|
73
|
+
// and per-particle scale. The bare `VIEW_MATRIX * mat4(INV_VIEW_MATRIX[0..2],
|
|
74
|
+
// MODEL_MATRIX[3])` form throws both away — this mirrors what Godot's own
|
|
75
|
+
// BILLBOARD_PARTICLES generator emits (scene/resources/material.cpp:1313-1334).
|
|
76
|
+
mat4 mat_world = mat4(
|
|
77
|
+
normalize(INV_VIEW_MATRIX[0]),
|
|
78
|
+
normalize(INV_VIEW_MATRIX[1]),
|
|
79
|
+
normalize(INV_VIEW_MATRIX[2]),
|
|
80
|
+
MODEL_MATRIX[3]);
|
|
81
|
+
mat_world = mat_world * mat4(
|
|
82
|
+
vec4(cos(INSTANCE_CUSTOM.x), -sin(INSTANCE_CUSTOM.x), 0.0, 0.0),
|
|
83
|
+
vec4(sin(INSTANCE_CUSTOM.x), cos(INSTANCE_CUSTOM.x), 0.0, 0.0),
|
|
84
|
+
vec4(0.0, 0.0, 1.0, 0.0),
|
|
85
|
+
vec4(0.0, 0.0, 0.0, 1.0));
|
|
86
|
+
MODELVIEW_MATRIX = VIEW_MATRIX * mat_world * mat4(
|
|
87
|
+
vec4(length(MODEL_MATRIX[0].xyz), 0.0, 0.0, 0.0),
|
|
88
|
+
vec4(0.0, length(MODEL_MATRIX[1].xyz), 0.0, 0.0),
|
|
89
|
+
vec4(0.0, 0.0, length(MODEL_MATRIX[2].xyz), 0.0),
|
|
90
|
+
vec4(0.0, 0.0, 0.0, 1.0));
|
|
91
|
+
MODELVIEW_NORMAL_MATRIX = mat3(MODELVIEW_MATRIX);
|
|
92
|
+
|
|
93
|
+
// CUSTOM.x is the rotation angle, NOT the age. The age lives in CUSTOM.y
|
|
94
|
+
// (it counts up by DELTA/LIFETIME) and CUSTOM.w holds the lifetime scale.
|
|
95
|
+
age = clamp(INSTANCE_CUSTOM.y / max(INSTANCE_CUSTOM.w, 0.0001), 0.0, 1.0);
|
|
96
|
+
seed = fract(sin(float(INSTANCE_ID) * 12.9898) * 43758.5453);
|
|
69
97
|
}
|
|
70
98
|
|
|
71
99
|
void fragment() {
|
|
72
|
-
float age = CUSTOM.x;
|
|
73
|
-
|
|
74
100
|
// Distort UVs with slow noise.
|
|
75
|
-
vec3 np = vec3(UV * noise_scale, TIME * noise_speed +
|
|
101
|
+
vec3 np = vec3(UV * noise_scale, TIME * noise_speed + seed * 10.0);
|
|
76
102
|
float n = fbm3(np);
|
|
77
103
|
|
|
78
104
|
// Soft round mask, modulated by noise so the silhouette is wispy not circular.
|
|
@@ -162,6 +188,10 @@ func _apply() -> void:
|
|
|
162
188
|
|
|
163
189
|
func _make_scale_curve(end_mult: float) -> CurveTexture:
|
|
164
190
|
var c := Curve.new()
|
|
191
|
+
# Curve clamps every sample to max_value, which defaults to 1.0. Without this
|
|
192
|
+
# line every end multiplier above 1.0 is silently flattened back to 1.0 and the
|
|
193
|
+
# whole cookbook's growth numbers do nothing.
|
|
194
|
+
c.max_value = maxf(1.0, end_mult)
|
|
165
195
|
c.add_point(Vector2(0.0, 0.4))
|
|
166
196
|
c.add_point(Vector2(0.3, 1.0))
|
|
167
197
|
c.add_point(Vector2(1.0, end_mult))
|
|
@@ -188,30 +218,52 @@ func stop_smoke(fade_seconds: float = 1.5) -> void:
|
|
|
188
218
|
```
|
|
189
219
|
GPUParticles3D ("Smoke") [script: smoke_controller.gd]
|
|
190
220
|
├── process_material: ParticleProcessMaterial
|
|
191
|
-
└── draw_pass_1: QuadMesh (size 1
|
|
192
|
-
└──
|
|
221
|
+
└── draw_pass_1: QuadMesh (size Vector2(1, 1))
|
|
222
|
+
└── material: ShaderMaterial (shader: smoke.gdshader)
|
|
193
223
|
```
|
|
194
224
|
|
|
225
|
+
`process_material` and `draw_pass_1` are resource properties on the one node, not
|
|
226
|
+
child nodes, and `ParticleProcessMaterial` is a `Material` — you cannot
|
|
227
|
+
`summer_add_node` it.
|
|
228
|
+
|
|
195
229
|
### 5. Wire it in (MCP calls)
|
|
196
230
|
|
|
231
|
+
Every scene-mutating call takes an explicit `scenePath`. `summer_set_prop` uses `key`
|
|
232
|
+
(not `property`) and only accepts a string, number, or boolean.
|
|
233
|
+
|
|
197
234
|
For chimney smoke or fire pairing, parent the Smoke just above the heat source:
|
|
198
235
|
|
|
199
236
|
```
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
summer_set_prop(path="./World/Cottage/Chimney/Smoke",
|
|
206
|
-
|
|
237
|
+
summer_write_file(path="res://addons/vfx/_building-blocks/noise-3d-fbm.gdshaderinc", content="<from this skill>", create_only=true)
|
|
238
|
+
summer_write_file(path="res://addons/vfx/smoke/smoke.gdshader", content="<section 2>", create_only=true)
|
|
239
|
+
summer_write_file(path="res://addons/vfx/smoke/smoke_controller.gd", content="<section 3>", create_only=true)
|
|
240
|
+
|
|
241
|
+
summer_add_node(scenePath="res://main.tscn", parent="./World/Cottage/Chimney", type="GPUParticles3D", name="Smoke")
|
|
242
|
+
summer_set_prop(scenePath="res://main.tscn", path="./World/Cottage/Chimney/Smoke", key="script", value="res://addons/vfx/smoke/smoke_controller.gd")
|
|
243
|
+
summer_set_prop(scenePath="res://main.tscn", path="./World/Cottage/Chimney/Smoke", key="position", value="Vector3(0, 0.3, 0)")
|
|
244
|
+
summer_set_prop(scenePath="res://main.tscn", path="./World/Cottage/Chimney/Smoke", key="rise_height", value=4.0)
|
|
245
|
+
summer_set_prop(scenePath="res://main.tscn", path="./World/Cottage/Chimney/Smoke", key="particle_count", value=48)
|
|
246
|
+
summer_set_prop(scenePath="res://main.tscn", path="./World/Cottage/Chimney/Smoke", key="wind_direction", value="Vector3(0.4, 0, 0.1)")
|
|
247
|
+
summer_save_scene(scenePath="res://main.tscn")
|
|
207
248
|
```
|
|
208
249
|
|
|
209
250
|
For pairing with a fire:
|
|
210
251
|
|
|
211
252
|
```
|
|
212
|
-
summer_add_node(parent="./World/Campfire", type="GPUParticles3D", name="Smoke")
|
|
213
|
-
summer_set_prop(path="./World/Campfire/Smoke",
|
|
214
|
-
summer_set_prop(path="./World/Campfire/Smoke",
|
|
253
|
+
summer_add_node(scenePath="res://main.tscn", parent="./World/Campfire", type="GPUParticles3D", name="Smoke")
|
|
254
|
+
summer_set_prop(scenePath="res://main.tscn", path="./World/Campfire/Smoke", key="position", value="Vector3(0, 1.5, 0)") # above flame top
|
|
255
|
+
summer_set_prop(scenePath="res://main.tscn", path="./World/Campfire/Smoke", key="rise_height", value=3.0)
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### 5b. Verify
|
|
259
|
+
|
|
260
|
+
The shader and the controller are compiled code. Check them before tuning anything.
|
|
261
|
+
|
|
262
|
+
```
|
|
263
|
+
summer_get_script_errors # smoke_controller.gd parsed?
|
|
264
|
+
summer_play
|
|
265
|
+
summer_get_debugger_errors # shader compile errors surface here at runtime
|
|
266
|
+
summer_stop
|
|
215
267
|
```
|
|
216
268
|
|
|
217
269
|
For a rocket trail (smoke follows the rocket):
|
|
@@ -323,13 +375,15 @@ color_old = Color(0.65, 0.68, 0.75)
|
|
|
323
375
|
- **Forgetting `cull_disabled`.** Billboarded quads disappear at certain camera angles otherwise.
|
|
324
376
|
- **`puff_lifetime < 1.0`.** Smoke needs time to spread out and dilute. Short-lifetime smoke looks like spray.
|
|
325
377
|
- **No scale curve.** Smoke that doesn't grow looks like a row of identical balls. The controller's `_make_scale_curve` ramps from 0.4× → 1.0× → end_mult.
|
|
378
|
+
- **A scale curve built without raising `Curve.max_value`.** `Curve` defaults to `max_value = 1.0` and silently clamps, so every `end_scale_multiplier` above 1.0 is inert and the smoke never grows. Set `c.max_value` before adding the points.
|
|
379
|
+
- **Reading `CUSTOM` in `fragment()`.** In a `spatial` shader `CUSTOM` does not exist at all ("SHADER ERROR: Unknown identifier in expression: 'CUSTOM'"). The particle built-in is `INSTANCE_CUSTOM`, it is vertex-stage only, and `INSTANCE_CUSTOM.x` is the rotation angle — the age is in `.y`.
|
|
326
380
|
- **`emission_energy > 0` on the shader's quad material.** Smoke isn't bright. Keep `unshaded` and let `ALBEDO` carry the look. (The shader as written has no EMISSION line — correct.)
|
|
327
381
|
- **Smoke local-space when paired with a moving emitter (rocket).** Set `local_coords = false` so puffs stay where they were emitted.
|
|
328
|
-
- **Skipping the angular velocity.** Static-rotation smoke looks weird. The controller adds ±25 deg/s for organic tumbling.
|
|
382
|
+
- **Skipping the angular velocity.** Static-rotation smoke looks weird. The controller adds ±25 deg/s for organic tumbling — but that only reaches the screen if the vertex billboard applies the `INSTANCE_CUSTOM.x` rotation, which is why section 2 uses the full block rather than the bare `VIEW_MATRIX * mat4(INV_VIEW_MATRIX[0..2], MODEL_MATRIX[3])` form.
|
|
329
383
|
|
|
330
384
|
## Performance notes
|
|
331
385
|
|
|
332
|
-
- Default 48 particles per smoke source
|
|
386
|
+
- Default 48 particles per smoke source is sub-millisecond on desktop. That is an order-of-magnitude expectation, not a measurement — profile your own scene.
|
|
333
387
|
- Heavy `density` at high count (128 particles, density 0.85) gets fillrate-bound — overdraw on a screen full of smoke. Budget ~256 smoke particles total visible at once.
|
|
334
388
|
- Mobile: drop default to 24, drop `puff_lifetime` to 2.5, drop `density` to 0.35.
|
|
335
389
|
- LOD: scale `amount_ratio` by `clamp(1.5 - dist_to_camera / 40.0, 0.10, 1.0)` for distant chimneys.
|
|
@@ -356,13 +410,20 @@ ALPHA *= clamp(d * depth_fade, 0.0, 1.0);
|
|
|
356
410
|
|
|
357
411
|
## Fallback (no MCP)
|
|
358
412
|
|
|
359
|
-
|
|
413
|
+
Section 5 is fully automatable — `summer_write_file` writes the shader and the
|
|
414
|
+
controller, `summer_create_scene` + `summer_add_node` + `summer_set_prop` +
|
|
415
|
+
`summer_save_scene` build and save `smoke.tscn`. Do not hand these steps to the user
|
|
416
|
+
when the MCP tools are available.
|
|
417
|
+
|
|
418
|
+
Without the MCP connection there is no engine to drive, so the user does it
|
|
419
|
+
manually in Summer Engine:
|
|
360
420
|
|
|
361
|
-
1. Create `addons/vfx/smoke/` with the
|
|
362
|
-
2.
|
|
421
|
+
1. Create `addons/vfx/_building-blocks/` and `addons/vfx/smoke/` with the files above.
|
|
422
|
+
2. Add a `GPUParticles3D`, attach `smoke_controller.gd`.
|
|
363
423
|
3. Set `process_material` to a new `ParticleProcessMaterial` (script configures it).
|
|
364
|
-
4. Set `draw_pass_1` to a new `QuadMesh` with `
|
|
424
|
+
4. Set `draw_pass_1` to a new `QuadMesh` with its `material` = `ShaderMaterial(smoke.gdshader)`.
|
|
365
425
|
5. Save as `smoke.tscn`; instantiate at every smoke source.
|
|
426
|
+
6. Check the Errors dock — a missing `.gdshaderinc` fails the whole shader.
|
|
366
427
|
|
|
367
428
|
## Handoff
|
|
368
429
|
|