summer-engine 2.0.0 → 2.3.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 +37 -4
- package/.codex-plugin/plugin.json +38 -5
- package/.cursor-plugin/plugin.json +37 -4
- package/.factory-plugin/plugin.json +20 -0
- package/.opencode/INSTALL.md +9 -3
- package/.opencode/plugins/summer.js +0 -2
- package/AGENTS.md +51 -0
- package/CLAUDE.md +5 -0
- package/GEMINI.md +56 -0
- package/README.md +211 -61
- package/_persona/summer/SKILL.md +113 -0
- package/dist/commands/install.js +2 -2
- package/dist/commands/setup.js +4 -0
- package/dist/commands/skills.js +115 -2
- package/dist/lib/agent-config.d.ts +2 -2
- package/dist/lib/agent-config.js +171 -5
- package/dist/lib/skills-registry.d.ts +290 -26
- package/dist/lib/skills-registry.js +503 -1
- package/dist/mcp/tools/generate-tools.js +101 -0
- package/gemini-extension.json +2 -2
- package/package.json +9 -3
- 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/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 +249 -3
- 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,340 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: water-ripple
|
|
3
|
+
description: Use when authoring a water-ripple visual effect — animated normal-distortion ripples on a water plane (or as a Decal) triggered by impacts. Trigger on "water ripple", "ripples on water", "raindrop ripple", "splash ripple", "rain on water", "footstep in puddle", "fish jumps".
|
|
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
|
+
# water-ripple — Impact Ripples on a Water Plane
|
|
13
|
+
|
|
14
|
+
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 exposes `add_ripple(world_pos, amplitude)` so any system (rain, footsteps, projectile impacts, fish) can spawn one with a single call. Tested in BotW-style stylized water and in flat-puddle FPS scenes.
|
|
15
|
+
|
|
16
|
+
## When to use
|
|
17
|
+
|
|
18
|
+
- "Add ripples when the player walks through the puddle."
|
|
19
|
+
- "Rain hitting the lake should make ripples."
|
|
20
|
+
- "When the bullet hits the water, ripple."
|
|
21
|
+
- "Fish jumping leaves ripples."
|
|
22
|
+
- "Footsteps on the wet street should ripple."
|
|
23
|
+
- A water plane already exists and looks too static.
|
|
24
|
+
|
|
25
|
+
## When NOT to use
|
|
26
|
+
|
|
27
|
+
- The user wants the *whole* water surface to wave (Gerstner / FFT ocean) — that's a separate `ocean-water` recipe; this is impact ripples on top.
|
|
28
|
+
- The user wants a 2D top-down ripple effect on a UI map — use `canvas_item` shader, not this `spatial` one.
|
|
29
|
+
- The user wants splash particles flying upward on impact — pair this with `hit-spark` (recolor blue) for the droplets; this only handles the ring on the surface.
|
|
30
|
+
- The user wants the ripple to physically displace floating objects — this is visual only. Use `Area3D` impulses for the simulation side.
|
|
31
|
+
|
|
32
|
+
## Recipe
|
|
33
|
+
|
|
34
|
+
### 1. Files to create
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
addons/vfx/water-ripple/water_ripple.gdshader # spatial shader on the water plane
|
|
38
|
+
addons/vfx/water-ripple/water_ripple_surface.gd # MeshInstance3D controller w/ add_ripple()
|
|
39
|
+
addons/vfx/water-ripple/water_ripple.tscn # reusable plane
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### 2. Shader code
|
|
43
|
+
|
|
44
|
+
`addons/vfx/water-ripple/water_ripple.gdshader`:
|
|
45
|
+
|
|
46
|
+
```glsl
|
|
47
|
+
shader_type spatial;
|
|
48
|
+
render_mode blend_mix, depth_draw_opaque, cull_disabled;
|
|
49
|
+
|
|
50
|
+
const int MAX_RIPPLES = 16;
|
|
51
|
+
|
|
52
|
+
uniform vec4 water_color : source_color = vec4(0.10, 0.30, 0.45, 0.85);
|
|
53
|
+
uniform vec4 ripple_color : source_color = vec4(0.85, 0.95, 1.00, 1.0);
|
|
54
|
+
uniform float water_metallic : hint_range(0.0, 1.0) = 0.4;
|
|
55
|
+
uniform float water_roughness: hint_range(0.0, 1.0) = 0.15;
|
|
56
|
+
uniform float fresnel_power : hint_range(0.5, 8.0) = 4.0;
|
|
57
|
+
uniform float scroll_speed : hint_range(0.0, 0.5) = 0.05;
|
|
58
|
+
uniform sampler2D normal_map : hint_normal;
|
|
59
|
+
|
|
60
|
+
// Per-ripple data: (origin.xz, start_time, amplitude * (1 if active else 0))
|
|
61
|
+
uniform vec4 ripples[MAX_RIPPLES];
|
|
62
|
+
|
|
63
|
+
uniform float ripple_speed : hint_range(0.5, 8.0) = 2.5; // m/s expansion
|
|
64
|
+
uniform float ripple_wavelength : hint_range(0.05, 1.0) = 0.20; // distance between crests
|
|
65
|
+
uniform float ripple_lifetime : hint_range(0.5, 6.0) = 2.5; // seconds visible
|
|
66
|
+
uniform float ripple_height : hint_range(0.0, 0.4) = 0.08; // visual normal kick
|
|
67
|
+
|
|
68
|
+
void vertex() {
|
|
69
|
+
// Sample ripples in vertex to displace Y for proper silhouette.
|
|
70
|
+
vec3 wpos = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
|
|
71
|
+
float disp = 0.0;
|
|
72
|
+
for (int i = 0; i < MAX_RIPPLES; i++) {
|
|
73
|
+
vec4 r = ripples[i];
|
|
74
|
+
if (r.w <= 0.0) continue;
|
|
75
|
+
float age = TIME - r.z;
|
|
76
|
+
if (age < 0.0 || age > ripple_lifetime) continue;
|
|
77
|
+
float dist = length(wpos.xz - r.xy);
|
|
78
|
+
float front = age * ripple_speed;
|
|
79
|
+
float ring = exp(-pow((dist - front) / 0.25, 2.0));
|
|
80
|
+
float fade = 1.0 - smoothstep(0.0, ripple_lifetime, age);
|
|
81
|
+
disp += sin((dist - front) / ripple_wavelength * 6.2831) * ring * fade * r.w;
|
|
82
|
+
}
|
|
83
|
+
VERTEX.y += disp * ripple_height;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
void fragment() {
|
|
87
|
+
// Base water normal scroll.
|
|
88
|
+
vec2 nuv = UV + vec2(TIME * scroll_speed, TIME * scroll_speed * 0.7);
|
|
89
|
+
vec3 base_n = texture(normal_map, nuv).rgb * 2.0 - 1.0;
|
|
90
|
+
|
|
91
|
+
// Composite ripple normals on top.
|
|
92
|
+
vec3 wpos = (INV_VIEW_MATRIX * vec4(VERTEX, 1.0)).xyz;
|
|
93
|
+
vec3 ripple_n = vec3(0.0, 0.0, 1.0);
|
|
94
|
+
float ripple_glow = 0.0;
|
|
95
|
+
for (int i = 0; i < MAX_RIPPLES; i++) {
|
|
96
|
+
vec4 r = ripples[i];
|
|
97
|
+
if (r.w <= 0.0) continue;
|
|
98
|
+
float age = TIME - r.z;
|
|
99
|
+
if (age < 0.0 || age > ripple_lifetime) continue;
|
|
100
|
+
vec2 d = wpos.xz - r.xy;
|
|
101
|
+
float dist = length(d);
|
|
102
|
+
float front = age * ripple_speed;
|
|
103
|
+
float ring = exp(-pow((dist - front) / 0.25, 2.0));
|
|
104
|
+
float fade = 1.0 - smoothstep(0.0, ripple_lifetime, age);
|
|
105
|
+
vec2 dir = (dist > 0.0001) ? (d / dist) : vec2(0.0);
|
|
106
|
+
float wave = cos((dist - front) / ripple_wavelength * 6.2831);
|
|
107
|
+
ripple_n.xy += dir * wave * ring * fade * r.w * 1.5;
|
|
108
|
+
ripple_glow += ring * fade * r.w * 0.5;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
NORMAL_MAP = normalize(base_n + vec3(ripple_n.xy, 0.0));
|
|
112
|
+
NORMAL_MAP_DEPTH = 0.6;
|
|
113
|
+
|
|
114
|
+
// Fresnel-tinted color.
|
|
115
|
+
float fres = pow(1.0 - clamp(dot(NORMAL, VIEW), 0.0, 1.0), fresnel_power);
|
|
116
|
+
ALBEDO = mix(water_color.rgb, ripple_color.rgb, ripple_glow);
|
|
117
|
+
METALLIC = water_metallic;
|
|
118
|
+
ROUGHNESS = water_roughness;
|
|
119
|
+
ALPHA = mix(water_color.a, 1.0, fres);
|
|
120
|
+
EMISSION = ripple_color.rgb * ripple_glow * 0.4;
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### 3. GDScript controller
|
|
125
|
+
|
|
126
|
+
`addons/vfx/water-ripple/water_ripple_surface.gd`:
|
|
127
|
+
|
|
128
|
+
```gdscript
|
|
129
|
+
@tool
|
|
130
|
+
class_name WaterRippleSurface
|
|
131
|
+
extends MeshInstance3D
|
|
132
|
+
|
|
133
|
+
const MAX_RIPPLES := 16
|
|
134
|
+
|
|
135
|
+
@export var water_color: Color = Color(0.10, 0.30, 0.45, 0.85)
|
|
136
|
+
@export var ripple_color: Color = Color(0.85, 0.95, 1.00)
|
|
137
|
+
@export_range(0.5, 8.0) var ripple_speed: float = 2.5
|
|
138
|
+
@export_range(0.5, 6.0) var ripple_lifetime: float = 2.5
|
|
139
|
+
@export_range(0.0, 0.4) var ripple_height: float = 0.08
|
|
140
|
+
@export var normal_texture: Texture2D
|
|
141
|
+
|
|
142
|
+
var _ripples: Array[Vector4] = []
|
|
143
|
+
var _next_slot: int = 0
|
|
144
|
+
var _mat: ShaderMaterial
|
|
145
|
+
|
|
146
|
+
func _ready() -> void:
|
|
147
|
+
_ripples.resize(MAX_RIPPLES)
|
|
148
|
+
for i in MAX_RIPPLES:
|
|
149
|
+
_ripples[i] = Vector4.ZERO
|
|
150
|
+
if material_override == null or not (material_override is ShaderMaterial):
|
|
151
|
+
var sm := ShaderMaterial.new()
|
|
152
|
+
sm.shader = preload("res://addons/vfx/water-ripple/water_ripple.gdshader")
|
|
153
|
+
material_override = sm
|
|
154
|
+
_mat = material_override as ShaderMaterial
|
|
155
|
+
_apply()
|
|
156
|
+
set_process(true)
|
|
157
|
+
|
|
158
|
+
func _apply() -> void:
|
|
159
|
+
if _mat == null: return
|
|
160
|
+
_mat.set_shader_parameter("water_color", water_color)
|
|
161
|
+
_mat.set_shader_parameter("ripple_color", ripple_color)
|
|
162
|
+
_mat.set_shader_parameter("ripple_speed", ripple_speed)
|
|
163
|
+
_mat.set_shader_parameter("ripple_lifetime", ripple_lifetime)
|
|
164
|
+
_mat.set_shader_parameter("ripple_height", ripple_height)
|
|
165
|
+
if normal_texture:
|
|
166
|
+
_mat.set_shader_parameter("normal_map", normal_texture)
|
|
167
|
+
|
|
168
|
+
## Spawn a ripple at a world position.
|
|
169
|
+
## amplitude: 0..1 (raindrop ~0.2, footstep ~0.5, body splash ~1.0)
|
|
170
|
+
func add_ripple(world_pos: Vector3, amplitude: float = 0.5) -> void:
|
|
171
|
+
var local := global_transform.affine_inverse() * world_pos
|
|
172
|
+
var t := float(Time.get_ticks_msec()) / 1000.0 - _shader_time_origin()
|
|
173
|
+
_ripples[_next_slot] = Vector4(world_pos.x, world_pos.z, t, clamp(amplitude, 0.0, 1.0))
|
|
174
|
+
_next_slot = (_next_slot + 1) % MAX_RIPPLES
|
|
175
|
+
_push()
|
|
176
|
+
|
|
177
|
+
func _push() -> void:
|
|
178
|
+
if _mat == null: return
|
|
179
|
+
_mat.set_shader_parameter("ripples", _ripples)
|
|
180
|
+
|
|
181
|
+
func _shader_time_origin() -> float:
|
|
182
|
+
# Shader's TIME starts at scene load; align our timestamps to it.
|
|
183
|
+
return 0.0
|
|
184
|
+
|
|
185
|
+
func _process(_delta: float) -> void:
|
|
186
|
+
# Re-push so editor updates pick up changes; cheap.
|
|
187
|
+
if Engine.is_editor_hint():
|
|
188
|
+
_push()
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### 4. Node tree
|
|
192
|
+
|
|
193
|
+
```
|
|
194
|
+
MeshInstance3D ("Water") [script: water_ripple_surface.gd]
|
|
195
|
+
└── mesh: PlaneMesh (size matches the puddle / lake)
|
|
196
|
+
└── material_override: ShaderMaterial (shader: water_ripple.gdshader, normal_map: <noise>)
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### 5. Wire it in (MCP calls)
|
|
200
|
+
|
|
201
|
+
```
|
|
202
|
+
summer_add_node(parent="./World", type="MeshInstance3D", name="Puddle")
|
|
203
|
+
summer_set_prop(path="./World/Puddle", property="script", value="res://addons/vfx/water-ripple/water_ripple_surface.gd")
|
|
204
|
+
summer_set_prop(path="./World/Puddle", property="position", value="Vector3(0, 0.01, 0)")
|
|
205
|
+
summer_set_resource_property(nodePath="./World/Puddle", resourceProperty="mesh", value="res://addons/vfx/water-ripple/plane.tres")
|
|
206
|
+
summer_set_resource_property(nodePath="./World/Puddle", resourceProperty="normal_texture", value="res://addons/vfx/water-ripple/water_normals.png")
|
|
207
|
+
summer_save_scene
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Then trigger ripples from any system. Footstep:
|
|
211
|
+
|
|
212
|
+
```gdscript
|
|
213
|
+
# In your character controller's footstep callback:
|
|
214
|
+
if puddle and puddle.is_in_water(global_position):
|
|
215
|
+
puddle.add_ripple(global_position, 0.5)
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Rain:
|
|
219
|
+
|
|
220
|
+
```gdscript
|
|
221
|
+
# In a Rain node spawning N drops/sec on the water plane bounds:
|
|
222
|
+
puddle.add_ripple(rand_xz_inside_puddle(), 0.20)
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Bullet impact:
|
|
226
|
+
|
|
227
|
+
```gdscript
|
|
228
|
+
# In the bullet's hit handler when hit.collider == puddle:
|
|
229
|
+
puddle.add_ripple(hit.position, 0.8)
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### 6. Parameters to tune
|
|
233
|
+
|
|
234
|
+
| Parameter | Range | Effect |
|
|
235
|
+
|---|---|---|
|
|
236
|
+
| `ripple_speed` | 0.5–8.0 m/s | how fast the ring expands |
|
|
237
|
+
| `ripple_lifetime` | 0.5–6.0 s | how long before it fades |
|
|
238
|
+
| `ripple_height` | 0.0–0.4 | vertex displacement amplitude (silhouette wobble) |
|
|
239
|
+
| `ripple_wavelength` | 0.05–1.0 m | distance between crests within one ring |
|
|
240
|
+
| `water_color` | Color | base tint, alpha controls transparency |
|
|
241
|
+
| `ripple_color` | Color | crest tint, additive glow at the ring front |
|
|
242
|
+
| `fresnel_power` | 0.5–8.0 | how grazing the angle has to be before the surface goes opaque |
|
|
243
|
+
| `scroll_speed` | 0.0–0.5 | base normal map scroll speed (subtle shimmer) |
|
|
244
|
+
|
|
245
|
+
## Cookbook — named variants
|
|
246
|
+
|
|
247
|
+
### puddle-shallow (default)
|
|
248
|
+
|
|
249
|
+
Tight, fast ripples, high contrast.
|
|
250
|
+
|
|
251
|
+
```
|
|
252
|
+
ripple_speed = 3.0
|
|
253
|
+
ripple_lifetime = 1.6
|
|
254
|
+
ripple_height = 0.04
|
|
255
|
+
ripple_wavelength= 0.12
|
|
256
|
+
water_color = Color(0.05, 0.07, 0.10, 0.75)
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### lake-calm
|
|
260
|
+
|
|
261
|
+
Slow, wide, long-lived ripples.
|
|
262
|
+
|
|
263
|
+
```
|
|
264
|
+
ripple_speed = 1.5
|
|
265
|
+
ripple_lifetime = 4.0
|
|
266
|
+
ripple_height = 0.10
|
|
267
|
+
ripple_wavelength= 0.30
|
|
268
|
+
water_color = Color(0.10, 0.30, 0.45, 0.85)
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### lava-glow (recolor for hot liquid)
|
|
272
|
+
|
|
273
|
+
```
|
|
274
|
+
water_color = Color(0.12, 0.02, 0.0, 1.0)
|
|
275
|
+
ripple_color = Color(1.0, 0.55, 0.10)
|
|
276
|
+
ripple_lifetime = 1.2
|
|
277
|
+
ripple_height = 0.02
|
|
278
|
+
emission via shader: bump fres mix toward ripple_color
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### blood-pool
|
|
282
|
+
|
|
283
|
+
Sticky, slow, dark.
|
|
284
|
+
|
|
285
|
+
```
|
|
286
|
+
water_color = Color(0.18, 0.02, 0.02, 0.95)
|
|
287
|
+
ripple_color = Color(0.45, 0.05, 0.05)
|
|
288
|
+
ripple_speed = 1.0
|
|
289
|
+
ripple_lifetime = 3.0
|
|
290
|
+
ripple_wavelength= 0.40
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
## Anti-patterns
|
|
294
|
+
|
|
295
|
+
- **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 GLSL `MAX_RIPPLES`) together, or throttle spawns.
|
|
296
|
+
- **Ripple position in local instead of world space.** The shader expects world XZ. Always convert in the GDScript before pushing.
|
|
297
|
+
- **No normal map assigned.** The surface looks like flat-shaded plastic. Always assign a tileable water normals texture (any blue noise normals will do).
|
|
298
|
+
- **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 = 32` minimum on a 10×10 plane.
|
|
299
|
+
- **`depth_draw_disabled` on the water material.** You'll see the bottom of the puddle through the water at every angle. Use `depth_draw_opaque`.
|
|
300
|
+
- **Triggering ripples in `_process` without spacing.** Spamming 60/sec floods the buffer. Throttle to ~5 ripples/sec for rain unless tuned.
|
|
301
|
+
|
|
302
|
+
## Performance notes
|
|
303
|
+
|
|
304
|
+
- Cost is O(MAX_RIPPLES × pixels_drawn × 2). With 16 ripples on a 5×5 m puddle visible at 1080p: ~0.15 ms. Cheap.
|
|
305
|
+
- The vertex displacement loop runs per vertex; raising plane subdivision past 64 starts to hurt. 32×32 is plenty.
|
|
306
|
+
- 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 `Decal` projection instead (see edge cases).
|
|
307
|
+
- Mobile: drop `MAX_RIPPLES` to 8, disable vertex displacement (set `ripple_height = 0` and skip the vertex loop), and rely on normal-only ripples.
|
|
308
|
+
|
|
309
|
+
## Edge cases
|
|
310
|
+
|
|
311
|
+
- **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.
|
|
312
|
+
- **Want to use a Decal instead of overriding the water material.** Use `Decal` projecting straight down with a 256×256 ring texture animated by `albedo_mix`. Cheaper but doesn't refract reflections — only colors the surface.
|
|
313
|
+
- **Ripple needs to interact with floating debris.** Visual ripples don't push physics. Spawn an `Area3D` impulse alongside if you need physical effect.
|
|
314
|
+
- **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.
|
|
315
|
+
- **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.
|
|
316
|
+
|
|
317
|
+
## Fallback (no MCP)
|
|
318
|
+
|
|
319
|
+
VFX is code, no MCP required:
|
|
320
|
+
|
|
321
|
+
1. Create `addons/vfx/water-ripple/` and write the three files above.
|
|
322
|
+
2. In Godot, add a `MeshInstance3D` with a high-subdivision `PlaneMesh`.
|
|
323
|
+
3. Attach `water_ripple_surface.gd` and assign a tileable water normals texture.
|
|
324
|
+
4. From your trigger code, call `$Puddle.add_ripple(impact_pos, amplitude)`.
|
|
325
|
+
|
|
326
|
+
## Handoff
|
|
327
|
+
|
|
328
|
+
After firing this recipe, suggest:
|
|
329
|
+
|
|
330
|
+
- `summer:visual-effects/recipes/hit-spark` — recolor white-blue and spawn at the impact point for a few water droplets flying up. Pair them.
|
|
331
|
+
- `summer:visual-effects/recipes/smoke` — recolor pale gray for the steam if you're hitting hot water or lava.
|
|
332
|
+
- `summer:audio/sound-effect` — generate `single water drop in shallow puddle, splash, 200ms` and trigger on each `add_ripple`.
|
|
333
|
+
- For weather-driven rain ripples, see `summer:visual-effects/weather` (if available) or scaffold a simple Rain node that calls `add_ripple` 30×/sec across the puddle bounds.
|
|
334
|
+
|
|
335
|
+
## See also
|
|
336
|
+
|
|
337
|
+
- `_building-blocks/additive-billboard-particles.md` — for paired splash droplets
|
|
338
|
+
- `summer:visual-effects/recipes/hit-spark` — sister recipe for the upward droplets
|
|
339
|
+
- `summer:visual-effects/recipes/smoke` — for steam over hot liquids
|
|
340
|
+
- `summer:gdscript-patterns` — for the controller idioms
|