useParticles

useParticles creates and controls a particle emitter for visual effects like fire, smoke, explosions, and more. It reads from the ParticleManager which is automatically set up by Game.

tsx
import { useParticles } from "@carverjs/core/hooks";

Quick Start #

tsx
import { useRef } from "react";
import { Actor } from "@carverjs/core/components";
import { useParticles } from "@carverjs/core/hooks";
import type { Group } from "@carverjs/core/types";

function Torch() {
  const particles = useParticles({ preset: "fire" });

  return (
    <Actor type="primitive" shape="box" color="#333">
      <group ref={particles.ref} position={[0, 1, 0]} />
    </Actor>
  );
}

Return Value #

PropertyTypeDescription
refRefObject<Object3D>Attach to a <group> to position the emitter
burst(count?)(number?) => voidEmit a burst of particles (default: 30)
start()() => voidStart continuous emission
stop()() => voidStop emission (alive particles finish their lifetime)
clear()() => voidStop emission and kill all particles immediately
setRate(rate)(number) => voidChange emission rate (stream mode)
getActiveCount()() => numberCurrent number of alive particles
isEmitting()() => booleanWhether the emitter is currently emitting
reset()() => voidReset to initial state

Options #

useParticles accepts all ParticleEmitterConfig fields plus:

OptionTypeDefaultDescription
presetParticlePresetBase preset: "fire", "smoke", "explosion", "sparks", "rain", "snow", "magic", "confetti". Individual props override preset values
enabledbooleantrueEnable/disable the emitter

Emission Config #

OptionTypeDefaultDescription
maxParticlesnumber1000Maximum alive particles at once
emission"stream" | "burst""stream"Emission mode
rateValueRange50Particles per second (stream mode)
burstsBurstConfig[]Burst schedule (burst mode)
durationnumberInfinityEmission duration. 0 = one-shot
loopbooleantrueLoop after duration ends
startDelaynumber0Delay before first emission (seconds)
autoPlaybooleantrueStart emitting immediately

Emitter Shape #

OptionTypeDefaultDescription
shapeEmitterShapeConfig{ shape: "point" }Emission shape

Shapes #

tsx
// Point (all particles emit from one spot)
shape: { shape: "point" }

// Cone (spread upward)
shape: { shape: "cone", angle: Math.PI / 4, radius: 1, surface: false }

// Sphere (radial emission)
shape: { shape: "sphere", radius: 1, surface: false }

// Rectangle (flat area)
shape: { shape: "rectangle", width: 1, height: 1 }

// Edge (line segment)
shape: { shape: "edge", from: [-0.5, 0, 0], to: [0.5, 0, 0] }

// Ring (donut area)
shape: { shape: "ring", radius: 1, innerRadius: 0.8 }

Particle Properties #

OptionTypeDefaultDescription
particle.speedValueRange5Initial speed
particle.lifetimeValueRange1Lifetime in seconds
particle.sizeValueRange1Initial scale
particle.rotationValueRange0Initial rotation (radians)
particle.rotationSpeedValueRange0Rotation speed (radians/sec)
particle.colorColorRange"#ffffff"Initial color
particle.alphaValueRange1Initial opacity
particle.acceleration[x, y, z][0, 0, 0]Constant acceleration
particle.gravitynumber0Downward gravity force
particle.dragnumber0Linear drag (0 = none, 1 = full stop)

ValueRange can be a single number or [min, max] for randomization:

tsx
particle: {
  speed: [2, 5],        // Random between 2 and 5
  lifetime: 1,          // Always exactly 1 second
  color: ["#ff0000", "#ffff00"],  // Random color between red and yellow
}

Over-Lifetime Curves #

Modify particle properties over their lifetime using keyframe curves. t is normalized (0 = birth, 1 = death).

tsx
overLifetime: {
  // Size multiplier (multiplies initial size)
  size: [
    { t: 0, value: 0 },
    { t: 0.2, value: 1 },
    { t: 1, value: 0 },
  ],

  // Alpha multiplier (multiplies initial alpha)
  alpha: [
    { t: 0, value: 1 },
    { t: 0.7, value: 0.8 },
    { t: 1, value: 0 },
  ],

  // Color gradient (replaces initial color)
  color: [
    { t: 0, color: "#ffffff" },
    { t: 0.3, color: "#ff8800" },
    { t: 1, color: "#331100" },
  ],

  // Speed multiplier
  speed: [
    { t: 0, value: 1 },
    { t: 1, value: 0.2 },
  ],

  // Rotation speed multiplier
  rotationSpeed: [
    { t: 0, value: 1 },
    { t: 1, value: 0 },
  ],
}

Rendering #

