pixijs-scene-particle-container

Use this skill when rendering thousands of lightweight sprites in PixiJS v8. Covers ParticleContainer with Particle instances, addParticle/removeParticle, particleChildren array, dynamicProperties (vertex, position, rotation, uvs, color), boundsArea, roundPixels, update. Triggers on: ParticleContainer, Particle, IParticle, addParticle, particleChildren, dynamicProperties, boundsArea, particle effects, constructor options, ParticleContainerOptions, ParticleOptions.

Skill file

Preview skill file
---
name: pixijs-scene-particle-container
description: "Use this skill when rendering thousands of lightweight sprites in PixiJS v8. Covers ParticleContainer with Particle instances, addParticle/removeParticle, particleChildren array, dynamicProperties (vertex, position, rotation, uvs, color), boundsArea, roundPixels, update. Triggers on: ParticleContainer, Particle, IParticle, addParticle, particleChildren, dynamicProperties, boundsArea, particle effects, constructor options, ParticleContainerOptions, ParticleOptions."
license: MIT
---

`ParticleContainer` is a specialized container for rendering hundreds to tens of thousands of lightweight sprites in a single draw call. Use it for particle effects, bullet patterns, or any case where you need a large number of similar-looking objects with minimal per-object overhead. Particles share a single base texture and have a restricted transform set; they are not full `Container` children.

Assumes familiarity with `pixijs-scene-core-concepts`. `ParticleContainer` is a special leaf in a different sense: it contains `Particle` instances in its own `particleChildren` array and rejects normal PixiJS children. Use `addParticle`, not `addChild`, and wrap the whole `ParticleContainer` in a `Container` if you need to group it with other scene objects.

The Particle API is new in v8 but is stable for production use.

## Quick Start

```ts
const texture = await Assets.load("particle.png");

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

```ts
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:

```ts
Particle.defaultOptions = {
  ...Particle.defaultOptions,
  anchorX: 0.5,
  anchorY: 0.5,
};
```

### Pre-populating with the particles option

```ts
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()

```ts
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:

```ts
{ 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:

```ts
container.particleChildren.forEach((p) => {
  p.tint = 0x00ff00;
});
container.update();
```

### Batch operations on particleChildren

```ts
// 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:

```ts
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:

```ts
const container = new ParticleContainer({ texture, shader: myCustomShader });
```

### Limitations

`ParticleContainer` intentionally sacrifices features for speed:

- No filters, masks, or per-particle blend modes. Blend modes apply at the container level: set `blendMode` on the `ParticleContainer` or let it inherit from a parent `Container` (the default `"inherit"` resolves to the ancestor's blend mode, like `Sprite` and `Mesh`). The whole particle batch shares one blend mode.
- 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:

```ts
const container = new ParticleContainer();
const sprite = new Sprite(texture);
container.addChild(sprite);
```

Correct:

```ts
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:

```ts
const container = new ParticleContainer();
// bounds is always (0, 0, 0, 0) — culling and hit testing fail
```

Correct:

```ts
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:

```ts
container.addParticle(new Particle(texture));
console.log(container.children.length); // 0
```

Correct:

```ts
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`:

```ts
const world = new Container();
world.addChild(backgroundSprite, particleContainer, uiLayer);
```


## API Reference

- [ParticleContainer](https://pixijs.download/release/docs/scene.ParticleContainer.html.md)
- [ParticleContainerOptions](https://pixijs.download/release/docs/scene.ParticleContainerOptions.html.md)
- [Particle](https://pixijs.download/release/docs/scene.Particle.html.md)
- [ParticleOptions](https://pixijs.download/release/docs/scene.ParticleOptions.html.md)
- [IParticle](https://pixijs.download/release/docs/scene.IParticle.html.md)
- [ParticleProperties](https://pixijs.download/release/docs/scene.ParticleProperties.html.md)

Source

Creator's repository · pixijs/pixijs-skills

View on GitHub

License: MIT

Security

Security checks in progress
Results will appear here once audits complete
What this skill can do
Reads your filesConnects to the internetRuns code on your machine
Checked by 3 independent security firms
Does it try to trick the AI?Not yet checkedPending · Gen Agent Trust Hub
Does it sneak in hidden code?Not yet checkedPending · Socket
Does it have known bugs?Not yet checkedPending · Snyk