vfx-water-ripple 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. Water-ripple effect: animated normal-distortion ripples on a water plane or Decal, set off by impacts.
When your agent uses it
- Authoring a water-ripple effect — raindrop ripples, splash ripples, footstep in a puddle, fish jumps
- Rain, footsteps, or falling objects should disturb a water surface with spreading ripples
Install
summer setup installs every skill for your agent. To add only this one:
claude-code for your agent, such as codex or cursor. See Skills for every option.
The skill
Concentric expanding rings on a water surface, normal-distorted so the ripple actually refracts the reflection. The shader keeps a small ring buffer of (origin, time, amplitude) tuples and renders all of them simultaneously over a base water plane. GDScript exposesadd_ripple(world_pos, amplitude) so any system (rain, footsteps, projectile impacts, fish) can spawn one with a single call.
When to use
- “Add ripples when the player walks through the puddle.”
- “Rain hitting the lake should make ripples.”
- “When the bullet hits the water, ripple.”
- “Fish jumping leaves ripples.”
- “Footsteps on the wet street should ripple.”
- A water plane already exists and looks too static.
When NOT to use
- The user wants the whole water surface to wave (Gerstner / FFT ocean) — that needs an ocean wave shader; this recipe adds impact ripples on top.
- The user wants a 2D top-down ripple effect on a UI map — use
canvas_itemshader, not thisspatialone. - The user wants splash particles flying upward on impact — pair this with
vfx-hit-spark(recolor blue) for the droplets; this only handles the ring on the surface. - The user wants the ripple to physically displace floating objects — this is visual only. Use
Area3Dimpulses for the simulation side.
Recipe
1. Files to create
2. Shader code
addons/vfx/water-ripple/water_ripple.gdshader:
3. GDScript controller
addons/vfx/water-ripple/water_ripple_surface.gd:
4. Node tree
5. Wire it in (MCP calls)
Write the two files, then wire the surface with ONEsummer_run_script call
with an explicit scenePath — transactional, and it builds the subdivided
PlaneMesh directly, so no plane.tres sidecar is needed:
_ready; either way,
ctx.add_node has already owned the node, so it survives the save. Tuning one
mesh value later is one summer_set_resource_property call (resourceProperty="mesh",
subProperty="subdivide_width"): it reaches into a resource, it never assigns one.
5a. Verify
The shader and the controller are compiled code, and the controllerpreloads the
shader — a shader that failed to write takes the script down at parse time.
6. Parameters to tune
Cookbook — named variants
puddle-shallow (default)
Tight, fast ripples, high contrast.lake-calm
Slow, wide, long-lived ripples.lava-glow (recolor for hot liquid)
blood-pool
Sticky, slow, dark.Anti-patterns
- Spawning more than 16 ripples per surface. The shader has a fixed array. Older ripples get overwritten — fine for rain (they were fading anyway), bad if you spawn 50 in one frame. Either raise
MAX_RIPPLES(and the GLSLMAX_RIPPLES) together, or throttle spawns. - Ripple position in local instead of world space. The shader expects world XZ. Always convert in the GDScript before pushing.
- No normal map assigned. The surface looks like flat-shaded plastic. Always assign a tileable water normals texture (any blue noise normals will do).
- Writing a decoded ±1 vector into
NORMAL_MAP. The engine decodes it again (normal_map.xy = normal_map.xy * 2.0 - 1.0), so a flat surface reads as permanent maximum tilt. Re-encode with* 0.5 + 0.5before assigning, as section 2 does. - Timing ripples against shader
TIME.TIMEwraps atrendering/limits/time/time_rollover_secs(3600 s default) and is notTime.get_ticks_msec() / 1000. Push your own accumulated clock as a uniform and compare against that; otherwise every ripple silently vanishes after an hour of uptime. - PlaneMesh too low-res. The vertex displacement needs ~2–4 verts per meter or the silhouette wobble looks blocky. Use
subdivide_width = 32, subdivide_depth = 32minimum on a 10×10 plane. depth_draw_disabledon the water material. You’ll see the bottom of the puddle through the water at every angle. Usedepth_draw_opaque.- Triggering ripples in
_processwithout spacing. Spamming 60/sec floods the buffer. Throttle to ~5 ripples/sec for rain unless tuned.
Performance notes
- Cost is O(MAX_RIPPLES × pixels_drawn) in the fragment loop plus O(MAX_RIPPLES × verts) in the vertex loop. 16 ripples on a 5×5 m puddle at 1080p is cheap — an order-of-magnitude expectation, not a measurement. Profile if you need a number.
- The vertex displacement loop runs per vertex; raising plane subdivision past 64 starts to hurt. 32×32 is plenty.
- For lakes (large planes), don’t apply this shader to the entire lake — split the lake into chunks and only apply ripples to the chunk near impacts. Or use a
Decalprojection instead (see edge cases). - Mobile: drop
MAX_RIPPLESto 8, disable vertex displacement (setripple_height = 0and skip the vertex loop), and rely on normal-only ripples.
Edge cases
- Ripple on a non-flat water surface (Gerstner ocean). This shader assumes the water plane is flat. For wavy water, sample the wave height in the same shader and add the ripple displacement on top.
- Want to use a Decal instead of overriding the water material. Use
Decalprojecting straight down with a 256×256 ring texture animated byalbedo_mix. Cheaper but doesn’t refract reflections — only colors the surface. - Ripple needs to interact with floating debris. Visual ripples don’t push physics. Spawn an
Area3Dimpulse alongside if you need physical effect. - Camera under water (POV swimming). This shader is one-sided in concept (ring expanding outward from above). For under-water POV, render a separate shader.
- Ripple at the edge of the plane. It clips at the plane boundary because the plane has no geometry past it. Expand the plane 2 m beyond the gameplay area.
Fallback (no MCP)
Section 5 is fully automatable —summer_write_file writes the shader and the
controller, and summer_add_node + summer_set_prop +
summer_set_resource_property + summer_save_scene build the plane including its
subdivision. 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:
- Create
addons/vfx/water-ripple/and write the three files above. - Add a
MeshInstance3Dwith a high-subdivisionPlaneMesh(32×32 minimum). - Attach
water_ripple_surface.gdand assign a tileable water normals texture. - From your trigger code, call
$Puddle.add_ripple(impact_pos, amplitude). - Check the Errors dock — the controller
preloads the shader, so a broken shader path fails the script too.
Handoff
After firing this recipe, suggest:vfx-hit-spark— recolor white-blue and spawn at the impact point for a few water droplets flying up. Pair them.vfx-smoke— recolor pale gray for the steam if you’re hitting hot water or lava.sound-effect— generatesingle water drop in shallow puddle, splash, 200msand trigger on eachadd_ripple.- For weather-driven rain ripples, scaffold a simple Rain node that calls
add_ripple30×/sec across the puddle bounds.
See also
_building-blocks/additive-billboard-particles.md— for paired splash dropletsvfx-hit-spark— sister recipe for the upward dropletsvfx-smoke— for steam over hot liquidsgdscript-patterns— for the controller idioms
From a shell
No Summer MCP tools in this session? Run each one as a command; whensummer --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
Related skills
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.vfx-hit-spark: Hit-spark effect: a one-shot burst of additive particles oriented to the surface, fired on impact.vfx-smoke: Smoke effect: a rising column or puff of soft particles with a noise and density falloff shader.
Source:
library/skills/vfx-water-ripple/SKILL.md, rendered from summer-engine@4.3.0 on npm.
