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.
import { useParticles } from "@carverjs/core/hooks";Particles use GPU-instanced rendering via InstancedMesh. Each emitter is a single draw call regardless of particle count.
Quick Start #
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 #
| Property | Type | Description |
|---|---|---|
ref | RefObject<Object3D> | Attach to a <group> to position the emitter |
burst(count?) | (number?) => void | Emit a burst of particles (default: 30) |
start() | () => void | Start continuous emission |
stop() | () => void | Stop emission (alive particles finish their lifetime) |
clear() | () => void | Stop emission and kill all particles immediately |
setRate(rate) | (number) => void | Change emission rate (stream mode) |
getActiveCount() | () => number | Current number of alive particles |
isEmitting() | () => boolean | Whether the emitter is currently emitting |
reset() | () => void | Reset to initial state |
Options #
useParticles accepts all ParticleEmitterConfig fields plus:
| Option | Type | Default | Description |
|---|---|---|---|
preset | ParticlePreset | — | Base preset: "fire", "smoke", "explosion", "sparks", "rain", "snow", "magic", "confetti". Individual props override preset values |
enabled | boolean | true | Enable/disable the emitter |
Emission Config #
| Option | Type | Default | Description |
|---|---|---|---|
maxParticles | number | 1000 | Maximum alive particles at once |
emission | "stream" | "burst" | "stream" | Emission mode |
rate | ValueRange | 50 | Particles per second (stream mode) |
bursts | BurstConfig[] | — | Burst schedule (burst mode) |
duration | number | Infinity | Emission duration. 0 = one-shot |
loop | boolean | true | Loop after duration ends |
startDelay | number | 0 | Delay before first emission (seconds) |
autoPlay | boolean | true | Start emitting immediately |
Emitter Shape #
| Option | Type | Default | Description |
|---|---|---|---|
shape | EmitterShapeConfig | { shape: "point" } | Emission shape |
Shapes #
// 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 #
| Option | Type | Default | Description |
|---|---|---|---|
particle.speed | ValueRange | 5 | Initial speed |
particle.lifetime | ValueRange | 1 | Lifetime in seconds |
particle.size | ValueRange | 1 | Initial scale |
particle.rotation | ValueRange | 0 | Initial rotation (radians) |
particle.rotationSpeed | ValueRange | 0 | Rotation speed (radians/sec) |
particle.color | ColorRange | "#ffffff" | Initial color |
particle.alpha | ValueRange | 1 | Initial opacity |
particle.acceleration | [x, y, z] | [0, 0, 0] | Constant acceleration |
particle.gravity | number | 0 | Downward gravity force |
particle.drag | number | 0 | Linear drag (0 = none, 1 = full stop) |
ValueRange can be a single number or [min, max] for randomization:
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).
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 #
| Option | Type | Default | Description |
|---|---|---|---|
blendMode | "normal" | "additive" | "multiply" | "screen" | "normal" | Particle blend mode |
texture | Texture | string | — | Particle texture (URL or Three.js Texture) |
spriteSheet | SpriteSheetConfig | — | Animated sprite sheet |
billboard | boolean | true | Particles face the camera |
space | "world" | "local" | "world" | Coordinate space for simulation |
sortByDistance | boolean | false | Sort back-to-front for transparency |
Blend Modes #
| Mode | Best For |
|---|---|
"normal" | Smoke, confetti, solid particles |
"additive" | Fire, sparks, magic, glow effects |
"multiply" | Shadows, dark effects |
"screen" | Bright overlays |
Sprite Sheets #
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:
const explosion = useParticles({
preset: "explosion",
autoPlay: false,
});
// Trigger on demand
function onHit() {
explosion.burst(80);
}Burst Schedule #
const particles = useParticles({
emission: "burst",
bursts: [
{ time: 0, count: [60, 100] },
{ time: 0.1, count: [20, 40] },
],
duration: 0,
loop: false,
});| Field | Type | Default | Description |
|---|---|---|---|
time | number | 0 | Offset from emitter start (seconds) |
count | ValueRange | — | Required. Particles to emit |
cycles | number | 1 | Repeat count. 0 = infinite |
interval | number | 1 | Delay between cycles (seconds) |
Presets #
8 built-in presets for common effects:
| Preset | Mode | Description |
|---|---|---|
"fire" | stream | Ascending glow, orange to transparent, additive |
"smoke" | stream | Slow rise, grey, expanding, normal blend |
"explosion" | burst | Radial burst, quick fade, additive |
"sparks" | burst | Fast bright particles with gravity |
"rain" | stream | Vertical downpour, additive |
"snow" | stream | Gentle falling, white, normal blend |
"magic" | stream | Colorful sphere emission, additive |
"confetti" | burst | Tumbling colored particles with gravity |
Custom Presets #
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 #
// 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 #
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 #
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:
| Phase | Behavior |
|---|---|
"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.