Onceworlds
Engine · Effects 3D

Effects in 3D

Particles, trails, decals, words and sprites in 3D, and which for which moment.

Every action in a 3D game answers with an effect: a hit sparks, a coin bursts, a death explodes, a spell glows. The engine has them built in. They come with Render3D(), need no files, follow the player's Graphics setting by themselves, and are one line each. The full reference is "3D effects" in API.md.

The lines you need

ctx.effects3d.burst('explosion', enemy);                       // once, at an entity or [x, y, z]
ctx.effects3d.burst('sparks', hit.point, { direction: hit.normal });
ctx.effects3d.decal('scorch', hit.point, { normal: hit.normal, size: 2 });
ctx.feel.particles.burst('sparks', target);                    // the same call works in 2D
ctx.feel.floatText('-12', target, { color: '#ff4040' });       // damage numbers, drawn on top
world.spawn([Transform({ position: [0, 0, 0] }), Particles3D({ preset: 'fire' })]);   // a looping emitter
world.spawn([Transform(), Trail3D.sword(), ...Mesh.box([0.1, 1.2, 0.05], blade)]);     // a ribbon behind a node
node.effect('levelUp');                                        // in a script

Presets: fire, smoke, sparks, explosion, magic, dust, rain, snow, confetti, splash, heal, hit, muzzleFlash, coinBurst, levelUp. Decal looks: scorch, footprint, splat, hole, shadow, crack, ring, skid. Trails: Trail3D.sword(), .kart(), .projectile(), .magic().

Which effect for which moment

Moment Effect
A hit lands hit at the target, feel.floatText the damage, feel.hitStop(60) for a big one
A bullet hits a wall sparks with direction: normal, and a hole decal (size: 0.15)
Something blows up explosion, a scorch decal under it, feel.shake(0.6)
A pickup coinBurst (or heal for health) at the player
A jump lands dust at the feet
A goal, a win, a level confetti or levelUp
A gun fires muzzleFlash on the gun's node (it points down the node's -z)
A torch, a campfire, a burning wreck Particles3D({ preset: 'fire' }) on a node (smoke for embers that have gone out)
Weather Particles3D({ preset: 'rain' }) on the camera or the player, so it falls around them
A sword swing, a kart's skids, a fast shot Trail3D.sword(), Trail3D.kart() on a wheel, Trail3D.projectile()
Props on the low tier (no real shadows there) a shadow decal under each (Decal3D, size their width)
A name or a price in the world Label3D({ text: 'SHOP' })

Make one of your own

Tweak a preset before writing one: Particles3D({ preset: 'fire', color: '#53e0ff' }) is blue fire, scale: 2 is twice as big, amount: 0.5 half as many, settings: { look: 'star' } changes every layer. For a new effect, define it once and use it by name:

// @run-fx3d
import { createGame, Transform } from '@onceworlds/engine';
import { Render3D, Particles3D, effectsOf } from '@onceworlds/engine/modules';

const game = createGame({ headless: true, modules: [Render3D()] });
game.start();
await game.ready;
const effects = effectsOf(game.world);
effects.define('shieldHit', {
  layers: [
    { count: 1, speed: 0, life: 0.25, size: 1.2, sizeEnd: 2, color: '#53e0ff', look: 'ring', glow: 2 },
    { count: 18, shape: 'sphere', radius: 0.3, speed: [2, 4], life: [0.2, 0.4], size: 0.08, color: '#bff4ff', look: 'spark', glow: 2 },
  ],
});
effects.burst('shieldHit', [0, 1, 0]);
game.world.spawn([Transform(), Particles3D({ preset: 'fire', color: '#53e0ff', scale: 0.5 })]);
game.run(0.2);
expect(effects.count).toBeGreaterThan(10);
expect(game.errors).toEqual([]);

A layer needs life and size, and count (a burst) or rate (a second). The rest have good defaults: it starts at a point, flies up at 1 m/s, glows (blend: 'add'), fades out over the second half of its life. blend: 'normal' for smoke, dust and anything that should cover rather than glow; facing: 'velocity' for streaks; facing: 'flat' for rings on the ground. Mistakes throw at define, saying what to write.

Rules of thumb