Skip to main content
vfx-lightning is a skill from the Summer skill library: a guide your AI agent reads when the task matches, so it builds the good version on the first try. Lightning bolt effect: a jagged procedural path with a glow shader, end sparks and screen shake.

When your agent uses it

  • Authoring a lightning bolt effect — chain lightning, electric attack, tesla coil, shock spell, energy beam
  • A spell, weapon, or hazard needs a jagged electric arc with glow, endpoint sparks, and camera shake

Install

summer setup installs every skill for your agent. To add only this one:
Swap claude-code for your agent, such as codex or cursor. See Skills for every option.

The skill

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 vfx-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.

When to use

  • “Cast a lightning bolt from the wizard’s hand to the enemy.”
  • “Chain lightning from one enemy to the next.” (call cast_lightning per segment)
  • “Tesla coil firing at the player every 2 seconds.”
  • “Energy weapon laser.” (use the plasma-laser variant)
  • “Lightning strike from the sky.”
  • The user wants a short, dramatic energy connection between two points.

When NOT to use

  • 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 the plasma-laser variant.)
  • 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.
  • The user wants a 2D lightning effect on UI — canvas_item shader, not this.
  • The user wants a tracer line for a bullet — use a simple ImmediateMesh without noise displacement; skip this recipe. The current Summer build has no Line3D (ClassDB.class_exists("Line3D") is false); Line2D is 2D-only.

Recipe

1. Files to create

2. Shader code

addons/vfx/lightning/lightning.gdshader:

3. GDScript controller

addons/vfx/lightning/lightning_caster.gd:

4. Node tree

Recommended: register one LightningCaster as an autoload (/root/Lightning) so any system can call Lightning.cast_lightning(from, to) from anywhere.

5. Wire it in (MCP calls)

Place one LightningCaster per “world” (or autoload it), then call from gameplay code. Write the two files, then place the caster with ONE summer_run_script call with an explicit scenePath — one transactional run, and several casters or anything computed is a loop in the same script:
Then verify — the caster preloads its own shader, so a shader that failed to write takes the whole script down at parse time:
Then from the spell code:
For chain lightning:

6. Parameters to tune

Cookbook — named variants

thunderbolt (default)

Cool blue-white, dramatic shake, sky-to-ground.

chain-lightning-spell

Tighter, chains between targets.

tesla-arc

Short, fast, lots of forks.

plasma-laser (held beam variant)

Override the script to NOT free after LIFETIME — use a MeshInstance3D with a stretched cylinder mesh updated each frame between two transforms; same shader.

Anti-patterns

  • Reaching for a Line3D. The current Summer build has no such class (ClassDB.class_exists("Line3D") is false); Line2D is 2D-only. Use an ImmediateMesh triangle strip with the billboard vertex shader.
  • 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.
  • Forgetting blend_add. A bolt that doesn’t bloom looks like a curved metal stick.
  • No camera shake. Lightning without screen shake feels weightless. Always call add_trauma on the /root/CameraShake autoload.
  • Gating the shake on Engine.has_singleton("CameraShake"). That is always false for an autoload (measured) — the shake would never fire. Autoloads are nodes at /root/<Name>; look them up with get_node_or_null.
  • Spawning lightning every frame for a “continuous” beam. Use the plasma-laser variant pattern instead — one mesh, updated per-frame transform, never re-instantiated.
  • SEGMENTS too low (< 6). Bolt looks like a zigzag triangle. Default 18 is right.
  • 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.

Performance notes

  • One bolt at SEGMENTS = 18: 19 path points × 2 verts = 38 verts for the trunk, plus 2 × (SEGMENTS / 3 + 1) = 14 verts per forked branch. One draw call, freed after 150 ms. Effectively free.
  • Chain lightning with 5 segments stays in the same negligible band — that is an order-of-magnitude expectation, not a measurement.
  • Endpoint sparks (instantiated vfx-hit-spark scenes) are the bigger cost — 64 particles × 2 = 128 particles per cast. Throttle for storms (see edge cases).
  • 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.

Edge cases

  • Lightning storm (50 strikes/sec). Disable endpoint sparks for ambient strikes. Pool the LightningCaster mesh nodes instead of allocating per cast.
  • 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).
  • 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.
  • Underwater bolt. Tint blue-green, drop emission_boost to 5.0; underwater bloom is muted.
  • First-person caster (player’s hand). The bolt starts inside the player’s view and reads as a flash. Use the plasma-laser variant or shorten LIFETIME to 0.08.
  • No CameraShake autoload registered. get_node_or_null("/root/CameraShake") returns null and the if cs check skips the call; the bolt fires without shake. Suggest _building-blocks/trauma-shake-snippet.md to wire it.

Fallback (no MCP)

Section 5 is fully automatable — summer_write_file writes the shader and the caster, summer_add_node + summer_set_prop + summer_save_scene place it, and summer_project_setting registers the autoload. Do not hand these steps to the user when the MCP tools are available. Without the MCP connection there is no engine to drive, so the user does it manually in Summer Engine:
  1. Create addons/vfx/lightning/ and write the three files above.
  2. Add a Node3D, attach lightning_caster.gd. Optionally autoload as Lightning.
  3. From any spell script, call Lightning.cast_lightning(from_pos, to_pos).
  4. Wire the CameraShake autoload from _building-blocks/trauma-shake-snippet.md for the punch.
  5. Check the Errors dock — lightning_caster.gd preloads the shader, so a broken shader path fails the script too.

Handoff

After firing this recipe, suggest:
  • vfx-hit-spark — automatically called at endpoints; tune the spark color to match bolt_color.
  • vfx-magic-glow — for the wizard’s hand glow during charge-up before the cast.
  • vfx-muzzle-flash — for the brief bright flash at the casting hand on release frame.
  • game-feel — CameraShake.add_trauma is already called; pair with hit-stop on the target for impact emphasis.
  • sound-effect — generate electric crack thunder, sharp impact, rumble tail, 800ms and play on cast.

See also

  • _building-blocks/trauma-shake-snippet.md — CameraShake autoload (REQUIRED for the shake call)
  • _building-blocks/additive-billboard-particles.md — for the endpoint sparks
  • vfx-hit-spark — endpoint companion
  • vfx-magic-glow — pre-cast charge-up
  • vfx-muzzle-flash — for the cast-release flash
  • game-feel — full game-feel pairing

From a shell

No Summer MCP tools in this session? Run each one as a command; when summer --version is missing or below 4.0, use npx -y summer-engine@latest in place of summer. Fields are flags in kebab-case (--scene-path res://main.tscn) or one --input '<json>', and summer ops describe <op> prints the exact input. The summer-cli skill has the rest.

Files in this skill

  • scene-scripting: Build scenes, levels, HUDs and wiring with one editor GDScript (summer_run_script) instead of call chains.
  • sound-effect: Make sound effects (swings, hits, whooshes, reloads, UI): three versions to pick from, then wire them.
  • game-feel: Add juice: hit-flash, trauma camera shake and audio ducking, one hit fires all three.
  • vfx-hit-spark: Hit-spark effect: a one-shot burst of additive particles oriented to the surface, fired on impact.
  • vfx-magic-glow: Magic-glow effect: a pulsing OmniLight3D, drifting additive motes and an optional emission shader.
  • vfx-muzzle-flash: Muzzle-flash effect: a one-shot ~80 ms burst at a gun barrel, from particles or a star-burst quad.

Source: library/skills/vfx-lightning/SKILL.md, rendered from summer-engine@4.3.0 on npm.