OptionTypeDefaultDescription
blendMode"normal" | "additive" | "multiply" | "screen""normal"Particle blend mode
textureTexture | stringParticle texture (URL or Three.js Texture)
spriteSheetSpriteSheetConfigAnimated sprite sheet
billboardbooleantrueParticles face the camera
space"world" | "local""world"Coordinate space for simulation
sortByDistancebooleanfalseSort back-to-front for transparency

Blend Modes #

ModeBest For
"normal"Smoke, confetti, solid particles
"additive"Fire, sparks, magic, glow effects
"multiply"Shadows, dark effects
"screen"Bright overlays

Sprite Sheets #

tsx
spriteSheet: {
  texture: "/textures/smoke-sheet.png",
  columns: 8,
  rows: 8,
  totalFrames: 60,
  fps: 30,
  loop: true,
  randomStart: true,
}

Burst Mode #

For one-shot effects like explosions:

tsx
const explosion = useParticles({
  preset: "explosion",
  autoPlay: false,
});

// Trigger on demand
function onHit() {
  explosion.burst(80);
}

Burst Schedule #

tsx
const particles = useParticles({
  emission: "burst",
  bursts: [
    { time: 0, count: [60, 100] },
    { time: 0.1, count: [20, 40] },
  ],
  duration: 0,
  loop: false,
});
FieldTypeDefaultDescription
timenumber0Offset from emitter start (seconds)
countValueRangeRequired. Particles to emit
cyclesnumber1Repeat count. 0 = infinite
intervalnumber1Delay between cycles (seconds)

Presets #

8 built-in presets for common effects:

PresetModeDescription
"fire"streamAscending glow, orange to transparent, additive
"smoke"streamSlow rise, grey, expanding, normal blend
"explosion"burstRadial burst, quick fade, additive
"sparks"burstFast bright particles with gravity
"rain"streamVertical downpour, additive
"snow"streamGentle falling, white, normal blend
"magic"streamColorful sphere emission, additive
"confetti"burstTumbling colored particles with gravity

Custom Presets #

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

registerParticlePreset("myEffect", {
  maxParticles: 500,
  rate: 60,
  shape: { shape: "sphere", radius: 0.5 },
  particle: {
    speed: [2, 5],
    lifetime: [1, 3],
    color: ["#00ffaa", "#0044ff"],
  },
  blendMode: "additive",
});

// Use it
const particles = useParticles({ preset: "myEffect" as any });

World/Local Space #

tsx
// World space (default): particles stay where they were born
const trail = useParticles({
  preset: "fire",
  space: "world",
});

// Local space: particles follow the emitter
const aura = useParticles({
  preset: "magic",
  space: "local",
});

Lifecycle Callbacks #

tsx
const particles = useParticles({
  preset: "explosion",
  autoPlay: false,
  onParticleBorn: (index) => { /* particle spawned */ },
  onParticleDeath: (index) => { /* particle expired */ },
  onComplete: () => { /* all particles died after emission stopped */ },
});

Full Example — Rocket Thruster #

tsx
import { useRef } from "react";
import { Actor } from "@carverjs/core/components";
import { useParticles, useGameLoop, useInput } from "@carverjs/core/hooks";
import type { Group } from "@carverjs/core/types";

function Rocket() {
  const ref = useRef<Group>(null);
  const { isDown } = useInput();

  const thruster = useParticles({
    maxParticles: 300,
    rate: 100,
    shape: { shape: "cone", angle: Math.PI / 8, radius: 0.1 },
    particle: {
      speed: [5, 10],
      lifetime: [0.3, 0.8],
      size: [0.1, 0.3],
      color: ["#ff6600", "#ffcc00"],
    },
    overLifetime: {
      alpha: [
        { t: 0, value: 1 },
        { t: 1, value: 0 },
      ],
      size: [
        { t: 0, value: 1 },
        { t: 1, value: 0.3 },
      ],
    },
    blendMode: "additive",
    autoPlay: false,
  });

  useGameLoop(() => {
    if (isDown("Space")) {
      thruster.start();
      thruster.setRate(150);
    } else {
      thruster.stop();
    }
  });

  return (
    <Actor ref={ref} type="primitive" shape="box" color="#888">
      <group ref={thruster.ref} position={[0, -1, 0]} />
    </Actor>
  );
}

Game Phase Integration #

Particles automatically respect the game phase:

PhaseBehavior
"loading"All emitters frozen
"playing"Normal operation
"paused"Emitters freeze — no emission, no updates
"gameover"Same as paused

Cleanup #

Emitters created via useParticles are automatically destroyed when the component unmounts. The GPU resources (InstancedMesh, material, textures) are disposed.


Type Definitions #

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