This is a small damage effect for a PixiJS 8 game: a hero has a 3-hit shield. The first two hits chip it at the point of impact, on the side facing the attacker. The third hit shatters the whole shield into spinning triangle shards.
Both effects are particle JSON files exported with the nixie-fx CLI and played by its PixiJS renderer. Everything below was run in PixiJS 8.22.0, nixie-fx 0.1.17 and Vite 6 in a Chromium browser on 2026-10-07. The screenshots are frames from the same code, rendered on a fixed 1/60 s clock so they can be reproduced.
Before and after
Hit 1 of 3: the chip. A short flare and 12 sparks at the impact point, 0.08 s after the hit. The shield stays up.
Hit 3 of 3: the shatter. A core flash and 28 triangle shards, 0.12 s after the hit. The shield ring is gone.
At 0.35 s the shards have spread wider and started to fall:
Two effects, one manifest
| Effect | Emitter | What it does |
|---|---|---|
shield-chip |
flare |
1 additive billboard, 0.14 s, grows and fades |
sparks |
12 billboards from a tiny flat circle, speed 3 to 5.5 units/s, drag 2 | |
shield-shatter |
core |
1 large additive flash over the hero, 0.2 s |
shards |
28 triangle shards on a 0.9-unit circle (the shield's radius), random spin, gravity 4, drag 1.2 |
The projection maps 1 world unit to 100 px, so the 0.9-unit spawn circle sits exactly on the 90 px shield ring.
Validate and export from the project folder:
npx nixie-fx validate .
npx nixie-fx export .
Validation passed with one warning, and it's worth reading:
warning [unsupported-module] shield-shatter.json:emitters.1.mode: Mesh mode exports as Pixi 2D shard geometry semantics, not true 3D mesh rendering.
Validated 2 effects: 0 errors, 1 warning.
The shards use mesh mode with the built-in triangleShard template. On the Pixi backend that becomes flat 2D shard geometry, which is exactly what a 2D game wants here, but the export marks shield-shatter as partial for pixi2d because of it. shield-chip is plain billboards and exports as supported on both backends.
Loading and triggering
loadVfxExportBundle checks the manifest, hashes and validation before anything plays. With requiredBackend: "pixi2d" it accepted the partial effect; it refuses effects that are blocked on that backend.
import { loadVfxExportBundle } from "nixie-fx/export";
import { PixiVfxRenderer, createPixiVfx2dProjection } from "nixie-fx/pixi";
import manifest from "./vfx/manifest.json";
import chipJson from "./vfx/effects/shield-chip.json";
import shatterJson from "./vfx/effects/shield-shatter.json";
const bundle = loadVfxExportBundle(
{
manifest,
effectsByPath: {
"effects/shield-chip.json": chipJson,
"effects/shield-shatter.json": shatterJson,
},
},
{ requiredBackend: "pixi2d", requiredEffectIds: ["shield-chip", "shield-shatter"] },
);
const chip = bundle.effectsById.get("shield-chip");
const shatter = bundle.effectsById.get("shield-shatter");
const vfx = new PixiVfxRenderer({
parent: vfxLayer, // a Container above the hero and shield, below the HUD
projection: createPixiVfx2dProjection({
originX: HERO.x, originY: HERO.y, pixelsPerUnit: 100, yAxis: "up",
}),
});
The impact point is on the shield ring, on the side facing the attacker, with up to 22.5 degrees of spread either way. It's in world units around the hero, y up:
function impactPoint(rng) {
const toEnemy = Math.atan2(-(ENEMY.y - HERO.y), ENEMY.x - HERO.x);
const angle = toEnemy + (rng() - 0.5) * (Math.PI / 4);
const r = SHIELD_RADIUS / PX_PER_UNIT; // 0.9
return [Math.cos(angle) * r, Math.sin(angle) * r, 0];
}
One damage event in, one effect out. The hit that leaves the shield standing plays the chip at the impact point. The hit that takes it to 0 plays the shatter at the hero's centre:
function takeHit() {
if (shieldHp <= 0) return;
shieldHp -= 1;
const at = impactPoint(rng);
if (shieldHp > 0) {
live.add(vfx.createEffect(chip, { position: at, seed: seed++ }));
} else {
live.add(vfx.createEffect(shatter, { position: [0, 0, 0], seed: seed++ }));
}
drawShield();
}
Neither effect loops, so each instance is removed once it finishes:
app.ticker.add((ticker) => {
vfx.update(ticker.deltaMS / 1000);
for (const instance of live) {
if (!instance.isActive) {
vfx.removeEffect(instance, true);
live.delete(instance);
}
}
});
The gotcha: a flat circle doesn't push particles outward by itself
My first export of both effects looked wrong. The sparks sat in a 6 px blob and never left the impact point. The shards appeared on the ring and then just dropped under gravity, keeping the ring's shape.
Both emitters spawn from a circle rotated [90, 0, 0] so it lies flat in the screen plane, with velocity.mode: "shapeDirection". In this setup that direction doesn't point outward along the screen, so on a 2D projection the particles get no visible outward speed. The fix was one spawn setting:
"spawn": {
"shape": "circle",
"radius": 0.03,
"radiusThickness": 0,
"rotation": [90, 0, 0],
"sphericalDirectionAmount": 1
}
With sphericalDirectionAmount: 1, each particle moves away from the circle's centre, which on a flat circle means straight outward on screen. I measured the spread of the particles (the bounding box of their screen positions, from vfx.getParticleDebugQuads()) after 10 fixed steps of 1/60 s:
| Emitter | Without the flag | With sphericalDirectionAmount: 1
|
|---|---|---|
chip sparks
|
6 × 6 px | 95 × 112 px |
shatter shards (gravity off for the test) |
179 × 179 px | 227 × 226 px |
For the sparks, 6 px is the spawn circle's diameter, so they weren't moving at all. For the shards, 179 px is the 0.9-unit ring itself. With the flag and gravity back on, the shards go from 180 px wide on the first frame to 269 px wide half a second later, which is the spread you can see in the 0.35 s screenshot.
If your burst "doesn't burst" in PixiJS, check this flag before you start raising speed values. I raised the speed first and it changed nothing.
A second thing that didn't work: rotating an instance
I wanted the chip sparks to spray away from the shield, not in every direction. The obvious approach was to author a one-sided spray and rotate each instance towards the attacker. The Pixi renderer's createEffect options in 0.1.17 take position and seed but no rotation. I passed rotation: [0, 0, 90] anyway to check, and the particle positions after 10 frames were identical with and without it.
So this demo aims the chip by position only: it lands on the side of the ring facing the attacker, and the sparks spray in all directions from there. If you need a truly directional spray in PixiJS today, the options are separate exported effects per direction, or moving the emitter rather than rotating it.
Measured numbers
From the same fixed-step run, one effect at a time:
-
shield-chip: peak 13 particles, inactive after 22 frames (0.37 s). -
shield-shatter: peak 29 particles, inactive after 60 frames (1.00 s).
That's small enough that I didn't benchmark frame time. The numbers are here so you know what one break costs in particle count, not as a performance claim.
What's supported and what isn't
-
Supported on
pixi2d: billboards, color over lifetime, size over lifetime, velocity, gravity, drag, start rotation and spin on the shards, additive blending. No textures: both effects use the runtime's built-in procedural circle and triangle shard. -
Partial on
pixi2d: mesh mode. The shards are flat 2D geometry, not 3D meshes, and the export says so. -
Partial on
three3d: the sameshield-shatterfile. The export notes that Three.js renders true mesh particles only withmesh.renderMode: "meshAsset", so I'd re-author the shards before using this effect in a Three.js scene. I didn't run it there. - Not used: bloom, lit shading, trails, sub-emitters. Per the NixieFX docs, the Pixi backend has no lit shading and uses 2.5D draw ordering instead of GPU depth.
- Tested in: this PixiJS page in a Chromium browser only. Not in the NixieFX editor preview, not on mobile, not in Firefox or Safari.
-
Art caveat: the core flash is additive and sits above the hero, so it briefly brightens the hero. Move the VFX layer below the hero or drop the
coreemitter if you don't want that.
If you're choosing a pixi js particle editor and want to see how authoring, export and the Pixi runtime fit together, the NixieFX PixiJS particle effects page covers the workflow this demo uses.



Top comments (0)