summer-engine 1.5.0 → 2.2.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/{marketplace.json → .claude-plugin/marketplace.json} +3 -3
- package/.claude-plugin/plugin.json +52 -11
- package/.codex-plugin/plugin.json +69 -8
- package/.cursor-plugin/plugin.json +53 -12
- package/.factory-plugin/plugin.json +20 -0
- package/.opencode/INSTALL.md +75 -0
- package/.opencode/plugins/summer.js +9 -6
- package/AGENTS.md +51 -0
- package/CHANGELOG.md +31 -1
- package/CLAUDE.md +5 -0
- package/GEMINI.md +56 -0
- package/README.md +409 -80
- package/_persona/summer/SKILL.md +113 -0
- package/dist/bin/summer.js +0 -0
- package/dist/commands/assets.d.ts +2 -0
- package/dist/commands/assets.js +189 -0
- package/dist/commands/create.js +55 -17
- package/dist/commands/debug.d.ts +2 -0
- package/dist/commands/debug.js +127 -0
- package/dist/commands/install.js +2 -2
- package/dist/commands/list.js +24 -5
- package/dist/commands/project.d.ts +2 -0
- package/dist/commands/project.js +170 -0
- package/dist/commands/scene.d.ts +2 -0
- package/dist/commands/scene.js +290 -0
- package/dist/lib/banner.js +2 -2
- package/dist/lib/log-filters.d.ts +53 -0
- package/dist/lib/log-filters.js +130 -0
- package/dist/lib/plugin-manifests.test.d.ts +1 -0
- package/dist/lib/plugin-manifests.test.js +134 -0
- package/dist/lib/remote-templates.d.ts +25 -0
- package/dist/lib/remote-templates.js +86 -0
- package/dist/lib/skills-registry.d.ts +283 -3
- package/dist/lib/skills-registry.js +522 -2
- package/dist/lib/telemetry.d.ts +13 -0
- package/dist/lib/telemetry.js +63 -0
- package/dist/lib/with-engine.d.ts +14 -0
- package/dist/lib/with-engine.js +109 -0
- package/dist/mcp/tools/asset-tools.js +12 -13
- package/dist/mcp/tools/debug-tools.js +32 -9
- package/dist/mcp/tools/generate-tools.js +101 -0
- package/dist/mcp/tools/with-engine.js +4 -0
- package/gemini-extension.json +2 -2
- package/hooks/pre-commit-doctor.sh +0 -0
- package/hooks/run-hook.cmd +0 -0
- package/hooks/session-start.sh +0 -0
- package/package.json +11 -5
- package/skills/2d-assets/character-portrait/SKILL.md +196 -0
- package/skills/2d-assets/concept-art/SKILL.md +152 -0
- package/skills/2d-assets/pixel-art/SKILL.md +180 -0
- package/skills/2d-assets/skybox-panorama/SKILL.md +207 -0
- package/skills/2d-assets/sprite-sheet/SKILL.md +239 -0
- package/skills/2d-assets/tileable-texture/SKILL.md +205 -0
- package/skills/2d-assets/ui-graphics/SKILL.md +210 -0
- package/skills/3d-assets/character-model/SKILL.md +225 -0
- package/skills/3d-assets/environment-kit/SKILL.md +164 -0
- package/skills/3d-assets/organic-model/SKILL.md +192 -0
- package/skills/3d-assets/prop-model/SKILL.md +133 -0
- package/skills/3d-assets/vehicle-model/SKILL.md +202 -0
- package/skills/README.md +1 -1
- package/skills/_meta/skill-create/SKILL.md +1 -1
- package/skills/_meta/skill-improve/SKILL.md +1 -1
- package/skills/_meta/skill-test/SKILL.md +1 -1
- package/skills/_meta/using-summer/SKILL.md +130 -0
- package/skills/ai-and-npcs/design-npc/SKILL.md +3 -1
- package/skills/animation/animation-tree/SKILL.md +260 -0
- package/skills/animation/facial-and-lipsync/SKILL.md +282 -0
- package/skills/animation/generate-motion/SKILL.md +129 -0
- package/skills/animation/procedural-animation/SKILL.md +234 -0
- package/skills/animation/retarget/SKILL.md +172 -0
- package/skills/asset-pipeline/asset-strategy/SKILL.md +102 -212
- package/skills/audio/adaptive-music/SKILL.md +326 -0
- package/skills/audio/ambient-bed/SKILL.md +278 -0
- package/skills/audio/music-track/SKILL.md +268 -0
- package/skills/audio/sound-effect/SKILL.md +237 -0
- package/skills/audio/voice-line/SKILL.md +253 -0
- package/skills/catalog.yaml +266 -5
- package/skills/character-controllers/fps-controller/SKILL.md +1 -1
- package/skills/debugging/debug/SKILL.md +102 -82
- package/skills/rendering-and-lighting/3d-lighting/SKILL.md +1 -1
- package/skills/scene-and-project/browse-templates/SKILL.md +105 -0
- package/skills/scene-and-project/make-game/SKILL.md +159 -346
- package/skills/scene-and-project/new-project/SKILL.md +106 -0
- package/skills/scene-and-project/scene-composition/SKILL.md +1 -1
- package/skills/scripting-patterns/gdscript-patterns/SKILL.md +1 -1
- package/skills/ui-and-ux/ui-basics/SKILL.md +1 -1
- package/skills/video/animated-loop/SKILL.md +200 -0
- package/skills/video/cinematic-cutscene/SKILL.md +204 -0
- package/skills/video/trailer-shot/SKILL.md +203 -0
- package/skills/visual-effects/_building-blocks/additive-billboard-particles.md +28 -0
- package/skills/visual-effects/_building-blocks/noise-3d-fbm.gdshaderinc +45 -0
- package/skills/visual-effects/_building-blocks/trauma-shake-snippet.md +51 -0
- package/skills/visual-effects/{vfx → game-feel}/SKILL.md +1 -1
- package/skills/visual-effects/recipes/dissolve/SKILL.md +349 -0
- package/skills/visual-effects/recipes/fire/SKILL.md +387 -0
- package/skills/visual-effects/recipes/hit-spark/SKILL.md +330 -0
- package/skills/visual-effects/recipes/lightning/SKILL.md +372 -0
- package/skills/visual-effects/recipes/magic-glow/SKILL.md +377 -0
- package/skills/visual-effects/recipes/muzzle-flash/SKILL.md +326 -0
- package/skills/visual-effects/recipes/smoke/SKILL.md +381 -0
- package/skills/visual-effects/recipes/water-ripple/SKILL.md +340 -0
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: smoke
|
|
3
|
+
description: Use when authoring a smoke visual effect — a slow-rising column or puff of soft particles built with a noise + density falloff shader on a quad mesh, GPUParticles3D. Trigger on "smoke", "smoke trail", "puff", "chimney smoke", "smoke from a fire", "steam", "fog cloud", "explosion smoke".
|
|
4
|
+
license: MIT
|
|
5
|
+
compatibility: [Cursor, Claude Code, Windsurf, Codex]
|
|
6
|
+
category: visual-effects
|
|
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
|
|
9
|
+
paths: ["**/*.tscn", "**/*.gd", "**/*.gdshader", "addons/vfx/**"]
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# smoke — Soft Particle Smoke Plume
|
|
13
|
+
|
|
14
|
+
Smoke is the same particle pattern as fire — billboard quads emitted from a small disc, drifting upward — except the shader is dim, soft, low-emission, and tinted by depth instead of glowing. Slower spawn rate, longer lifetime, larger end scale. Pair it with `fire` for any campfire/torch/explosion. Used everywhere: chimney smoke, smoldering wreckage, smoke trails behind rockets, steam from vents, fog clouds.
|
|
15
|
+
|
|
16
|
+
## When to use
|
|
17
|
+
|
|
18
|
+
- "Smoke rising from the campfire."
|
|
19
|
+
- "Chimney smoke on the cottage."
|
|
20
|
+
- "Smoke trail behind the rocket."
|
|
21
|
+
- "Steam from the vent."
|
|
22
|
+
- "Smoke from the smoldering wreckage."
|
|
23
|
+
- "Fog cloud at the cave entrance."
|
|
24
|
+
- After spawning `fire`, smoke almost always belongs above it.
|
|
25
|
+
|
|
26
|
+
## When NOT to use
|
|
27
|
+
|
|
28
|
+
- The user wants flames, not smoke — use `fire`.
|
|
29
|
+
- The user wants a giant ground-hugging fog volume — use a `FogVolume` node with the `WorldEnvironment.volumetric_fog` shader, not a particle system. Particles don't scale to room-size fog.
|
|
30
|
+
- The user wants a 2D smoke effect — `canvas_item` shader, not this.
|
|
31
|
+
- The user wants the smoke to physically interact with the player (pushed by movement) — particles are visual; use a `FogVolume` + script that follows the player.
|
|
32
|
+
- The user wants thick black bonfire smoke that completely occludes — bump density way up, but consider a `FogVolume` for genuine visual occlusion.
|
|
33
|
+
|
|
34
|
+
## Recipe
|
|
35
|
+
|
|
36
|
+
### 1. Files to create
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
addons/vfx/smoke/smoke.gdshader
|
|
40
|
+
addons/vfx/smoke/smoke_controller.gd
|
|
41
|
+
addons/vfx/smoke/smoke.tscn
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### 2. Shader code
|
|
45
|
+
|
|
46
|
+
`addons/vfx/smoke/smoke.gdshader`:
|
|
47
|
+
|
|
48
|
+
```glsl
|
|
49
|
+
shader_type spatial;
|
|
50
|
+
render_mode unshaded, blend_mix, depth_draw_never, cull_disabled, shadows_disabled;
|
|
51
|
+
|
|
52
|
+
#include "res://addons/vfx/_building-blocks/noise-3d-fbm.gdshaderinc"
|
|
53
|
+
|
|
54
|
+
uniform vec4 smoke_color_young : source_color = vec4(0.85, 0.85, 0.85, 1.0); // freshly emitted
|
|
55
|
+
uniform vec4 smoke_color_old : source_color = vec4(0.45, 0.45, 0.45, 1.0); // late life
|
|
56
|
+
uniform float density : hint_range(0.0, 1.0) = 0.55;
|
|
57
|
+
uniform float noise_scale : hint_range(0.5, 8.0) = 2.0;
|
|
58
|
+
uniform float noise_speed : hint_range(0.0, 2.0) = 0.5;
|
|
59
|
+
uniform float soft_edge : hint_range(0.01, 0.5) = 0.30;
|
|
60
|
+
uniform float depth_fade : hint_range(0.0, 4.0) = 1.0; // soft against geometry behind
|
|
61
|
+
|
|
62
|
+
void vertex() {
|
|
63
|
+
MODELVIEW_MATRIX = VIEW_MATRIX * mat4(
|
|
64
|
+
INV_VIEW_MATRIX[0],
|
|
65
|
+
INV_VIEW_MATRIX[1],
|
|
66
|
+
INV_VIEW_MATRIX[2],
|
|
67
|
+
MODEL_MATRIX[3]
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
void fragment() {
|
|
72
|
+
float age = CUSTOM.x;
|
|
73
|
+
|
|
74
|
+
// Distort UVs with slow noise.
|
|
75
|
+
vec3 np = vec3(UV * noise_scale, TIME * noise_speed + CUSTOM.y * 10.0);
|
|
76
|
+
float n = fbm3(np);
|
|
77
|
+
|
|
78
|
+
// Soft round mask, modulated by noise so the silhouette is wispy not circular.
|
|
79
|
+
vec2 c = UV - vec2(0.5);
|
|
80
|
+
float r = length(c) * 2.0;
|
|
81
|
+
float mask = smoothstep(1.0, 1.0 - soft_edge, r);
|
|
82
|
+
mask *= (0.6 + 0.4 * n); // bite chunks out of the puff
|
|
83
|
+
|
|
84
|
+
// Fade in fast, fade out slow.
|
|
85
|
+
float alpha_age = smoothstep(0.0, 0.15, age) * (1.0 - smoothstep(0.6, 1.0, age));
|
|
86
|
+
|
|
87
|
+
// Color drifts young → old as it ages (cools off / dilutes).
|
|
88
|
+
vec3 col = mix(smoke_color_young.rgb, smoke_color_old.rgb, age);
|
|
89
|
+
|
|
90
|
+
ALBEDO = col;
|
|
91
|
+
ALPHA = mask * alpha_age * density;
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Note: for soft-particle depth fade against geometry behind the puff, the proper way is sampling DEPTH_TEXTURE; for clarity we keep the shader straightforward and rely on `depth_draw_never` + alpha for soft-look. If you need true soft-particle blending, add a DEPTH_TEXTURE sample in `fragment` — see Edge Cases.
|
|
96
|
+
|
|
97
|
+
### 3. GDScript controller
|
|
98
|
+
|
|
99
|
+
`addons/vfx/smoke/smoke_controller.gd`:
|
|
100
|
+
|
|
101
|
+
```gdscript
|
|
102
|
+
@tool
|
|
103
|
+
class_name SmokeController
|
|
104
|
+
extends GPUParticles3D
|
|
105
|
+
|
|
106
|
+
@export_group("Plume size")
|
|
107
|
+
@export_range(0.1, 8.0) var rise_height: float = 3.0 :
|
|
108
|
+
set(v): rise_height = v; _apply()
|
|
109
|
+
@export_range(0.05, 3.0) var spawn_radius: float = 0.20 :
|
|
110
|
+
set(v): spawn_radius = v; _apply()
|
|
111
|
+
@export_range(0.5, 4.0) var end_scale_multiplier: float = 2.5 :
|
|
112
|
+
set(v): end_scale_multiplier = v; _apply()
|
|
113
|
+
|
|
114
|
+
@export_group("Plume intensity")
|
|
115
|
+
@export_range(8, 512) var particle_count: int = 64 :
|
|
116
|
+
set(v): particle_count = v; _apply()
|
|
117
|
+
@export_range(1.0, 8.0) var puff_lifetime: float = 4.0 :
|
|
118
|
+
set(v): puff_lifetime = v; _apply()
|
|
119
|
+
@export_range(0.0, 1.0) var density: float = 0.55
|
|
120
|
+
|
|
121
|
+
@export_group("Wind")
|
|
122
|
+
@export var wind_direction: Vector3 = Vector3(0.3, 0.0, 0.0) :
|
|
123
|
+
set(v): wind_direction = v; _apply()
|
|
124
|
+
@export_range(0.0, 5.0) var wind_strength: float = 0.4 :
|
|
125
|
+
set(v): wind_strength = v; _apply()
|
|
126
|
+
|
|
127
|
+
@export_group("Color")
|
|
128
|
+
@export var color_young: Color = Color(0.85, 0.85, 0.85)
|
|
129
|
+
@export var color_old: Color = Color(0.45, 0.45, 0.45)
|
|
130
|
+
|
|
131
|
+
func _ready() -> void:
|
|
132
|
+
one_shot = false
|
|
133
|
+
explosiveness = 0.0
|
|
134
|
+
_apply()
|
|
135
|
+
|
|
136
|
+
func _apply() -> void:
|
|
137
|
+
amount = particle_count
|
|
138
|
+
lifetime = puff_lifetime
|
|
139
|
+
|
|
140
|
+
var pm := process_material as ParticleProcessMaterial
|
|
141
|
+
if pm:
|
|
142
|
+
pm.emission_shape = ParticleProcessMaterial.EMISSION_SHAPE_SPHERE
|
|
143
|
+
pm.emission_sphere_radius = spawn_radius
|
|
144
|
+
pm.direction = Vector3.UP
|
|
145
|
+
pm.spread = 8.0
|
|
146
|
+
pm.initial_velocity_min = rise_height / puff_lifetime * 0.7
|
|
147
|
+
pm.initial_velocity_max = rise_height / puff_lifetime * 1.2
|
|
148
|
+
pm.gravity = wind_direction.normalized() * wind_strength
|
|
149
|
+
pm.scale_min = spawn_radius * 1.2
|
|
150
|
+
pm.scale_max = spawn_radius * 1.8
|
|
151
|
+
pm.scale_curve = _make_scale_curve(end_scale_multiplier)
|
|
152
|
+
pm.angular_velocity_min = -25.0
|
|
153
|
+
pm.angular_velocity_max = 25.0
|
|
154
|
+
|
|
155
|
+
if draw_pass_1:
|
|
156
|
+
var mesh := draw_pass_1 as QuadMesh
|
|
157
|
+
var mat := mesh.surface_get_material(0) as ShaderMaterial
|
|
158
|
+
if mat:
|
|
159
|
+
mat.set_shader_parameter("smoke_color_young", color_young)
|
|
160
|
+
mat.set_shader_parameter("smoke_color_old", color_old)
|
|
161
|
+
mat.set_shader_parameter("density", density)
|
|
162
|
+
|
|
163
|
+
func _make_scale_curve(end_mult: float) -> CurveTexture:
|
|
164
|
+
var c := Curve.new()
|
|
165
|
+
c.add_point(Vector2(0.0, 0.4))
|
|
166
|
+
c.add_point(Vector2(0.3, 1.0))
|
|
167
|
+
c.add_point(Vector2(1.0, end_mult))
|
|
168
|
+
var ct := CurveTexture.new()
|
|
169
|
+
ct.curve = c
|
|
170
|
+
return ct
|
|
171
|
+
|
|
172
|
+
func puff(amount_scalar: float = 1.0) -> void:
|
|
173
|
+
# One-shot burst on top of the continuous emission (e.g., chimney puffs from a sneeze).
|
|
174
|
+
var saved := amount_ratio
|
|
175
|
+
amount_ratio = amount_scalar
|
|
176
|
+
restart()
|
|
177
|
+
var t := create_tween()
|
|
178
|
+
t.tween_interval(0.1)
|
|
179
|
+
t.tween_callback(func() -> void: amount_ratio = saved)
|
|
180
|
+
|
|
181
|
+
func stop_smoke(fade_seconds: float = 1.5) -> void:
|
|
182
|
+
var t := create_tween()
|
|
183
|
+
t.tween_property(self, "amount_ratio", 0.0, fade_seconds)
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### 4. Node tree
|
|
187
|
+
|
|
188
|
+
```
|
|
189
|
+
GPUParticles3D ("Smoke") [script: smoke_controller.gd]
|
|
190
|
+
├── process_material: ParticleProcessMaterial
|
|
191
|
+
└── draw_pass_1: QuadMesh (size 1.0 × 1.0)
|
|
192
|
+
└── surface_material_override[0]: ShaderMaterial (shader: smoke.gdshader)
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### 5. Wire it in (MCP calls)
|
|
196
|
+
|
|
197
|
+
For chimney smoke or fire pairing, parent the Smoke just above the heat source:
|
|
198
|
+
|
|
199
|
+
```
|
|
200
|
+
summer_add_node(parent="./World/Cottage/Chimney", type="GPUParticles3D", name="Smoke")
|
|
201
|
+
summer_set_prop(path="./World/Cottage/Chimney/Smoke", property="script", value="res://addons/vfx/smoke/smoke_controller.gd")
|
|
202
|
+
summer_set_prop(path="./World/Cottage/Chimney/Smoke", property="position", value="Vector3(0, 0.3, 0)")
|
|
203
|
+
summer_set_prop(path="./World/Cottage/Chimney/Smoke", property="rise_height", value=4.0)
|
|
204
|
+
summer_set_prop(path="./World/Cottage/Chimney/Smoke", property="particle_count", value=48)
|
|
205
|
+
summer_set_prop(path="./World/Cottage/Chimney/Smoke", property="wind_direction", value="Vector3(0.4, 0, 0.1)")
|
|
206
|
+
summer_save_scene
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
For pairing with a fire:
|
|
210
|
+
|
|
211
|
+
```
|
|
212
|
+
summer_add_node(parent="./World/Campfire", type="GPUParticles3D", name="Smoke")
|
|
213
|
+
summer_set_prop(path="./World/Campfire/Smoke", property="position", value="Vector3(0, 1.5, 0)") # above flame top
|
|
214
|
+
summer_set_prop(path="./World/Campfire/Smoke", property="rise_height", value=3.0)
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
For a rocket trail (smoke follows the rocket):
|
|
218
|
+
|
|
219
|
+
```gdscript
|
|
220
|
+
# On the Rocket scene:
|
|
221
|
+
$Smoke.local_coords = false # smoke stays in world space, rocket leaves it behind
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### 6. Parameters to tune
|
|
225
|
+
|
|
226
|
+
| Parameter | Range | Effect |
|
|
227
|
+
|---|---|---|
|
|
228
|
+
| `particle_count` (`amount`) | 16–256 | density of the column |
|
|
229
|
+
| `puff_lifetime` | 1.0–8.0 s | how long each puff is visible (longer = taller column) |
|
|
230
|
+
| `rise_height` | 0.1–8.0 m | how far each puff travels upward |
|
|
231
|
+
| `spawn_radius` | 0.05–3.0 m | spawn disc + initial puff size |
|
|
232
|
+
| `end_scale_multiplier` | 0.5–4.0× | how much each puff grows over its life |
|
|
233
|
+
| `density` | 0.0–1.0 | overall opacity of every puff |
|
|
234
|
+
| `wind_direction` | Vector3 | drift direction (also bends the column) |
|
|
235
|
+
| `wind_strength` | 0.0–5.0 | horizontal drift speed |
|
|
236
|
+
| `color_young` / `color_old` | Color | tint at birth and at death |
|
|
237
|
+
|
|
238
|
+
## Cookbook — named variants
|
|
239
|
+
|
|
240
|
+
### chimney-smoke (default)
|
|
241
|
+
|
|
242
|
+
Light gray, gentle, drifts on a slight wind.
|
|
243
|
+
|
|
244
|
+
```
|
|
245
|
+
particle_count = 48
|
|
246
|
+
puff_lifetime = 4.0
|
|
247
|
+
rise_height = 4.0
|
|
248
|
+
spawn_radius = 0.30
|
|
249
|
+
end_scale_multiplier = 2.5
|
|
250
|
+
density = 0.55
|
|
251
|
+
wind_direction = Vector3(0.3, 0.0, 0.1)
|
|
252
|
+
wind_strength = 0.4
|
|
253
|
+
color_young = Color(0.85, 0.85, 0.85)
|
|
254
|
+
color_old = Color(0.55, 0.55, 0.55)
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### black-fire-smoke
|
|
258
|
+
|
|
259
|
+
Burning oil / tires. Pair with `fire` set to `bonfire`.
|
|
260
|
+
|
|
261
|
+
```
|
|
262
|
+
particle_count = 96
|
|
263
|
+
puff_lifetime = 5.0
|
|
264
|
+
rise_height = 5.0
|
|
265
|
+
spawn_radius = 0.60
|
|
266
|
+
end_scale_multiplier = 3.0
|
|
267
|
+
density = 0.85
|
|
268
|
+
color_young = Color(0.20, 0.18, 0.16)
|
|
269
|
+
color_old = Color(0.05, 0.05, 0.05)
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### steam-vent
|
|
273
|
+
|
|
274
|
+
White, fast-rising, short-lived.
|
|
275
|
+
|
|
276
|
+
```
|
|
277
|
+
particle_count = 64
|
|
278
|
+
puff_lifetime = 1.6
|
|
279
|
+
rise_height = 2.5
|
|
280
|
+
spawn_radius = 0.10
|
|
281
|
+
end_scale_multiplier = 2.0
|
|
282
|
+
density = 0.45
|
|
283
|
+
wind_strength = 0.0
|
|
284
|
+
color_young = Color(1.0, 1.0, 1.0)
|
|
285
|
+
color_old = Color(0.95, 0.95, 1.0)
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### rocket-trail
|
|
289
|
+
|
|
290
|
+
Long-lived, narrow spawn, follows the rocket.
|
|
291
|
+
|
|
292
|
+
```
|
|
293
|
+
particle_count = 128
|
|
294
|
+
puff_lifetime = 3.0
|
|
295
|
+
rise_height = 0.5 # local Y; the rocket's velocity drives it
|
|
296
|
+
spawn_radius = 0.08
|
|
297
|
+
end_scale_multiplier = 4.0
|
|
298
|
+
density = 0.50
|
|
299
|
+
local_coords = false # set on the node
|
|
300
|
+
color_young = Color(0.95, 0.95, 0.95)
|
|
301
|
+
color_old = Color(0.40, 0.40, 0.40)
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### fog-bank
|
|
305
|
+
|
|
306
|
+
Slow, wide, ground-hugging. (For very large fog, use `FogVolume` instead.)
|
|
307
|
+
|
|
308
|
+
```
|
|
309
|
+
particle_count = 128
|
|
310
|
+
puff_lifetime = 8.0
|
|
311
|
+
rise_height = 0.3
|
|
312
|
+
spawn_radius = 3.0
|
|
313
|
+
end_scale_multiplier = 1.5
|
|
314
|
+
density = 0.30
|
|
315
|
+
gravity = Vector3(0, -0.05, 0) # slightly drift downward
|
|
316
|
+
color_young = Color(0.75, 0.78, 0.85)
|
|
317
|
+
color_old = Color(0.65, 0.68, 0.75)
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
## Anti-patterns
|
|
321
|
+
|
|
322
|
+
- **Using `blend_add` like fire.** Smoke is `blend_mix` (alpha-blended). Additive smoke looks like glowing dust.
|
|
323
|
+
- **Forgetting `cull_disabled`.** Billboarded quads disappear at certain camera angles otherwise.
|
|
324
|
+
- **`puff_lifetime < 1.0`.** Smoke needs time to spread out and dilute. Short-lifetime smoke looks like spray.
|
|
325
|
+
- **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.
|
|
326
|
+
- **`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
|
+
- **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.
|
|
329
|
+
|
|
330
|
+
## Performance notes
|
|
331
|
+
|
|
332
|
+
- Default 48 particles per smoke source: ~0.03 ms on desktop.
|
|
333
|
+
- 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
|
+
- Mobile: drop default to 24, drop `puff_lifetime` to 2.5, drop `density` to 0.35.
|
|
335
|
+
- LOD: scale `amount_ratio` by `clamp(1.5 - dist_to_camera / 40.0, 0.10, 1.0)` for distant chimneys.
|
|
336
|
+
- The depth-fade soft-particle technique (sampling DEPTH_TEXTURE in fragment) costs an extra texture read per pixel — only enable for foreground smoke close to walls.
|
|
337
|
+
|
|
338
|
+
## Edge cases
|
|
339
|
+
|
|
340
|
+
- **Smoke clipping into walls/ceilings.** The basic shader doesn't depth-fade. Add this to the fragment for soft particles:
|
|
341
|
+
|
|
342
|
+
```glsl
|
|
343
|
+
uniform sampler2D depth_tex : hint_depth_texture;
|
|
344
|
+
// in fragment, after the mask line:
|
|
345
|
+
float scene_d = texture(depth_tex, SCREEN_UV).r;
|
|
346
|
+
vec4 wpos = INV_PROJECTION_MATRIX * vec4(SCREEN_UV * 2.0 - 1.0, scene_d * 2.0 - 1.0, 1.0);
|
|
347
|
+
wpos.xyz /= wpos.w;
|
|
348
|
+
float d = abs(VERTEX.z - wpos.z);
|
|
349
|
+
ALPHA *= clamp(d * depth_fade, 0.0, 1.0);
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
- **Wind-driven smoke on a ship at sea.** Drive `wind_direction` from a global wind script polled in `_process`.
|
|
353
|
+
- **Smoke from an explosion (one-shot huge puff).** Use `puff()` API, set `one_shot = true`, increase `amount` to 256 for the burst, then free the node after `puff_lifetime`.
|
|
354
|
+
- **Smoke at very low light (night scene).** With `unshaded`, smoke is the same brightness day or night — looks bright at night. Either change to `shading_mode lit` and accept lighting cost, or tween `density` down with sun angle.
|
|
355
|
+
- **Smoke for an underwater plume (silt).** Drop `wind_strength` to 0, `gravity` to slight upward, color tan: `Color(0.55, 0.50, 0.40)`.
|
|
356
|
+
|
|
357
|
+
## Fallback (no MCP)
|
|
358
|
+
|
|
359
|
+
VFX is code, no MCP required:
|
|
360
|
+
|
|
361
|
+
1. Create `addons/vfx/smoke/` with the three files.
|
|
362
|
+
2. In Godot, add a `GPUParticles3D`, attach `smoke_controller.gd`.
|
|
363
|
+
3. Set `process_material` to a new `ParticleProcessMaterial` (script configures it).
|
|
364
|
+
4. Set `draw_pass_1` to a new `QuadMesh` with `surface_material_override[0]` = `ShaderMaterial(smoke.gdshader)`.
|
|
365
|
+
5. Save as `smoke.tscn`; instantiate at every smoke source.
|
|
366
|
+
|
|
367
|
+
## Handoff
|
|
368
|
+
|
|
369
|
+
After firing this recipe, suggest:
|
|
370
|
+
|
|
371
|
+
- `summer:visual-effects/recipes/fire` — the partner; almost any fire wants smoke 0.5–2 m above the flame top.
|
|
372
|
+
- `summer:visual-effects/recipes/dissolve` — pair `black-fire-smoke` with a dissolving target for "the bandit camp burns and ashes drift away."
|
|
373
|
+
- `summer:visual-effects/recipes/lightning` — for a smoke flash from a lightning strike (use `puff()` on impact).
|
|
374
|
+
- `summer:audio/sound-effect` — generate `gentle wind through smoke, soft hiss, 4s loop` for chimneys.
|
|
375
|
+
|
|
376
|
+
## See also
|
|
377
|
+
|
|
378
|
+
- `_building-blocks/noise-3d-fbm.gdshaderinc` — the FBM noise this shader includes
|
|
379
|
+
- `summer:visual-effects/recipes/fire` — sister recipe, almost always paired
|
|
380
|
+
- `summer:visual-effects/recipes/dissolve` — for ash particles after disintegration
|
|
381
|
+
- `summer:gdscript-patterns` — for the controller idioms (signals, exports, `@tool`)
|