Skip to content

pixi-particle-systems

**Scope**: GPU particles with @spd789562/pixi-v8-particle-emitter, wrestling presets, DOM particle replacement **Version range**: pixi.js ^8.5.0

From plugin
vexjoy-agent
413198 skills198 agents10 commands86 hooks
Install
$ npx -y skills add notque/vexjoy-agent --agent claude-code

How it fires

How this agent gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.

Context preview

The summary Claude sees to decide when to auto-load this agent.

**Scope**: GPU particles with @spd789562/pixi-v8-particle-emitter, wrestling presets, DOM particle replacement **Version range**: pixi.js ^8.5.0

Agent definition

pixi-particle-systems.md

PixiJS v8 Particle Systems Reference

> **Scope**: GPU particles with @spd789562/pixi-v8-particle-emitter, wrestling presets, DOM particle replacement > **Version range**: pixi.js ^8.5.0

---

Why Replace DOM Particles

The `effects.ts` failure mode — `document.createElement` + `setTimeout` removal — causes reflow per particle. GPU particles via `ParticleContainer`: 1000 particles = 1 draw call, zero reflow.

---

Installation

`@spd789562/pixi-v8-particle-emitter` is on JSR (original `@pixi/particle-emitter` is v7 only):

npx jsr add @spd789562/particle-emitter

---

Core Emitter Setup

import { Emitter } from '@spd789562/particle-emitter';
import { ParticleContainer, Assets } from 'pixi.js';

export class ParticleManager {
  private particleContainer: ParticleContainer;
  private activeEmitters: Map<string, Emitter> = new Map();

  constructor(parentContainer: Container) {
    this.particleContainer = new ParticleContainer(5000, {
      position: true, alpha: true, scale: true, rotation: true, tint: true,
    });
    parentContainer.addChild(this.particleContainer);
  }

  update(deltaMS: number): void {
    for (const [id, emitter] of this.activeEmitters) {
      emitter.update(deltaMS * 0.001);
      if (!emitter.emit && emitter.particleCount === 0) {
        emitter.destroy();
        this.activeEmitters.delete(id);
      }
    }
  }

  async spawnEffect(type: WrestlingEffect, x: number, y: number): Promise<void> {
    const config = EFFECT_CONFIGS[type];
    if (!config) return;
    const texture = await Assets.load(config.texturePath);
    const emitter = new Emitter(this.particleContainer, { ...config.emitterConfig, pos: { x, y } });
    this.activeEmitters.set(`${type}-${Date.now()}`, emitter);
  }

  destroy(): void {
    for (const emitter of this.activeEmitters.values()) emitter.destroy();
    this.activeEmitters.clear();
    this.particleContainer.destroy();
  }
}

---

Wrestling Effect Presets

Key fields: `emitterLifetime: 0.05-0.15` = burst; `-1` = infinite. `maxParticles` = burst peak (silent drops if too low).

Strike — orange/gold radial burst, 300ms

const STRIKE_CONFIG: EmitterConfig = {
  lifetime: { min: 0.15, max: 0.3 }, frequency: 0.001, emitterLifetime: 0.05, maxParticles: 20,
  pos: { x: 0, y: 0 },
  behaviors: [
    { type: 'alpha', config: { alpha: { list: [{ value: 1, time: 0 }, { value: 0, time: 1 }] } } },
    { type: 'scale', config: { scale: { list: [{ value: 0.8, time: 0 }, { value: 0.1, time: 1 }] }, minMult: 0.5 } },
    { type: 'color', config: { color: { list: [{ value: 'ff9500', time: 0 }, { value: 'ffcc00', time: 0.5 }, { value: 'ffffff', time: 1 }] } } },
    { type: 'moveSpeedStatic', config: { min: 200, max: 400 } },
    { type: 'rotationStatic', config: { min: 0, max: 360 } },
    { type: 'spawnShape', config: { type: 'circle', data: { radius: 5 } } },
  ],
};

Aerial — dust cloud, 500ms

