ParticleManager

ParticleManager is a singleton that manages all particle emitters. It uses GPU-instanced rendering via InstancedMesh and Structure-of-Arrays (SoA) particle data for zero-GC per-frame performance.

It is mounted automatically by Game — you do not need to set it up manually.

tsx
import { getParticleManager } from "@carverjs/core/systems";

How It Works #

  1. On Game mount, the ParticleFlush internal component is created, which calls getParticleManager().tick(delta) every frame at priority -12.

  2. Each frame, tick() runs for each registered emitter:

    • Reads game phase via useGameStore.getState() — emitters freeze during "paused" / "gameover" / "loading"

    • Emission phase: accumulates fractional particles (stream) or checks burst schedules, spawns new particles from the free-list pool

    • Update phase: advances age, applies velocity/acceleration/drag/gravity, evaluates over-lifetime curves, kills expired particles (returns to pool)

    • Write phase: composes instance matrices, writes color/alpha/sprite-frame to GPU buffers, sets mesh.count to alive count

  3. On Game unmount, all emitters are destroyed, GPU resources are disposed, and the singleton is reset.


Zero-GC Architecture #


Frame Priority #

text
Priority | System
---------|------------------
-50      | InputFlush
-48      | AudioFlush
-45      | Physics (Rapier)
-40      | earlyUpdate
-30      | fixedUpdate
-25      | CollisionFlush
-20      | update
-15      | TweenFlush
-12      | ParticleFlush        ← particles update here
-10      | lateUpdate
 -5      | GameLoopTick
  0      | R3F render

Particles run after tweens (-15) so tween-animated emitter positions are up-to-date. They run before lateUpdate (-10) so late-update code sees current particle state.


API #

getParticleManager() #

Returns the singleton ParticleManager instance. Creates one if it doesn't exist.

destroyParticleManager() #

Disposes all emitters, frees GPU resources, and destroys the singleton. Called automatically when Game unmounts.

Emitter Lifecycle #

MethodDescription
createEmitter(config)Create and register a new emitter. Returns a unique ID string
destroyEmitter(id)Remove an emitter and dispose its GPU resources
getEmitter(id)Get an EmitterInstance by ID for imperative control

Global Controls #

MethodDescription
setMode(mode)Set world mode ("2d" or "3d"). Called automatically by ParticleFlush
tick(delta)Advance all emitters by delta seconds. Called by ParticleFlush
dispose()Destroy all emitters and free resources

EmitterInstance Controls #

Each EmitterInstance returned by getEmitter() exposes:

MethodDescription
start()Start continuous emission
stop()Stop emission (alive particles finish their lifetime)
clear()Stop and kill all particles immediately
reset()Reset to initial state
burst(count?)Emit a burst of particles
setRate(rate)Change stream emission rate
getActiveCount()Number of alive particles
isEmitting()Whether currently emitting

Preset Registry #

tsx
import { getParticlePreset, registerParticlePreset } from "@carverjs/core/systems";

// Get a built-in preset config
const fireConfig = getParticlePreset("fire");

// Register a custom preset
registerParticlePreset("plasma", {
  maxParticles: 400,
  rate: 80,
  shape: { shape: "sphere", radius: 0.3 },
  particle: {
    speed: [1, 4],
    lifetime: [0.5, 1.5],
    color: ["#00ffaa", "#0044ff"],
  },
  blendMode: "additive",
});

Usage #

Most game code should use the useParticles hook or <ParticleEmitter> component, which provide auto-cleanup on unmount. Direct manager access is useful for systems-level code:

tsx
import { getParticleManager } from "@carverjs/core/systems";

// Direct emitter creation (you manage lifecycle)
const id = getParticleManager().createEmitter({
  maxParticles: 200,
  rate: 50,
  particle: { speed: 5, lifetime: 1, color: "#ff0000" },
  blendMode: "additive",
});

const emitter = getParticleManager().getEmitter(id)!;
emitter.burst(100);

// Clean up when done
getParticleManager().destroyEmitter(id);

Type Definitions #

See Types for ParticleEmitterConfig, ParticlePreset, EmitterShapeConfig, ParticlePropertyConfig, OverLifetimeConfig, ValueRange, ColorRange, LifetimeCurve, ColorGradient, BurstConfig, SpriteSheetConfig, and ParticleBlendMode.