Getting started
Add Pylinka to a PixiJS v8 app and mount your first effect.
Install
pnpm add @pylinka/core Run it on a canvas (WebGL2)
The quickest path — works in any WebGL2 browser, no pixi and no WebGPU required. Give it a canvas and a project; the simulation runs on the GPU via transform feedback. Browse ready-made effects in the recipes gallery, or tweak one live in the editor.
import { createParticles } from '@pylinka/core/webgl';
import project from './sparks.pylinka.json';
const fx = createParticles(canvas, project);
function frame(dt) {
fx.setEmitter(mouseX, mouseY); // where new particles are born
fx.update(dt); // step the sim + render
requestAnimationFrame(() => frame(1 / 60));
}
requestAnimationFrame(() => frame(1 / 60));
fx.setKnob('windPower', 40); // live, zero recompile
fx.spawnBurst(300); createParticles interprets the common node patterns — spawn
shape, random velocity & life, gravity, wind, drag, colour- and
scale-over-life — into a GPU model. Effects using nodes outside that set
still run; the extra nodes are ignored.
The pixi v8 path
For interleaving particles inside a PixiJS scene graph, the runtime also peers
on pixi.js@^8 and integrates as a render pipe. This mounts a
system as a scene-graph node:
Mount a system
A Pylinka project is a JSON document describing one or more particle systems. Author it in the editor or start from a recipe, then load it into your renderer:
import { Application } from 'pixi.js';
import { createPylinka } from '@pylinka/core';
import project from './sparks.pylinka.json';
const app = new Application();
await app.init({ preference: 'webgpu', resizeTo: window });
const pylinka = await createPylinka(project, { renderer: app.renderer });
// Add the view to a STATIC layer — never a moving sprite (see below).
const vfx = new Container();
app.stage.addChild(vfx);
vfx.addChild(pylinka.systems.sparks.view);
app.ticker.add((ticker) => pylinka.update(ticker.deltaMS / 1000));
The backend is derived from the renderer you pass: a WebGPU renderer shares its device, a WebGL
renderer uses the WebGL2 simulation in the same context. For headless use, pass an explicit
device instead of renderer.
Move the emitter, not the view
Simulation is world-space: the emitter position is read only at spawn. To make an effect trail a moving object, drive the emitter — don't reparent the view.
// ✅ the view stays in a static layer; the emitter follows the target
pylinka.systems.sparks.follow(coinSprite);
// ✅ or set the position yourself each frame
pylinka.systems.sparks.setEmitterPosition(x, y);
// ❌ never do this — it transforms every live particle with the coin
coinSprite.addChild(pylinka.systems.sparks.view); Tweak values live
Any value promoted to a knob is adjustable at runtime with zero recompilation — the write lands on the next frame:
pylinka.params.set('windPower', 40);
pylinka.params.set('windDir', Math.PI * 0.5); Bursts and lifecycle
const sparks = pylinka.systems.sparks;
sparks.spawnBurst(200); // add 200 particles next frame
sparks.restart(); // reset the pool (+ prewarm if configured)
sparks.destroy(); // release GPU resources That's the whole surface for playing an effect. Next, read Core concepts to understand systems, the graph, and knobs, or jump to the full API.