Node2D
The base 2D scene node — an empty transform. Sprite and Tilemap
extend it, so everything here (transform, hierarchy, events, aspects) works on them too. A bare
Node2D is useful as a grouping parent, a world-space marker/spawn point, or a physics/trigger
object (attach a Shape2D plus Physics2D or Trigger2D).
At a glance
const door = new Node2D()
.aspect(Shape2D, { box: [16, 32] })
.aspect(Trigger2D)
door.position = [320, 64]
door.addEventListener('enter', other => console.log('entered by', other.id))
scene.add(door)
const cart = new Node2D()
cart.add(wheelA, wheelB) // children move with the cart
cart.x += 40Transform
Local to the parent, Y-up; 1 unit = 1 logical px at camera zoom 1. See conventions.
node.x, node.y // single-axis setters
node.position = [120, 64] // Vec2Like; getter returns a fresh Vec2
node.rotation = 45 // degrees, CCW
node.scale = 2 // number (uniform) or Vec2Like — draw scale only
node.layer = 1 // draw layer (int); higher layers render on top
node.z = 5 // intra-layer depth on non-Y-sorted layers (higher = on top)
node.visible = falseposition and scale getters return fresh copies — node.position.x = 3 is a
silent no-op. Use node.x = 3, or mutate a local and assign back. See
math value semantics.
scale affects drawing only — physics shapes never read it. A scaled-up sprite keeps
the collider you declared. See 2D physics.
Hierarchy
node.parent // Node2D | null (settable — sugar for setParent)
node.children // readonly Node2D[]
node.add(...children: Node2D[]): this // attach (each keeps its world transform)
node.remove(child: Node2D): this // detach a direct child → root, keeping world transform
node.setParent(parent: Node2D | null, keepWorld = true): thissetParent with keepWorld: true (the default) recomputes the local transform so nothing moves
on screen; pass false to keep the local values and snap to the new parent's frame. Self and
descendant parents are rejected (no cycles).
node.worldPosition // Vec2 — composes all ancestors
node.worldToLocal(p: Vec2Like): Vec2 // world point → this node's local space
node.localToWorld(p: Vec2Like): Vec2 // and backPhysics-driven transforms
Once a node has a dynamic or kinematic Physics2D body, the native physics
step owns its transform. Reading node.position / worldPosition is always correct — the node
re-syncs from native automatically. Do not write node.position per frame; drive the body
with velocity / applyImpulse (kinematic: moveTo).
Events
node.addEventListener(channel, callback)
node.removeEventListener(channel, callback)| Channel | Fires when | Callback argument |
|---|---|---|
click |
pointer-up over this node's physics shape | ClickEvent |
touchstart |
pointer-down on its shape — ev.track() for drags |
TouchStartEvent |
enter / exit |
a physics contact / sensor overlap began / ended | the other Node2D |
loopReached |
a looping SpriteAnimation clip wrapped around |
clip name (string) |
completed |
a non-looping clip finished | clip name (string) |
click / touchstart require the node to be hittable: a Shape2D plus a Physics2D or
Trigger2D. See pointer events & gestures and
2D physics picking.
Destroying
node.destroy(): voidFrees the native entity and detaches from the parent. Children are reparented to the root, keeping their world transforms — they are not destroyed with the parent. Using a destroyed node afterwards is undefined behavior.
Pitfalls
// ✗ mutating a transform copy
node.position.x = 3
// ✓ single-axis setter, or assign a whole value
node.x = 3
// ✗ fighting the physics step on a dynamic body
setLoop(() => { node.y += 2 })
// ✓ drive the body instead
node.physics.velocity = [0, 120]See also
- Scene2D — adding nodes, layers, Y-sort
- Sprite — the textured node
- 2D physics —
Shape2D, bodies, triggers,enter/exit - Pointer events & gestures —
click,touchstart, drags - Conventions — units, value semantics, lifecycle