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,372 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: lightning
|
|
3
|
+
description: Use when authoring a lightning bolt visual effect — procedural jagged path drawn via ImmediateMesh, glow shader, sparks at endpoints, screen shake. Trigger on "lightning bolt", "chain lightning", "electric attack", "tesla coil", "shock spell", "thunderbolt", "energy beam".
|
|
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
|
+
# lightning — Procedural Jagged Bolt
|
|
13
|
+
|
|
14
|
+
A lightning bolt is a noisy line from A to B, drawn for ~150 ms with a glow shader, sparks at both endpoints, and camera shake on cast. The recipe builds the jagged path in GDScript (midpoint displacement on a polyline), feeds the verts into an `ImmediateMesh`, applies a `ShaderMaterial` for additive bloom + flicker, spawns `hit-spark` particles at each endpoint, and calls `CameraShake.add_trauma(0.6)` for the impact. Used for shock spells, tesla coils, weapon discharges, electric enemies.
|
|
15
|
+
|
|
16
|
+
## When to use
|
|
17
|
+
|
|
18
|
+
- "Cast a lightning bolt from the wizard's hand to the enemy."
|
|
19
|
+
- "Chain lightning from one enemy to the next." (call `cast_lightning` per segment)
|
|
20
|
+
- "Tesla coil firing at the player every 2 seconds."
|
|
21
|
+
- "Energy weapon laser." (use the `laser-beam` variant)
|
|
22
|
+
- "Lightning strike from the sky."
|
|
23
|
+
- The user wants a *short, dramatic* energy connection between two points.
|
|
24
|
+
|
|
25
|
+
## When NOT to use
|
|
26
|
+
|
|
27
|
+
- The user wants a continuous beam that holds (e.g., laser sniper holding the trigger) — use the same shader on a `MeshInstance3D` cylinder with continuous emission, not the `ImmediateMesh` one-shot pattern. (See variants for `held-beam`.)
|
|
28
|
+
- The user wants ambient electricity *on* an object (Frankenstein arcs) — use a custom shader on the mesh with screen-space arcs; this recipe is for two-point bolts.
|
|
29
|
+
- The user wants a 2D lightning effect on UI — `canvas_item` shader, not this.
|
|
30
|
+
- The user wants a tracer line for a bullet — use a simple `Line3D`/`ImmediateMesh` without the noise displacement; skip this recipe.
|
|
31
|
+
|
|
32
|
+
## Recipe
|
|
33
|
+
|
|
34
|
+
### 1. Files to create
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
addons/vfx/lightning/lightning.gdshader # spatial shader for the bolt mesh
|
|
38
|
+
addons/vfx/lightning/lightning_caster.gd # build path, draw, animate, spawn endpoints, shake
|
|
39
|
+
addons/vfx/lightning/lightning.tscn # reusable scene
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### 2. Shader code
|
|
43
|
+
|
|
44
|
+
`addons/vfx/lightning/lightning.gdshader`:
|
|
45
|
+
|
|
46
|
+
```glsl
|
|
47
|
+
shader_type spatial;
|
|
48
|
+
render_mode unshaded, blend_add, depth_draw_never, cull_disabled, shadows_disabled;
|
|
49
|
+
|
|
50
|
+
uniform vec4 bolt_color : source_color = vec4(0.55, 0.75, 1.00, 1.0);
|
|
51
|
+
uniform vec4 core_color : source_color = vec4(1.00, 1.00, 1.00, 1.0);
|
|
52
|
+
uniform float intensity : hint_range(0.0, 1.0) = 1.0; // driven by the controller
|
|
53
|
+
uniform float emission_boost: hint_range(0.0, 16.0) = 8.0;
|
|
54
|
+
uniform float thickness : hint_range(0.05, 1.0) = 0.30; // visual halo width
|
|
55
|
+
uniform float flicker_rate : hint_range(0.0, 60.0) = 30.0;
|
|
56
|
+
|
|
57
|
+
void vertex() {
|
|
58
|
+
// Billboard each segment toward the camera around its own forward axis.
|
|
59
|
+
vec3 right = INV_VIEW_MATRIX[0].xyz;
|
|
60
|
+
vec3 up = INV_VIEW_MATRIX[1].xyz;
|
|
61
|
+
VERTEX += UV.y > 0.5 ? up * thickness * 0.5 : -up * thickness * 0.5;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
void fragment() {
|
|
65
|
+
// V across the line gives 0 (top) → 1 (bottom); recenter to ±1.
|
|
66
|
+
float v = (UV.y - 0.5) * 2.0;
|
|
67
|
+
float core = smoothstep(1.0, 0.0, abs(v));
|
|
68
|
+
core = pow(core, 3.0);
|
|
69
|
+
float halo = smoothstep(1.0, 0.0, abs(v));
|
|
70
|
+
halo = pow(halo, 0.7);
|
|
71
|
+
|
|
72
|
+
// Per-bolt flicker (random per fragment time slice).
|
|
73
|
+
float t = floor(TIME * flicker_rate) / flicker_rate;
|
|
74
|
+
float flick = 0.85 + 0.30 * fract(sin(t * 91.7) * 43758.5);
|
|
75
|
+
|
|
76
|
+
vec3 col = mix(bolt_color.rgb, core_color.rgb, core);
|
|
77
|
+
float a = (core + halo * 0.4) * intensity * flick;
|
|
78
|
+
|
|
79
|
+
ALBEDO = col;
|
|
80
|
+
EMISSION = col * emission_boost * intensity * flick * (core + halo * 0.5);
|
|
81
|
+
ALPHA = clamp(a, 0.0, 1.0);
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### 3. GDScript controller
|
|
86
|
+
|
|
87
|
+
`addons/vfx/lightning/lightning_caster.gd`:
|
|
88
|
+
|
|
89
|
+
```gdscript
|
|
90
|
+
@tool
|
|
91
|
+
class_name LightningCaster
|
|
92
|
+
extends Node3D
|
|
93
|
+
|
|
94
|
+
## Static-style helper. Add one anywhere in the scene; call cast_lightning(from, to) anytime.
|
|
95
|
+
|
|
96
|
+
const LIFETIME := 0.15
|
|
97
|
+
const SEGMENTS := 18 ## subdivisions of the bolt
|
|
98
|
+
const DISPLACEMENT := 0.35 ## meters of jaggedness per segment
|
|
99
|
+
const BRANCH_CHANCE := 0.25 ## probability of a forked sub-bolt per segment
|
|
100
|
+
const BRANCH_LENGTH := 0.8 ## relative length of a forked branch
|
|
101
|
+
|
|
102
|
+
@export var bolt_color: Color = Color(0.55, 0.75, 1.00)
|
|
103
|
+
@export var core_color: Color = Color(1.00, 1.00, 1.00)
|
|
104
|
+
@export var emission_boost: float = 8.0
|
|
105
|
+
@export var thickness: float = 0.30
|
|
106
|
+
@export var trauma_amount: float = 0.6
|
|
107
|
+
@export var spawn_endpoint_sparks: bool = true
|
|
108
|
+
@export_file("*.tscn") var spark_scene_path: String = "res://addons/vfx/hit-spark/hit_spark.tscn"
|
|
109
|
+
|
|
110
|
+
## Public API. Call this to fire a bolt.
|
|
111
|
+
func cast_lightning(from: Vector3, to: Vector3, intensity_scale: float = 1.0) -> void:
|
|
112
|
+
var bolt := _build_bolt_node(from, to, intensity_scale)
|
|
113
|
+
add_child(bolt)
|
|
114
|
+
bolt.global_position = Vector3.ZERO
|
|
115
|
+
|
|
116
|
+
if spawn_endpoint_sparks:
|
|
117
|
+
_spawn_sparks(from)
|
|
118
|
+
_spawn_sparks(to)
|
|
119
|
+
|
|
120
|
+
if Engine.has_singleton("CameraShake") or _has_autoload("CameraShake"):
|
|
121
|
+
var cs := Engine.get_main_loop().root.get_node_or_null("/root/CameraShake")
|
|
122
|
+
if cs and cs.has_method("add_trauma"):
|
|
123
|
+
cs.add_trauma(trauma_amount * intensity_scale)
|
|
124
|
+
|
|
125
|
+
func _has_autoload(name: String) -> bool:
|
|
126
|
+
var root := Engine.get_main_loop().root
|
|
127
|
+
return root and root.has_node("/root/" + name)
|
|
128
|
+
|
|
129
|
+
func _build_bolt_node(from: Vector3, to: Vector3, intensity_scale: float) -> Node3D:
|
|
130
|
+
var holder := Node3D.new()
|
|
131
|
+
var im := MeshInstance3D.new()
|
|
132
|
+
var mesh := ImmediateMesh.new()
|
|
133
|
+
im.mesh = mesh
|
|
134
|
+
var mat := ShaderMaterial.new()
|
|
135
|
+
mat.shader = preload("res://addons/vfx/lightning/lightning.gdshader")
|
|
136
|
+
mat.set_shader_parameter("bolt_color", bolt_color)
|
|
137
|
+
mat.set_shader_parameter("core_color", core_color)
|
|
138
|
+
mat.set_shader_parameter("emission_boost", emission_boost)
|
|
139
|
+
mat.set_shader_parameter("thickness", thickness)
|
|
140
|
+
mat.set_shader_parameter("intensity", intensity_scale)
|
|
141
|
+
im.material_override = mat
|
|
142
|
+
holder.add_child(im)
|
|
143
|
+
|
|
144
|
+
var path := _generate_path(from, to, SEGMENTS, DISPLACEMENT)
|
|
145
|
+
_draw_polyline(mesh, path)
|
|
146
|
+
|
|
147
|
+
# Optional forked branches.
|
|
148
|
+
for i in range(1, path.size() - 1):
|
|
149
|
+
if randf() < BRANCH_CHANCE:
|
|
150
|
+
var dir := (path[i] - path[i - 1]).normalized()
|
|
151
|
+
var perp := dir.cross(Vector3.UP).normalized()
|
|
152
|
+
if perp.length_squared() < 0.01:
|
|
153
|
+
perp = dir.cross(Vector3.RIGHT).normalized()
|
|
154
|
+
var branch_end: Vector3 = path[i] + (dir + perp * randf_range(-1.5, 1.5)) * (to - from).length() * BRANCH_LENGTH * 0.25
|
|
155
|
+
var branch_path := _generate_path(path[i], branch_end, SEGMENTS / 3, DISPLACEMENT * 0.6)
|
|
156
|
+
_draw_polyline(mesh, branch_path)
|
|
157
|
+
|
|
158
|
+
var t := holder.create_tween()
|
|
159
|
+
t.tween_method(func(v: float) -> void:
|
|
160
|
+
mat.set_shader_parameter("intensity", v * intensity_scale),
|
|
161
|
+
1.0, 0.0, LIFETIME).set_trans(Tween.TRANS_EXPO).set_ease(Tween.EASE_IN)
|
|
162
|
+
t.tween_callback(holder.queue_free)
|
|
163
|
+
return holder
|
|
164
|
+
|
|
165
|
+
func _generate_path(a: Vector3, b: Vector3, n: int, displacement: float) -> PackedVector3Array:
|
|
166
|
+
var path: PackedVector3Array = []
|
|
167
|
+
var dir := (b - a).normalized()
|
|
168
|
+
var perp1 := dir.cross(Vector3.UP).normalized()
|
|
169
|
+
if perp1.length_squared() < 0.01:
|
|
170
|
+
perp1 = dir.cross(Vector3.RIGHT).normalized()
|
|
171
|
+
var perp2 := dir.cross(perp1).normalized()
|
|
172
|
+
for i in n + 1:
|
|
173
|
+
var t := float(i) / float(n)
|
|
174
|
+
var p := a.lerp(b, t)
|
|
175
|
+
# Falloff at endpoints so they meet cleanly.
|
|
176
|
+
var falloff := sin(t * PI)
|
|
177
|
+
var jitter := perp1 * randf_range(-1.0, 1.0) + perp2 * randf_range(-1.0, 1.0)
|
|
178
|
+
p += jitter * displacement * falloff
|
|
179
|
+
path.append(p)
|
|
180
|
+
return path
|
|
181
|
+
|
|
182
|
+
func _draw_polyline(mesh: ImmediateMesh, points: PackedVector3Array) -> void:
|
|
183
|
+
if points.size() < 2: return
|
|
184
|
+
mesh.surface_begin(Mesh.PRIMITIVE_TRIANGLE_STRIP)
|
|
185
|
+
for i in points.size():
|
|
186
|
+
var v: float = float(i) / float(points.size() - 1)
|
|
187
|
+
mesh.surface_set_uv(Vector2(v, 0.0))
|
|
188
|
+
mesh.surface_add_vertex(points[i])
|
|
189
|
+
mesh.surface_set_uv(Vector2(v, 1.0))
|
|
190
|
+
mesh.surface_add_vertex(points[i])
|
|
191
|
+
mesh.surface_end()
|
|
192
|
+
|
|
193
|
+
func _spawn_sparks(at: Vector3) -> void:
|
|
194
|
+
if not ResourceLoader.exists(spark_scene_path):
|
|
195
|
+
return
|
|
196
|
+
var sparks := load(spark_scene_path).instantiate()
|
|
197
|
+
add_child(sparks)
|
|
198
|
+
sparks.global_position = at
|
|
199
|
+
if sparks.has_method("restart"):
|
|
200
|
+
sparks.restart()
|
|
201
|
+
var t := sparks.create_tween()
|
|
202
|
+
t.tween_interval(0.5)
|
|
203
|
+
t.tween_callback(sparks.queue_free)
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### 4. Node tree
|
|
207
|
+
|
|
208
|
+
```
|
|
209
|
+
Node3D ("LightningCaster") [script: lightning_caster.gd, autoload-friendly]
|
|
210
|
+
└── (children created at runtime per cast: ImmediateMesh + sparks scenes)
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Recommended: register one `LightningCaster` as an autoload (`/root/Lightning`) so any system can call `Lightning.cast_lightning(from, to)` from anywhere.
|
|
214
|
+
|
|
215
|
+
### 5. Wire it in (MCP calls)
|
|
216
|
+
|
|
217
|
+
Place one `LightningCaster` per "world" (or autoload it), then call from gameplay code.
|
|
218
|
+
|
|
219
|
+
```
|
|
220
|
+
summer_add_node(parent="./World", type="Node3D", name="LightningCaster")
|
|
221
|
+
summer_set_prop(path="./World/LightningCaster", property="script", value="res://addons/vfx/lightning/lightning_caster.gd")
|
|
222
|
+
summer_set_prop(path="./World/LightningCaster", property="bolt_color", value="Color(0.55, 0.75, 1.0)")
|
|
223
|
+
summer_set_prop(path="./World/LightningCaster", property="trauma_amount", value=0.6)
|
|
224
|
+
summer_save_scene
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Then from the spell code:
|
|
228
|
+
|
|
229
|
+
```gdscript
|
|
230
|
+
var origin: Vector3 = $Wizard/HandTip.global_position
|
|
231
|
+
var target: Vector3 = enemy.global_position
|
|
232
|
+
$World/LightningCaster.cast_lightning(origin, target)
|
|
233
|
+
enemy.take_damage(35)
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
For chain lightning:
|
|
237
|
+
|
|
238
|
+
```gdscript
|
|
239
|
+
var prev: Vector3 = origin
|
|
240
|
+
for enemy in nearest_enemies(prev, 4):
|
|
241
|
+
$World/LightningCaster.cast_lightning(prev, enemy.global_position, 0.85)
|
|
242
|
+
enemy.take_damage(20)
|
|
243
|
+
prev = enemy.global_position
|
|
244
|
+
await get_tree().create_timer(0.05).timeout
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### 6. Parameters to tune
|
|
248
|
+
|
|
249
|
+
| Parameter | Range | Effect |
|
|
250
|
+
|---|---|---|
|
|
251
|
+
| `LIFETIME` (const) | 0.05–0.40 s | how long the bolt is on screen (0.15 is the sweet spot) |
|
|
252
|
+
| `SEGMENTS` (const) | 6–32 | path subdivisions; more = smoother jaggedness |
|
|
253
|
+
| `DISPLACEMENT` (const) | 0.05–1.5 m | how wild the jagged offset is per segment |
|
|
254
|
+
| `BRANCH_CHANCE` (const) | 0.0–0.6 | probability of forks per segment |
|
|
255
|
+
| `thickness` | 0.05–1.0 m | halo width of the bolt |
|
|
256
|
+
| `emission_boost` | 0.0–16.0 | bloom strength (needs Bloom in WorldEnvironment) |
|
|
257
|
+
| `bolt_color` / `core_color` | Color | recolor for fire-bolt, plasma, magic |
|
|
258
|
+
| `trauma_amount` | 0.0–1.0 | how hard the camera shakes |
|
|
259
|
+
|
|
260
|
+
## Cookbook — named variants
|
|
261
|
+
|
|
262
|
+
### thunderbolt (default)
|
|
263
|
+
|
|
264
|
+
Cool blue-white, dramatic shake, sky-to-ground.
|
|
265
|
+
|
|
266
|
+
```
|
|
267
|
+
bolt_color = Color(0.55, 0.75, 1.00)
|
|
268
|
+
core_color = Color(1.0, 1.0, 1.0)
|
|
269
|
+
emission_boost = 8.0
|
|
270
|
+
thickness = 0.30
|
|
271
|
+
trauma_amount = 0.6
|
|
272
|
+
LIFETIME = 0.18
|
|
273
|
+
DISPLACEMENT = 0.45
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### chain-lightning-spell
|
|
277
|
+
|
|
278
|
+
Tighter, chains between targets.
|
|
279
|
+
|
|
280
|
+
```
|
|
281
|
+
bolt_color = Color(0.65, 0.85, 1.0)
|
|
282
|
+
emission_boost = 6.0
|
|
283
|
+
thickness = 0.18
|
|
284
|
+
trauma_amount = 0.25
|
|
285
|
+
LIFETIME = 0.12
|
|
286
|
+
DISPLACEMENT = 0.20
|
|
287
|
+
SEGMENTS = 14
|
|
288
|
+
BRANCH_CHANCE = 0.15
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### tesla-arc
|
|
292
|
+
|
|
293
|
+
Short, fast, lots of forks.
|
|
294
|
+
|
|
295
|
+
```
|
|
296
|
+
bolt_color = Color(0.75, 0.95, 1.0)
|
|
297
|
+
emission_boost = 10.0
|
|
298
|
+
thickness = 0.10
|
|
299
|
+
LIFETIME = 0.06
|
|
300
|
+
DISPLACEMENT = 0.15
|
|
301
|
+
SEGMENTS = 24
|
|
302
|
+
BRANCH_CHANCE = 0.45
|
|
303
|
+
trauma_amount = 0.10
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### plasma-laser (held beam variant)
|
|
307
|
+
|
|
308
|
+
Override the script to NOT free after `LIFETIME` — use a `MeshInstance3D` with a stretched cylinder mesh updated each frame between two transforms; same shader.
|
|
309
|
+
|
|
310
|
+
```
|
|
311
|
+
bolt_color = Color(1.0, 0.40, 0.55)
|
|
312
|
+
core_color = Color(1.0, 0.85, 0.95)
|
|
313
|
+
emission_boost = 12.0
|
|
314
|
+
thickness = 0.20
|
|
315
|
+
DISPLACEMENT = 0.05 # nearly straight
|
|
316
|
+
SEGMENTS = 8
|
|
317
|
+
trauma_amount = 0.0 # no shake on a held beam
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
## Anti-patterns
|
|
321
|
+
|
|
322
|
+
- **Drawing the bolt as a `Line3D`.** Line3D doesn't billboard or accept the additive shader cleanly. Use `ImmediateMesh` triangle strip with the billboard vertex shader.
|
|
323
|
+
- **Animating the path frame-by-frame.** A bolt is one shape, on screen for 150 ms. You don't see the inside detail moving. One generation per cast.
|
|
324
|
+
- **Forgetting `blend_add`.** A bolt that doesn't bloom looks like a curved metal stick.
|
|
325
|
+
- **No camera shake.** Lightning without screen shake feels weightless. Always call `CameraShake.add_trauma`.
|
|
326
|
+
- **Spawning lightning every frame for a "continuous" beam.** Use the `plasma-laser` variant pattern instead — one mesh, updated per-frame transform, never re-instantiated.
|
|
327
|
+
- **`SEGMENTS` too low (< 6).** Bolt looks like a zigzag triangle. Default 18 is right.
|
|
328
|
+
- **`DISPLACEMENT` proportional to total length without falloff.** Endpoints drift away from `from`/`to`. The included code uses `sin(t * PI)` falloff so endpoints meet cleanly.
|
|
329
|
+
|
|
330
|
+
## Performance notes
|
|
331
|
+
|
|
332
|
+
- One bolt: ~36 verts, one draw call, freed after 150 ms. Effectively free.
|
|
333
|
+
- Chain lightning with 5 segments: still under 0.05 ms.
|
|
334
|
+
- Endpoint sparks (instantiated `hit-spark` scenes) are the bigger cost — 64 particles × 2 = 128 particles per cast. Throttle for storms (see edge cases).
|
|
335
|
+
- The shader's flicker uses `floor(TIME * flicker_rate)`, so it ages with `TIME` not particle time — flicker is consistent across all bolts in flight.
|
|
336
|
+
|
|
337
|
+
## Edge cases
|
|
338
|
+
|
|
339
|
+
- **Lightning storm (50 strikes/sec).** Disable endpoint sparks for ambient strikes. Pool the `LightningCaster` mesh nodes instead of allocating per cast.
|
|
340
|
+
- **Bolt passes through a wall.** Visual only — the bolt shader doesn't depth test. If the user wants the bolt to be occluded by walls, set `depth_draw_opaque` and let the depth buffer cull it (loses the additive bloom in front of geometry).
|
|
341
|
+
- **Bolt origin and target at the same point.** Falloff goes to zero; `_generate_path` returns a degenerate strip. Add a guard: if `(to - from).length() < 0.01`, skip.
|
|
342
|
+
- **Underwater bolt.** Tint blue-green, drop `emission_boost` to 5.0; underwater bloom is muted.
|
|
343
|
+
- **First-person caster (player's hand).** The bolt starts inside the player's view and reads as a flash. Use a `held-beam` variant or shorten `LIFETIME` to 0.08.
|
|
344
|
+
- **No `CameraShake` autoload registered.** The script's `_has_autoload` guard prevents a crash; the bolt fires without shake. Suggest `_building-blocks/trauma-shake-snippet.md` to wire it.
|
|
345
|
+
|
|
346
|
+
## Fallback (no MCP)
|
|
347
|
+
|
|
348
|
+
VFX is code, no MCP required:
|
|
349
|
+
|
|
350
|
+
1. Create `addons/vfx/lightning/` and write the three files above.
|
|
351
|
+
2. In Godot, add a `Node3D`, attach `lightning_caster.gd`. Optionally autoload as `Lightning`.
|
|
352
|
+
3. From any spell script, call `Lightning.cast_lightning(from_pos, to_pos)`.
|
|
353
|
+
4. Wire the `CameraShake` autoload from `_building-blocks/trauma-shake-snippet.md` for the punch.
|
|
354
|
+
|
|
355
|
+
## Handoff
|
|
356
|
+
|
|
357
|
+
After firing this recipe, suggest:
|
|
358
|
+
|
|
359
|
+
- `summer:visual-effects/recipes/hit-spark` — automatically called at endpoints; tune the spark color to match `bolt_color`.
|
|
360
|
+
- `summer:visual-effects/recipes/magic-glow` — for the wizard's hand glow during charge-up before the cast.
|
|
361
|
+
- `summer:visual-effects/recipes/muzzle-flash` — for the brief bright flash at the casting hand on release frame.
|
|
362
|
+
- `summer:visual-effects/game-feel` — `CameraShake.add_trauma` is already called; pair with hit-stop on the target for impact emphasis.
|
|
363
|
+
- `summer:audio/sound-effect` — generate `electric crack thunder, sharp impact, rumble tail, 800ms` and play on cast.
|
|
364
|
+
|
|
365
|
+
## See also
|
|
366
|
+
|
|
367
|
+
- `_building-blocks/trauma-shake-snippet.md` — `CameraShake` autoload (REQUIRED for the shake call)
|
|
368
|
+
- `_building-blocks/additive-billboard-particles.md` — for the endpoint sparks
|
|
369
|
+
- `summer:visual-effects/recipes/hit-spark` — endpoint companion
|
|
370
|
+
- `summer:visual-effects/recipes/magic-glow` — pre-cast charge-up
|
|
371
|
+
- `summer:visual-effects/recipes/muzzle-flash` — for the cast-release flash
|
|
372
|
+
- `summer:visual-effects/game-feel` — full game-feel pairing
|