A scene’s drawables form a tree: every renderable node has zero or one parent and zero or more children. Transforms cascade down the tree, drawing follows the tree’s order, and you compose larger groups by nesting smaller ones. The result is that “this object follows that object” stops being a special case — it’s the default.
Containers and nodes
Two key types: RenderNode is the abstract base for everything that can be drawn. Container is a RenderNode that holds children. Sprites, text, graphics, and meshes are all render nodes; containers are how you group them.
Every scene starts with one container already in place — this.root. Adding a node via this.addChild(...) is shorthand for this.root.addChild(...):
init(loader) { this.hero = new Sprite(loader.get(Texture, 'hero')); this.coin = new Sprite(loader.get(Texture, 'coin')); this.addChild(this.hero); this.addChild(this.coin);}
For larger scenes, build sub-containers and add nodes to them instead:
init(loader) { this.world = new Container(); this.hud = new Container(); this.world.addChild(new Sprite(loader.get(Texture, 'level'))); this.hud.addChild(new Sprite(loader.get(Texture, 'health-bar'))); this.addChild(this.world); this.addChild(this.hud);}
Containers can be added, removed, and moved between parents at any time. The hierarchy is your scene’s structure.
Transforms cascade
When a parent transforms, its children move with it. If you set the parent’s position to (100, 0) and a child’s position to (20, 0), the child renders at world position (120, 0):
this.player = new Container();this.player.setPosition(100, 0);const body = new Sprite(loader.get(Texture, 'body'));const head = new Sprite(loader.get(Texture, 'head'));head.setPosition(0, -32);this.player.addChild(body);this.player.addChild(head);this.addChild(this.player);
Now moving this.player moves the head and body together. Rotating the player rotates them around the player’s pivot. The same applies to scale.
Each node carries its own transform — helpers such as setPosition, setRotation, setScale, setSkew, and setOrigin describe where the node lives relative to its parent (positions are pixels, rotations and skew angles are degrees). Sprites add setAnchor(...) on top of that, which controls which point of the texture is placed at the sprite’s position. Local transforms are what you set; the global transform is the local one composed with every ancestor up the tree, which is what the renderer uses.
Skew
Skew (shear) slants the local shape along one or both axes without scaling or rotating it. Two properties control it:
skewX — shear along the X axis: positive values lean the top edge to the right.
skewY — shear along the Y axis: positive values lean the left edge downward.
Both are in degrees, consistent with rotation. Use the compound setter when setting both at once:
hero.skewX = 15; // lean right 15°hero.skewY = -5; // tilt top-left slightlyhero.setSkew(15, -5); // same as abovehero.setSkew(10); // sets both skewX and skewY to 10
Skew composes with position, rotation, scale, origin, and anchor. It cascades to children and invalidates bounds exactly like any other transform component. Any node with a non-zero skew is no longer axis-aligned — its bounds become the AABB of the skewed shape, and collision/hit-testing uses the exact parallelogram geometry.
Common uses:
Pseudo-3D effects — slant a floor or wall tile without a shader.
UI slant — angled buttons or callout boxes.
Squash/stretch animation — combine with scale for directional deformation.
Impact lean — have a character sprite lean into movement or absorb a hit.
Render order
Children render in the order they were added. The first child drawn ends up at the back; the last child drawn ends up on top. To put a sprite in front of another, add it later — or change its position in the parent’s child list:
this.world.addChild(this.background);this.world.addChild(this.player); // drawn over backgroundthis.world.addChild(this.foreground); // drawn over player
For dynamic z-order — characters that need to layer correctly based on their y position — assign zIndex to each child. ExoJS resolves sibling order from zIndex during render-plan playback (tie-breaker: child list order), without mutating container.children.
Adding, removing, and rearranging
The container API covers the common cases:
parent.addChild(child); // append (variadic — pass multiple)parent.addChildAt(child, index); // insert at a specific indexparent.removeChild(child); // remove by referenceparent.removeChildAt(index); // remove by indexparent.removeChildren(); // clear all childrenparent.swapChildren(a, b); // swap two children's positionsparent.setChildIndex(child, index); // move a child to a new position
Removing a child doesn’t destroy it — you can keep the reference and re-add it later. Destroy the node separately when it should no longer own resources or participate in the scene at all.
Why this stays composable
Because transforms cascade and children render in order, large scenes are built by nesting smaller scenes. A Player container groups body, weapon, hat, and effects. A World container groups player, enemies, and tiles. A Game scene groups world and HUD.
Each level is independent: rotating the player rotates everything inside it without the scene knowing how the player is structured internally. Replacing the player’s hat doesn’t touch any other code. Adding a “shake the world” effect is one rotation tween on the world container.
Masks
Each render node has a mask property that clips its rendering to a shape. The mask source can be a Rectangle, a Texture, another RenderNode, or null (no mask). Masks are applied during render, not during transform — children of a masked container are still positioned normally, but only the visible region is drawn.
import { Rectangle } from '@codexo/exojs';this.viewport = new Container();this.viewport.mask = new Rectangle(0, 0, 400, 300);this.viewport.addChild(this.world);this.addChild(this.viewport);
This pattern is useful for HUD windows, minimaps, and any area where you want to clip a complex subtree to a simple shape.
A Rectangle mask animates across a sprite to reveal it progressively.
Where to go next
The next chapter, Coordinates and views, covers world-vs-screen coordinates, the camera (View), and how to handle different canvas sizes without breaking layout assumptions.