Effects and game feel
Trigger transient effects through application events and reuse their resources.
Reuse before authoring. Adapt the closest official particle example with find-examples, prefer the
Engine's built-in particle component over a hand-written system, and discover any shipped trail or
effect script with reuse-scripts.
Copy particle curve construction from the installed example exactly. CurveSet takes one key array
per channel, such as new CurveSet([0, x0, 1, x1], [0, y0, 1, y1], [0, z0, 1, z1]); do not flatten three channels into one array. A malformed curve can type-check and then fail
inside the particle texture upload. Create one emitter, render a frame, and require empty runtime
and console diagnostics before reusing the helper for smoke, spray, or splashes.
For a polished wake, smoke, or spray requirement, do not stop at scaled planes, crossed billboards,
or generic render primitives. Use the built-in particle component for smoke and spray; reserve a
custom mesh and material for a continuously sampled wake ribbon. Verify tint and blending against
the actual scene lighting and surface.
Place and layer
- Place an effect at the emitter's world mount point; parent it there or copy the world transform once.
- Keep additive or transparent effects above the surface they sit on and biased toward the camera so
they do not z-fight. Inspect soft transparency at grazing angles for hard edges, black quads, or
dropout.
- Size and orient effects from the emitter's calibrated bounds, not a guessed constant.
Pool and prewarm
- Create emitters, ribbons, flashes, materials, and meshes at load. Events check out an effect, set
its transform, and call
particlesystem.reset() then play(); after its particles finish, call
stop() and return it to the pool.
- First-use shader compilation can stall a frame. Before gameplay, render every material and particle
variant through the relevant passes, on an offscreen rig or behind the ready overlay.
- Pulse or tint uniforms with
meshInstance.setParameter or material.setParameter.
material.update() marks the whole material dirty and clears variants when defines or chunks
change; reserve it for material changes that require it.
Moving trails
- Treat built-in render primitives as blocking geometry for soft effects. Follow the closest
official example to choose particles, a custom mesh, textures, materials, and blending for the
intended visual behavior.
- Match every custom mesh's vertex streams to its material. A lit
StandardMaterial needs normals;
calculate and set them before mesh.update(), or deliberately use an unlit material. Treat a
missing vertex-attribute diagnostic as a rendering failure even when some frames still appear.
For a dynamic horizontal ribbon created with setVertexStream, allocate a normal array alongside
positions, fill every vertex with (0, 1, 0), and upload it as SEMANTIC_NORMAL before the first
update(). Recheck this whenever changing the ribbon's material type.
- Never attach an empty dynamic mesh to an enabled render component. Upload a valid initial vertex
and index set before creating its
MeshInstance, or keep the render entity disabled until the
first complete update. On reset, disable it or restore valid seed geometry; do not call
mesh.clear() while an enabled renderer still references it. Reflection and refraction cameras
render the same mesh and can expose this lifecycle bug before the gameplay camera does.
- Judge a trail from its intended cameras. If it reads as a flat decal, use the reference example to
add the minimum depth cue needed rather than stacking arbitrary geometry.
- Sample the trail continuously. Append segments on a small time- or distance-step and interpolate
between them; coarse per-distance chunks make the trail stutter and break into visible dashes as
the object speeds up.
- Anchor it to the mover's contact or exhaust point, scale emission rate and length with speed, and
fade the tail so it dissolves instead of cutting off with a hard edge.
Camera shake and hit feedback
- Drive camera shake as a decaying offset on a camera parent or an additive layer; never write it
into the smoothed chase position, or it fights the follow. Keep shake and follow separate, as the
orbit-camera rule in
apply-conventions requires.
- Stagger the parts of a compound effect over a few frames — flash, then smoke, then splash — so an
event reads as a sequence rather than a single pop.
- Keep the readable peak long enough for the gameplay camera to see the layers together. A flash can
be brief, but the longer-lived layers should overlap for several frames rather than leaving the
primary action frame visually identical to the idle state.
- For side-mounted emitters, derive every spawn point and direction from calibrated world-space
mounts on the mover. Confirm the effect starts outside the source geometry from both sides; an
active cooldown with only a tiny flash elsewhere in the frame is failed feedback.
Prove it fires
Trigger each effect through real gameplay input and confirm with a screenshot captured at its peak,
not a saved filepath. Confirm the effect is visible from the gameplay camera and from a grazing
angle, that repeated events leave entity counts stable, and that nothing leaves a black quad or a
hard edge over the surface. Compare graphicsDevice.shaders.length after prewarming and after the
first real event; growth indicates a missed variant. Use two fixed poses and return screenshots at
accept-or-reject points only, not after every edit.
Choose the authoring surface with the build-app skill; create effects through its entity and
component primitives and own their lifecycle in the surface that spawned them.
1---2name: add-effects3description: Use when adding transient visual effects or trails to a PlayCanvas app to select an Engine-native implementation, place and layer it correctly, manage its lifecycle, and verify the rendered result.4---56# Effects and game feel78Trigger transient effects through application events and reuse their resources.910Reuse before authoring. Adapt the closest official particle example with `find-examples`, prefer the11Engine's built-in particle component over a hand-written system, and discover any shipped trail or12effect script with `reuse-scripts`.1314Copy particle curve construction from the installed example exactly. `CurveSet` takes one key array15per channel, such as `new CurveSet([0, x0, 1, x1], [0, y0, 1, y1], [0, z0, 1,16z1])`; do not flatten three channels into one array. A malformed curve can type-check and then fail17inside the particle texture upload. Create one emitter, render a frame, and require empty runtime18and console diagnostics before reusing the helper for smoke, spray, or splashes.1920For a polished wake, smoke, or spray requirement, do not stop at scaled planes, crossed billboards,21or generic render primitives. Use the built-in particle component for smoke and spray; reserve a22custom mesh and material for a continuously sampled wake ribbon. Verify tint and blending against23the actual scene lighting and surface.2425## Place and layer2627- Place an effect at the emitter's world mount point; parent it there or copy the world transform once.28- Keep additive or transparent effects above the surface they sit on and biased toward the camera so29 they do not z-fight. Inspect soft transparency at grazing angles for hard edges, black quads, or30 dropout.31- Size and orient effects from the emitter's calibrated bounds, not a guessed constant.3233## Pool and prewarm3435- Create emitters, ribbons, flashes, materials, and meshes at load. Events check out an effect, set36 its transform, and call `particlesystem.reset()` then `play()`; after its particles finish, call37 `stop()` and return it to the pool.38- First-use shader compilation can stall a frame. Before gameplay, render every material and particle39 variant through the relevant passes, on an offscreen rig or behind the ready overlay.40- Pulse or tint uniforms with `meshInstance.setParameter` or `material.setParameter`.41 `material.update()` marks the whole material dirty and clears variants when defines or chunks42 change; reserve it for material changes that require it.4344## Moving trails4546- Treat built-in render primitives as blocking geometry for soft effects. Follow the closest47 official example to choose particles, a custom mesh, textures, materials, and blending for the48 intended visual behavior.49- Match every custom mesh's vertex streams to its material. A lit `StandardMaterial` needs normals;50 calculate and set them before `mesh.update()`, or deliberately use an unlit material. Treat a51 missing vertex-attribute diagnostic as a rendering failure even when some frames still appear.52 For a dynamic horizontal ribbon created with `setVertexStream`, allocate a normal array alongside53 positions, fill every vertex with `(0, 1, 0)`, and upload it as `SEMANTIC_NORMAL` before the first54 `update()`. Recheck this whenever changing the ribbon's material type.55- Never attach an empty dynamic mesh to an enabled render component. Upload a valid initial vertex56 and index set before creating its `MeshInstance`, or keep the render entity disabled until the57 first complete update. On reset, disable it or restore valid seed geometry; do not call58 `mesh.clear()` while an enabled renderer still references it. Reflection and refraction cameras59 render the same mesh and can expose this lifecycle bug before the gameplay camera does.60- Judge a trail from its intended cameras. If it reads as a flat decal, use the reference example to61 add the minimum depth cue needed rather than stacking arbitrary geometry.62- Sample the trail continuously. Append segments on a small time- or distance-step and interpolate63 between them; coarse per-distance chunks make the trail stutter and break into visible dashes as64 the object speeds up.65- Anchor it to the mover's contact or exhaust point, scale emission rate and length with speed, and66 fade the tail so it dissolves instead of cutting off with a hard edge.6768## Camera shake and hit feedback6970- Drive camera shake as a decaying offset on a camera parent or an additive layer; never write it71 into the smoothed chase position, or it fights the follow. Keep shake and follow separate, as the72 orbit-camera rule in `apply-conventions` requires.73- Stagger the parts of a compound effect over a few frames — flash, then smoke, then splash — so an74 event reads as a sequence rather than a single pop.75- Keep the readable peak long enough for the gameplay camera to see the layers together. A flash can76 be brief, but the longer-lived layers should overlap for several frames rather than leaving the77 primary action frame visually identical to the idle state.78- For side-mounted emitters, derive every spawn point and direction from calibrated world-space79 mounts on the mover. Confirm the effect starts outside the source geometry from both sides; an80 active cooldown with only a tiny flash elsewhere in the frame is failed feedback.8182## Prove it fires8384Trigger each effect through real gameplay input and confirm with a screenshot captured at its peak,85not a saved filepath. Confirm the effect is visible from the gameplay camera and from a grazing86angle, that repeated events leave entity counts stable, and that nothing leaves a black quad or a87hard edge over the surface. Compare `graphicsDevice.shaders.length` after prewarming and after the88first real event; growth indicates a missed variant. Use two fixed poses and return screenshots at89accept-or-reject points only, not after every edit.9091Choose the authoring surface with the `build-app` skill; create effects through its entity and92component primitives and own their lifecycle in the surface that spawned them.