SKILL.md
$28
group.addChild(body, head);
group.pivot.set(group.width / 2, group.height / 2);
group.scale.set(1.5);
app.stage.addChild(group);
**Related skills:** `pixijs-scene-core-concepts` (scene graph mental model, masking, layers, render groups), `pixijs-scene-sprite` / `pixijs-scene-graphics` / `pixijs-scene-text` / `pixijs-scene-mesh` (leaf objects that go inside containers), `pixijs-events` (`eventMode`, hit testing), `pixijs-math` (Matrix, toGlobal/toLocal detail), `pixijs-performance` (`cacheAsTexture`, culling, render groups).
## Core Patterns
### Constructor options
const container = new Container({
label: "world",
x: 100,
y: 50,
scale: 2,
rotation: Math.PI / 4,
alpha: 0.8,
visible: true,
tint: 0xffaa00,
blendMode: "add",
sortableChildren: true,
isRenderGroup: true,
origin: { x: 0, y: 0 },
boundsArea: new Rectangle(0, 0, 1920, 1080),
});
All `Container` options (`position`, `scale`, `tint`, `label`, `filters`, `zIndex`, etc.) are also valid here — see `skills/pixijs-scene-core-concepts/references/constructor-options.md`.
The `Container` constructor uses `assignWithIgnore` to bulk-copy every field in the options object onto the instance except `children`, `parent`, and `effects`. Any public property of `Container` is a valid constructor option: `cullable`, `cullArea`, `mask`, `filterArea`, `eventMode`, `hitArea`, and so on. The options block above groups the most common ones; see the shared reference above for the full list.
`isRenderGroup: true` promotes the container to its own render group so its transforms are applied on the GPU rather than recomputed per-child on the CPU. Use it on stable sub-trees (large static worlds, UI layers). Don't overuse; most scenes don't need explicit render groups and too many hurts performance. Profile before promoting. See `pixijs-scene-core-concepts/references/scene-management.md`.
`sortableChildren: true` causes children to be re-sorted by `zIndex` at the next render. See `zIndex` below.
`origin` is a first-class v8 transform helper: an `ObservablePoint` that acts as a rotation/scale center **without** moving the container. Where `pivot` shifts the projection of the local origin in parent space (so changing it displaces the object), `origin` leaves position alone and simply rotates/scales around the specified local point. Accepts `PointData`, a single number (applied to both axes), or can be set live via `container.origin.set(x, y)`. Setting both `pivot` and `origin` on the same container produces compounding behavior and is discouraged; pick one.
`boundsArea` forces `getBounds()` to return a fixed rectangle instead of recursively measuring children; it is a performance win for containers with hundreds of cheap, predictable children.
`cullable` and `cullArea` are valid constructor options (the `assignWithIgnore` pass copies them like any other field), but they are documented in `pixijs-performance` because culling setup is a performance concern rather than a scene-graph concern.
### Leaves vs containers
const parent = new Container();
const sprite = new Sprite(texture);
parent.addChild(sprite);
Only `Container` (and subclasses intended to hold children, like `RenderLayer`) should have children. `Sprite`, `Graphics`, `Text`, `Mesh`, `ParticleContainer` particles, and `DOMContainer` content are leaves by convention in PixiJS v8. Wrap them in a `Container` whenever you need to group things: give the container the layout logic and keep the leaves pure visual data. Adding children to a leaf logs a deprecation warning and is scheduled to become a hard error.
### Adding and removing children
const parent = new Container();
parent.addChild(a, b, c);
parent.addChildAt(d, 0);
parent.swapChildren(a, b);
parent.setChildIndex(c, 0);
parent.removeChild(b);
parent.removeChildAt(0);
parent.removeChildren();
parent.removeChildren(0, 2);
`addChild` accepts any number of children and returns the first one. Children render in array order: index 0 is drawn first (behind), the last index is drawn last (in front). `addChildAt` inserts at a specific index; `setChildIndex` moves an existing child; `swapChildren` exchanges two children's positions.
`removeChildren(beginIndex?, endIndex?)` removes a slice and returns the removed array.
Calling `addChildAt` with a child that already belongs to the same container silently moves it to the new index. No `added` / `childAdded` / `removed` / `childRemoved` events fire, because the parent-child relationship didn't change. Events only fire when the child comes from a different parent (or from no parent).
For reparenting that preserves world transform (so the child doesn't visually jump), use `reparentChild` / `reparentChildAt`. For swapping a child in place while copying the old child's local transform, use `replaceChild`.
### Transform properties
const obj = new Container();
obj.x = 100;
obj.y = 200;
obj.position.set(100, 200);
obj.scale.set(2);
obj.scale = 2;
obj.rotation = Math.PI / 4;
obj.angle = 45;
obj.pivot.set(50, 50);
obj.skew.set(0.1, 0.2);
obj.alpha = 0.5;
obj.tint = 0xff0000;
obj.visible = false;
obj.renderable = false;
- `position`, `scale`, `pivot`, `skew` are `ObservablePoint`s. Assigning `scale = 2` is valid and sets both axes.
- `rotation` is radians; `angle` is degrees; they are aliases that stay in sync.
- `pivot` sets the point in local space that maps to `position` in parent space; changing it both moves and rotates the container.
- `alpha` and `tint` multiply down through children. `blendMode` applies to this container's draw instructions.
- `visible = false` skips rendering and transform updates. `renderable = false` skips rendering but still updates transforms (use when you need `getBounds()` or hit-testing without drawing).
### zIndex and sortableChildren
const world = new Container({ sortableChildren: true });
const ground = new Sprite(groundTexture);
ground.zIndex = 0;
const player = new Sprite(playerTexture);
player.zIndex = 10;
const ui = new Sprite(uiTexture);
ui.zIndex = 100;
world.addChild(ground, player, ui);
When `sortableChildren` is `true`, the container re-sorts its children by `zIndex` before the next render. Changing any child's `zIndex` automatically re-marks the parent as needing sort. Sort only what you need to sort; leaving `sortableChildren` off is cheaper. If you want full manual control, call `container.sortChildren()` yourself after changing `zIndex` values.
For render-order control that is decoupled from the hierarchy (children keep their logical parent for transforms but render at a different z), use `RenderLayer`. See `pixijs-scene-core-concepts/references/scene-management.md`.
### Bounds and coordinate conversion
const bounds = container.getBounds();
console.log(bounds.x, bounds.y, bounds.width, bounds.height);
const rect = container.getBounds().rectangle;
const local = new Point(10, 20);
const world = container.toGlobal(local);
const backToLocal = container.toLocal(world);
const selfPos = container.getGlobalPosition();
`getBounds()` returns a `Bounds` object (not a `Rectangle`); it exposes `x`, `y`, `width`, `height`, and a `.rectangle` getter for APIs that need an actual `Rectangle`. The signature is `getBounds(skipUpdate?: boolean, bounds?: Bounds)` — pass `true` as the first arg to skip the forced transform update, and an optional `Bounds` instance as the second arg to avoid allocating a new one.
`toGlobal(point)` converts a point in this container's local space to scene-root space. `toLocal(point, from?)` converts from another container's local space (or global space if `from` is omitted). `getGlobalPosition()` is shorthand for `parent.toGlobal(this._position)`.
### Sizing
const sprite = new Sprite(texture);
sprite.setSize(200, 100);
const { width, height } = sprite.getSize();
`setSize` adjusts `scale` so the container's bounds fit the requested pixel size, in one operation. Setting `.width` and `.height` individually works, but each assignment triggers a separate bounds recomputation; prefer `setSize` when changing both axes.
### Container events
const parent = new Container();
parent.on("childAdded", (child, container, index) => {
console.log("added at", index, child.label);
});
parent.on("childRemoved", (child, container, index) => {
console.log("removed from", index);
});
const child = new Container();
child.on("added", (newParent) => console.log("entered", newParent.label));
child.on("removed", (oldParent) => console.log("left", oldParent.label));
child.on("visibleChanged", (visible) => console.log("visible:", visible));
child.on("destroyed", (destroyed) => console.log("gone", destroyed.label));
parent.addChild(child);
Event
Fires on
Arguments
`childAdded`
the parent receiving the child
`(child, container, index)`
`childRemoved`
the parent losing the child
`(child, container, index)`
`added`
the child that was attached
`(parent)`
`removed`
the child that was detached
`(parent)`
`destroyed`
the destroyed container
`(container)`
`visibleChanged`
the container whose `visible` flipped
`(visible)`
These are emitted on the `EventEmitter` side of `Container`; do not confuse them with pointer events from `pixijs-events`.
`destroyed` fires after internal cleanup but before listeners are removed, so by the time your handler runs `position`, `scale`, `pivot`, `origin`, `skew`, and `parent` have already been nulled, and `children` has been emptied (length 0, but the array reference itself is not nulled). Capture any data you need from the container before calling `destroy()`, not inside the handler.
### Per-frame updates with onRender
const container = new Container();
container.onRender = (renderer) => {
container.rotation += 0.01;
};
container.onRender = null;
`onRender` runs every frame while the container is being rendered, and receives the active `Renderer`. Use it for lightweight animation or per-frame updates tied to a specific container. In v7 this pattern was done by overriding `updateTransform`, which no longer runs every frame in v8. Set `onRender = null` to detach the callback.
### Finding and removing from parent
const player = world.getChildByLabel("player");
const enemies = world.getChildrenByLabel(/enemy-\d+/, true);
const bounds = hud.getLocalBounds();
oldSprite.removeFromParent();
- `getChildByLabel(label, deep?)` — first match by string or `RegExp`. Pass `true` for a recursive search.
- `getChildrenByLabel(label, deep?, out?)` — all matches. Accepts an optional reusable output array.
- `getLocalBounds()` — bounds in this container's own coordinate space, ignoring parent transforms. Cheaper than `getBounds()` for self-contained layout math.
- `removeFromParent()` — detaches `this` from its current parent (no-op if already orphaned).
### Destroy
container.destroy();
container.destroy({
children: true,
texture: true,
textureSource: true,
});
console.log(container.destroyed);
By default `destroy()` unlinks this container from its parent and tears down its own state. Pass `{ children: true }` to recursively destroy every descendant; this is the usual call for killing a whole subtree. `texture: true` and `textureSource: true` additionally destroy the GPU resources referenced by leaf children (useful for sprites whose textures you loaded just for them). If `cacheAsTexture` is on, disable it with `container.cacheAsTexture(false)` before destroying.
## Common Mistakes
### [CRITICAL] Adding children to leaf scene objects
Wrong:
const sprite = new Sprite(texture);
const overlay = new Sprite(overlayTexture);
sprite.addChild(overlay);
Correct:
const group = new Container();
const sprite = new Sprite(texture);
const overlay = new Sprite(overlayTexture);
group.addChild(sprite, overlay);
Sprites, Graphics, Text, and Mesh are leaves. Adding children to them logs a deprecation warning now and will be an error in a future version. Always wrap them in a `Container` when you need grouping.
### [HIGH] Expecting getBounds() to return a Rectangle
Wrong:
const rect = container.getBounds();
rect.contains(x, y); // TypeError: contains is not a function
Correct:
const rect = container.getBounds().rectangle;
rect.contains(x, y);
const bounds = container.getBounds();
console.log(bounds.width, bounds.height);
`getBounds()` returns a `Bounds` instance in v8. Its basic getters (`x`, `y`, `width`, `height`) work, but for `Rectangle`-specific methods like `.contains()` or passing to APIs that require a `Rectangle`, read the `.rectangle` property.
### [HIGH] Using cacheAsBitmap instead of cacheAsTexture
Wrong:
container.cacheAsBitmap = true;
Correct:
container.cacheAsTexture(true);