pylinka
Menu

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.