const AERIAL_CONFIG: EmitterConfig = {
  lifetime: { min: 0.3, max: 0.5 }, frequency: 0.002, emitterLifetime: 0.1, maxParticles: 25,
  behaviors: [
    { type: 'alpha', config: { alpha: { list: [{ value: 0.8, time: 0 }, { value: 0, time: 1 }] } } },
    { type: 'scale', config: { scale: { list: [{ value: 0.3, time: 0 }, { value: 1.5, time: 1 }] } } },
    { type: 'color', config: { color: { list: [{ value: 'c8a882', time: 0 }, { value: '806040', time: 1 }] } } },
    { type: 'moveSpeedStatic', config: { min: 50, max: 150 } },
    { type: 'rotationStatic', config: { min: 0, max: 360 } },
    { type: 'spawnShape', config: { type: 'circle', data: { radius: 30 } } },
  ],
};

Submission — blue tendrils, continuous

const SUBMISSION_CONFIG: EmitterConfig = {
  lifetime: { min: 0.5, max: 1.0 }, frequency: 0.05, emitterLifetime: -1, maxParticles: 60,
  behaviors: [
    { type: 'alpha', config: { alpha: { list: [{ value: 0, time: 0 }, { value: 0.8, time: 0.2 }, { value: 0, time: 1 }] } } },
    { type: 'scale', config: { scale: { list: [{ value: 0.2, time: 0 }, { value: 0.6, time: 0.5 }, { value: 0.1, time: 1 }] }, minMult: 0.3 } },
    { type: 'color', config: { color: { list: [{ value: '00c8ff', time: 0 }, { value: '0080c0', time: 0.5 }, { value: '004080', time: 1 }] } } },
    { type: 'moveSpeedStatic', config: { min: 20, max: 80 } },
    { type: 'spawnShape', config: { type: 'circle', data: { radius: 15 } } },
  ],
};
// Stop: emitter.emit = false; let particles exhaust

Block — cyan flash, 200ms

const BLOCK_CONFIG: EmitterConfig = {
  lifetime: { min: 0.1, max: 0.2 }, frequency: 0.001, emitterLifetime: 0.02, maxParticles: 15,
  behaviors: [
    { type: 'alpha', config: { alpha: { list: [{ value: 1, time: 0 }, { value: 0, time: 1 }] } } },
    { type: 'scale', config: { scale: { list: [{ value: 1.0, time: 0 }, { value: 0.2, time: 1 }] } } },
    { type: 'color', config: { color: { list: [{ value: '80ffff', time: 0 }, { value: '0080ff', time: 1 }] } } },
    { type: 'moveSpeedStatic', config: { min: 100, max: 300 } },
    { type: 'rotationStatic', config: { min: 0, max: 360 } },
    { type: 'spawnShape', config: { type: 'circle', data: { radius: 10 } } },
  ],
};

Finisher — gold explosion, 1s

const FINISHER_CONFIG: EmitterConfig = {
  lifetime: { min: 0.5, max: 1.0 }, frequency: 0.001, emitterLifetime: 0.15, maxParticles: 50,
  behaviors: [
    { type: 'alpha', config: { alpha: { list: [{ value: 1, time: 0 }, { value: 0.8, time: 0.5 }, { value: 0, time: 1 }] } } },
    { type: 'scale', config: { scale: { list: [{ value: 1.5, time: 0 }, { value: 0.2, time: 1 }] }, minMult: 0.8 } },
    { type: 'color', config: { color: { list: [{ value: 'ffffff', time: 0 }, { value: 'ffdd00', time: 0.3 }, { value: 'ff8800', time: 0.7 }, { value: 'ff2200', time: 1 }] } } },
    { type: 'moveSpeedStatic', config: { min: 150, max: 500 } },
    { type: 'rotationStatic', config: { min: 0, max: 360 } },
    { type: 'spawnShape', config: { typ
Read more
Ships withvexjoy-agent

Essays and writing behind this toolkit live at vexjoy.com. AI agents skip steps. "Looks correct" replaces running tests. "Trivial change" replaces verification.

Get the whole plugin, auto-invoked