SKILL.md
$28
const container = new ParticleContainer({
texture,
boundsArea: new Rectangle(0, 0, app.screen.width, app.screen.height),
dynamicProperties: {
position: true,
rotation: false,
color: false,
},
});
for (let i = 0; i < 10000; i++) {
container.addParticle(
new Particle({
texture,
x: Math.random() * app.screen.width,
y: Math.random() * app.screen.height,
}),
);
}
app.stage.addChild(container);
**Related skills:** `pixijs-scene-core-concepts` (scene graph basics), `pixijs-scene-sprite` (when you need full features per object), `pixijs-assets` (shared textures, atlases), `pixijs-performance` (batching, texture optimization), `pixijs-scene-container` (wrap with other display objects).
## Constructor options
### ParticleContainerOptions
All `Container` options (`position`, `scale`, `tint`, `label`, `filters`, `zIndex`, etc.) are also valid here — see `skills/pixijs-scene-core-concepts/references/constructor-options.md`. Note that `children` is omitted: use `particles` instead.
| Option | Type | Default | Description |
| ------------------- | -------------------- | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `texture` | `Texture` | `null` | Shared base texture for all particles. If omitted, the container falls back to the texture of the first particle added; every particle must share the same base texture source. |
| `particles` | `T[]` | `[]` | Initial array of `Particle` (or `IParticle`) instances. Equivalent to calling `addParticle` for each, but skips per-call view updates. |
| `dynamicProperties` | `ParticleProperties` | `{ vertex: false, position: true, rotation: false, uvs: false, color: false }` | Flags for which particle attributes re-upload to the GPU every frame. Only `position` is dynamic by default; mark what you animate, leave the rest static for speed. |
| `roundPixels` | `boolean` | `false` | Rounds particle positions to the nearest pixel. Produces crisper rendering for pixel-art styles at the cost of smooth sub-pixel motion. |
| `shader` | `Shader` | default particle shader | Replaces the default particle shader. The custom shader must declare `aPosition`, `aUV`, `aColor`, plus any dynamic-only attributes enabled via `dynamicProperties`. |
`boundsArea` is inherited from `Container` but is effectively required on `ParticleContainer`: the container returns empty bounds `(0, 0, 0, 0)` by default for performance, so without `boundsArea` it is culled as invisible when culling is active and `containsPoint` always misses.
### ParticleOptions
`Particle` is a lightweight struct, not a `Container` subclass — none of the `ContainerOptions` fields apply. The full option list:
| Option | Type | Default | Description |
| ---------- | ------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `texture` | `Texture` | — | Required. Texture used to render this particle. All particles in the same `ParticleContainer` must share the same base texture source. |
| `x` | `number` | `0` | X position in the container's local space. |
| `y` | `number` | `0` | Y position in the container's local space. |
| `scaleX` | `number` | `1` | Horizontal scale factor. |
| `scaleY` | `number` | `1` | Vertical scale factor. |
| `anchorX` | `number` | `0` | Horizontal anchor in 0–1 range; `0` is left, `0.5` is center, `1` is right. |
| `anchorY` | `number` | `0` | Vertical anchor in 0–1 range; `0` is top, `0.5` is center, `1` is bottom. |
| `rotation` | `number` | `0` | Rotation in radians. |
| `tint` | `ColorSource` | `0xffffff` | Tint color as hex number or CSS color string. Combined with `alpha` into the internal `color` field. |
| `alpha` | `number` | `1` | Transparency (0–1). Values outside the range are clamped. Combined with `tint` into the internal `color` field. |
The constructor also accepts a bare `Texture` as its sole argument (`new Particle(texture)`), which is shorthand for `new Particle({ texture })` using the defaults above.
`Particle.defaultOptions` is a static object you can reassign to change defaults globally; see the "Particle creation" section below.
## Core Patterns
### Particle creation
const particle = new Particle({
texture,
x: 100,
y: 200,
scaleX: 0.5,
scaleY: 0.5,
anchorX: 0.5,
anchorY: 0.5,
rotation: Math.PI / 4,
tint: 0xff0000,
alpha: 0.8,
});
container.addParticle(particle);
`Particle` is a lightweight struct with flat numeric fields: `x`, `y`, `scaleX`, `scaleY`, `anchorX`, `anchorY`, `rotation`, `color`, `texture`. It also exposes `tint` (hex/CSS color) and `alpha` (0-1) as setters that combine into the internal `color` field. No transform hierarchy, no events, no filters.
You can pass a `Texture` directly as the sole argument: `new Particle(texture)`.
Override `Particle.defaultOptions` to change defaults globally:
Particle.defaultOptions = {
...Particle.defaultOptions,
anchorX: 0.5,
anchorY: 0.5,
};
### Pre-populating with the particles option
const particles = Array.from(
{ length: 10000 },
() =>
new Particle({
texture,
x: Math.random() * 800,
y: Math.random() * 600,
}),
);
const container = new ParticleContainer({
texture,
boundsArea: new Rectangle(0, 0, 800, 600),
particles,
});
Passing `particles` in the constructor is equivalent to creating the container empty and calling `addParticle` for each one, but avoids per-call view updates.
### Dynamic vs static properties and update()
const container = new ParticleContainer({
dynamicProperties: {
rotation: true,
},
});
`dynamicProperties` controls which particle attributes re-upload to the GPU every frame. The defaults on `ParticleContainer.defaultOptions.dynamicProperties` are:
{ vertex: false, position: true, rotation: false, uvs: false, color: false }
You only need to override the properties you are animating; the rest inherit the defaults (position dynamic, everything else static). Five properties in total:
- `vertex`: scale/anchor vertices
- `position`
- `rotation`
- `uvs`: texture coordinates (for frame-swapped particles)
- `color`: tint and alpha
Mark only what you animate; static properties are cheaper. If you change a static property at runtime, call `container.update()` to re-upload:
container.particleChildren.forEach((p) => {
p.tint = 0x00ff00;
});
container.update();
### Batch operations on particleChildren
// Bulk add
const batch = [];
for (let i = 0; i < 5000; i++) {
batch.push(
new Particle({ texture, x: Math.random() 800, y: Math.random() 600 }),
);
}
container.particleChildren.push(...batch);
container.update();
// Bulk remove
container.particleChildren.length = 0;
container.update();
`addParticle`, `addParticleAt`, `removeParticle`, `removeParticleAt`, and `removeParticles` all trigger view updates per call. For large batch operations, direct array manipulation plus a single `update()` is faster.
### Texture and shader options
`texture` in `ParticleContainerOptions` is optional. If omitted, the container falls back to the texture of the first particle added; every subsequent particle must share the same base texture source. Set it explicitly when you want to declare the atlas up front, or when the first particle might change mid-run:
const container = new ParticleContainer({ texture });
`shader` lets you replace the default particle shader with any `Shader` instance. The custom shader must declare the attributes the particle pipe uploads (`aPosition`, `aUV`, `aColor`, plus any dynamic-only attributes enabled via `dynamicProperties`). Use this for custom blending math, distance-field sprites, or non-standard effects:
const container = new ParticleContainer({ texture, shader: myCustomShader });
### Limitations
`ParticleContainer` intentionally sacrifices features for speed:
- No filters, masks, or blend modes on individual particles.
- No nested children on particles.
- No automatic bounds calculation.
- All particles must share the same base texture source (atlases work; multiple unrelated textures do not).
- Custom shaders are supported via the `shader` option.
### Container method migration
`ParticleContainer` uses a separate child management API optimized for GPU buffer updates. The standard `Container` child methods throw when called on a `ParticleContainer`.
Standard Container method
ParticleContainer equivalent
`addChild(child)`
`addParticle(particle)`
`removeChild(child)`
`removeParticle(particle)`
`addChildAt(child, index)`
`addParticleAt(particle, index)`
`removeChildAt(index)`
`removeParticleAt(index)`
`removeChildren(begin, end)`
`removeParticles(begin, end)`
`getChildAt(index)`
Access `container.particleChildren[index]` directly
`swapChildren()`
Not available
`reparentChild()`
Not available
## Common Mistakes
### [CRITICAL] Adding Sprites to ParticleContainer
Wrong:
const container = new ParticleContainer();
const sprite = new Sprite(texture);
container.addChild(sprite);
Correct:
const container = new ParticleContainer();
const particle = new Particle(texture);
container.addParticle(particle);
`ParticleContainer` does not accept `Sprite` children. `addChild` throws an error. Particles must be `Particle` instances (or any object implementing `IParticle`), added via `addParticle`. This is a complete rework from v7, where `ParticleContainer` accepted `Sprite` children.
### [HIGH] Not setting boundsArea on ParticleContainer
Wrong:
const container = new ParticleContainer();
// bounds is always (0, 0, 0, 0) — culling and hit testing fail
Correct:
const container = new ParticleContainer({
boundsArea: new Rectangle(0, 0, 800, 600),
});
`ParticleContainer` returns empty bounds `(0, 0, 0, 0)` by default for performance. Without `boundsArea`, the container is culled as invisible when culling is active, and `containsPoint` always misses. Set `boundsArea` to the region your particles occupy.
### [HIGH] Using children instead of particleChildren
Wrong:
container.addParticle(new Particle(texture));
console.log(container.children.length); // 0
Correct:
container.addParticle(new Particle(texture));
console.log(container.particleChildren.length); // 1
Particles are stored in the `particleChildren` array, not `children`. The standard `Container.children` array is empty on a `ParticleContainer`. All particle enumeration, counting, and manipulation must use `particleChildren` plus the `*Particle` methods.
### [MEDIUM] Do not use ParticleContainer as a normal container
`ParticleContainer` contains particles, not display objects. If you need to group a particle system with a background sprite or UI overlay, wrap the `ParticleContainer` itself inside a plain `Container`:
const world = new Container();
world.addChild(backgroundSprite, particleContainer, uiLayer);