Skip to content

Node ​

The base of every display object: transform, children, lifecycle hooks, input handlers and events.

ts
import { Node, Event, Align, PauseMode } from "willow";

Node ​

ts
class Node

Scene-graph base. Every visual is a Node. Nodes carry position plus visibility, can own children, and can be removed from their parent or disposed. Lifecycle hooks (onReady, onUpdate, onEnter, onExit) are assignable callable slots.

Node: constructors ​

ts
new Node(name?: string)

Creates an invisible grouping node with no visual representation of its own. name is optional and purely for debugging/creation-site tooling. A plain new Node() is the building block every scene root and container is made from.

Node: properties ​

MemberDescription
filters: Filter[]List of visual filters applied to this node's rendered output.
alpha: floatPer-node opacity multiplier in 0.0..1.0. Composes with parent alpha.
interactable: booleanPointer-input enable switch. Defaults to true. Writing false takes the node out of hit-testing even while a pointer handler is assigned; writing true restores the handler-driven behaviour. It never makes a handler-less node hit-testable: a node still participates only while it has a pointer handler assigned.
name: stringDebug / lookup name. Seeded from the constructor argument (empty when omitted); assigning a non-string raises a TypeError.
userData: anyFree-form value the script attaches to this node. undefined until assigned. The runtime never reads it.
blendMode: stringCompositor blend mode. One of "normal" | "add" | "multiply" | "screen" | "erase" | "mask" | "below" | "none". "add" brightens (additive particle FX); "mask" clips against this node's alpha. An unknown string raises a typed VM error. Defaults to "normal".
globalOrder: intSecondary render-sort key (default 0). Breaks ties within the same renderLayer: among nodes on one layer, a higher globalOrder draws on top, independent of tree/insertion order. renderLayer still dominates it. A plain settable int.
renderLayer: intPrimary render-sort key in [0, 255]. A higher renderLayer draws on top of a lower one regardless of scene-tree position, the mechanism for UI-over-world or additive-FX-over-everything. Reserved conventional values live on engine.scene (LAYER_BACKGROUND, LAYER_DEFAULT, LAYER_UI, LAYER_OVERLAY). A value outside [0, 255] raises a typed VM error. Defaults to 0. A node with no layer of its own draws on its nearest ancestor's layer, so a subtree (including children added later) follows the layer set on its root. Reading returns that effective layer; writing makes the layer explicit for the node and its unset descendants; clearRenderLayer() goes back to inheriting.
paused: booleanStops this subtree's onTick, onUpdate, onLateUpdate, ticker and tweens while true. A detached node is not walked at all, so reattaching resumes it. Default false.
pauseMode: PauseModeHow this node treats a pause above it: a PauseMode constant. INHERIT (default) follows its parent, ALWAYS keeps running (a pause menu inside the paused tree), WHEN_PAUSED runs only while an ancestor is paused, DISABLED never runs.
timeScale: floatScales this subtree's time (default 1): onTick runs floor(accumulator) times per app tick, dt for onUpdate and onLateUpdate is multiplied, and the node ticker follows the tick count. Must be >= 0.
unscaled: booleanWhen true this subtree ignores App.timeScale and its ancestors' timeScale. Default false.
interpolate: booleanWhen true the renderer draws x, y, rotation, scaleX and scaleY between their values at the last two ticks, using App.tickAlpha: move the node in onTick and it still looks smooth at a frame rate above App.tps. Only the drawing is blended; x and the other fields always read the logical value. Call resetInterpolation() after a teleport, or the node slides across the screen for one tick. Default false.
readonly ticker: TickerThe node's own Ticker, made on first read. It ticks with the node, pauses and scales with it, and disposing the node rejects its pending waits with Cancelled.
readonly parent: NodeThis node's parent in the scene graph. undefined while detached.
pivotX: floatPivot x in local pixel space. The rotation/scale anchor. (0, 0) leaves rotation around the top-left. Centring (e.g. setAnchor(0.5, 0.5) or setPivot(w/2, h/2)) makes a node spin in place.
pivotY: floatPivot y in local pixel space. See pivotX.
rotation: floatLocal rotation in radians.
scaleX: floatLocal x scale factor. 1.0 is the authored size.
scaleY: floatLocal y scale factor. 1.0 is the authored size.
skewX: floatLocal x skew in radians.
skewY: floatLocal y skew in radians.
visible: booleanWhether this node (and its subtree) renders.
x: floatLocal x position in parent space.
y: floatLocal y position in parent space.
zOrder: intRender order within the parent. Higher draws on top.

Node: callbacks ​

MemberDescription
onPointerMove: FunctionFires for pointer movement over this node.
onPointerEnter: FunctionFires directly when the pointer enters this node. Enter and leave do not bubble. A transition fires leave on the old node before enter on the new node.
onPointerLeave: FunctionFires directly when the pointer leaves this node.
onDragStart: FunctionFires once movement crosses the configured drag dead zone.
onDrag: FunctionFires for movement after a drag has started.
onDragEnd: FunctionFires when an active drag ends.
onPinch: FunctionFires during a two-finger pinch/rotate gesture over this node. Registering this ALSO makes the node participate in hit-testing, same as onPointerDown et al. Called as onPinch(e: PinchContext): void.
onWheel: FunctionFires when the mouse wheel or trackpad scrolls while the pointer is over this node (or over a descendant with no onWheel of its own). Registering this makes the node participate in hit-testing. Called as onWheel(e: WheelEvent): void.
onEnter: FunctionFires when the node is added to a parent. Assign null to detach. Called as onEnter(): void.
onExit: FunctionFires when the node is removed from its parent. Assign null to detach. Called as onExit(): void.
onReady: FunctionFires once on the first frame this node is in the tree. Assign null to detach. Called as onReady(): void.
onUpdate: FunctionFires every frame after onReady. Assign null to detach. Called as onUpdate(dt: float): void where dt is the frame-time in seconds.
onTick: FunctionFires once per logic tick (App.tps per second), in the tick phase before the frame, parent before children. Visibility does not stop it; paused and a detached node do. Assign null to detach. A hook that throws is reported once and disabled. Called as onTick(): void.
onLateUpdate: FunctionFires every frame after the whole tree's onUpdate and the tweens (cameras and followers). Assign null to detach. Called as onLateUpdate(dt: float): void.
onPointerDown: FunctionFires when a pointer is pressed over this node. Assign null to detach. A node participates in hit-testing only while it has a pointer handler assigned. Called as onPointerDown(e: PointerEvent): void.
onPointerUp: FunctionFires when a pointer is released over this node. Assign null to detach. Called as onPointerUp(e: PointerEvent): void.
onClick: FunctionFires when a press and release both land on this node. Assign null to detach. Called as onClick(e: PointerEvent): void.

Node: methods ​

MemberDescription
setMask(mask: Node | null): voidClips this node's rendered subtree to mask. The mask's own transform is ignored by the renderer. Child transforms still apply. Masks do not affect hit testing. Passing null clears it.
clearMask(): voidClears the subtree mask, if any.
getMask(): Node | nullReturns the current subtree mask, or null when none is set.
addFilter(filter: Filter): voidAppends a filter to this node's filter chain.
removeFilter(filter: Filter): voidRemoves a filter from this node's filter chain.
clearFilters(): voidRemoves all filters from this node.
getFilters(): Filter[]Returns this node's active filter chain.
setFilters(filters: Filter[]): voidReplaces this node's filter chain with the given array of filters.
setCustomPaint(paint: ((painter: Painter) => void) | null): voidInstalls a function that draws this node itself each frame, instead of its normal content. The function receives a Painter; call painter.paintDefault() to keep the normal content. Passing null removes it.
enablePhysics(config: PhysicsConfig): voidTurns this node into a physics root with its own isolated world. Nodes below it get bodies with setBody; the scene steps the world each logic tick. Raises if an ancestor or a descendant is already a physics root.
disablePhysics(): voidTears this physics root down: every body below it is removed.
isPhysicsRoot(): booleanTrue when this node is a physics root.
setBody(definition: PhysicsBodyDef): voidAttaches a body at this node's current position and rotation, replacing any existing body. Raises when no ancestor is a physics root or the definition is invalid.
removeBody(): voidDetaches this node's body; does nothing without one.
getBody(): PhysicsBody | nullReturns this node's body, or null when it has none.
setBodyEnabled(enabled: boolean): voidTakes the body out of, or back into, the simulation, keeping its state.
bodyEnabled(): booleanTrue when this node has a body that takes part in the simulation.
stepPhysics(dt: float): voidAdvances the physics root enclosing this node by dt seconds and skips that root's next automatic step. Raises when no ancestor is a physics root.
setCacheAsTree(enabled: boolean, mode?: int): voidEnables or disables this node's subtree as a cached texture. In CacheTreeMode.MANUAL mode (default), call invalidateCache() when the subtree changes; in CacheTreeMode.AUTO mode, changes are detected automatically (slight overhead per change). Renders the whole subtree to an offscreen texture on the next frame and draws the texture thereafter until invalidated. Useful for reducing overdraw in complex, rarely-changing UI or tilesets. Requires a real renderer; has no effect in headless builds.
invalidateCache(): voidInvalidates the cache for this node's subtree, forcing a re-render on the next frame. Has no effect if cache-as-tree is not enabled. Called automatically in CacheTreeMode.AUTO on any setter; required manually in CacheTreeMode.MANUAL mode.
isCacheEnabled(): booleanReturns true if this node's subtree is cached as a texture.
resetInterpolation(): voidForgets the previous tick: the next draw shows the node exactly where it is now, with no slide from where it was. Call it after setting x/y to teleport a node that has interpolate on.
dispose(): voidReleases this node's native resources now rather than whenever the collector runs. Calling dispose() twice is a silent no-op. Every other method or field on a disposed node, on every subclass, raises a catchable InvalidStateError that names it, for example "InvalidStateError: Node.x: node has been disposed". A disposed node also leaves its parent: children() no longer lists it and parent.contains(deadChild) answers false. engine.root2d cannot be disposed.
add(child: Node): voidAdds child to this node's child list and takes ownership. The scene graph holds a strong reference; removeChild drops it.
addAt(child: Node, index: int): voidAdds child at child index index, so it draws before the children already at or after that index. index runs 0..children().length; re-adding an existing child of this node moves it, and its valid range is then 0..children().length, 1. An out-of-range index raises a RangeError.
children(): Array<Node>Returns a snapshot array of this node's direct children.
contains(child: Node): booleanTrue if child is a direct child of this node.
directionTo(other: Node): Vec2Returns the unit vector pointing from this node toward other in world space. Returns the zero vector if both nodes share a position.
distanceTo(other: Node): floatEuclidean distance between this node's world position and other's, in pixels.
localToWorld(p: any): Vec2Maps a point in this node's local space to world space. Returns a freshly-allocated Vec2. p accepts any { x, y }-shaped value (a Vec2 or a plain object literal), only the numeric x/y fields are read.
toScreen(x: float, y: float): Vec2Maps a point in this node's local space to screen units (engine.screen2d's space, the framebuffer in the units the camera viewport is given in). A node under the world root goes through the camera; a node under engine.screen2d does not. Place a screen2d node over a world point with it each frame (a ring over a unit), or anchor an overlay to a world node. Returns a freshly-allocated Vec2.
fromScreen(x: float, y: float): Vec2The inverse of toScreen: maps a point in screen units to this node's local space, through the camera when the node is under the world root. Returns a freshly-allocated Vec2.
remove(): voidRemoves this node from its current parent. No-op if already detached.
removeAllChildren(): voidRemoves every direct child from this node in one call.
removeChild(child: Node): voidDrops the strong reference to child from this node. Inverse of add. No-op if child is not a direct child of this node.
setAnchor(nx: float, ny: float): voidPivot setter taking normalised 0..1 coordinates relative to the node's source size. (0, 0) is top-left (default), (0.5, 0.5) is the centre. Centring the pivot is what makes a rotating node spin in place instead of swinging around its top-left corner.
alignPivot(horizontal?: Align, vertical?: Align): voidMoves the pivot to an edge or the centre of the node's current content, Starling's alignPivot: alignPivot() centres it, alignPivot(Align.CENTER, Align.BOTTOM) puts it at the bottom middle (feet of a character). Set once from the current size; a later resize does not move it. horizontal takes Align.LEFT, CENTER or RIGHT; vertical takes Align.TOP, CENTER or BOTTOM; any other value raises RangeError.
clearRenderLayer(): voidDrops this node's own renderLayer so it inherits its nearest ancestor's again.
setHitRect(x: float, y: float, width: float, height: float): voidOverrides this node's hit-test area with an axis-aligned rectangle in LOCAL coordinates, replacing whatever custom shape (if any) was set before. Without a custom shape, hit-testing uses the node's default bounding-box rect.
setHitCircle(centerX: float, centerY: float, radius: float): voidOverrides this node's hit-test area with a circle in LOCAL coordinates. See setHitRect for the default/override relationship.
setHitPolygon(points: Float32Array | Array<float>): voidOverrides this node's hit-test area with a convex polygon in LOCAL coordinates, given as a flat [x0, y0, x1, y1...] array (at least 3 points). See setHitRect for the default/override relationship.
clearHitShape(): voidClears a custom hit-test shape set by setHitRect / setHitCircle / setHitPolygon, reverting to the default bounding-box rect.
hasHitShape(): booleanTrue while a custom hit-test shape set by setHitRect / setHitCircle / setHitPolygon is attached.
hitShapeContains(x: float, y: float): booleanWhether the LOCAL-space point (x, y) is inside this node's custom hit-test shape. false when no custom shape is attached.
setPivot(px: float, py: float): voidPivot setter in raw pixel coordinates. See setAnchor for the normalised form.
setPosition(x: float, y: float): voidSets local position. Equivalent to writing x and y individually but in one call.
setRotation(radians: float): voidSets local rotation in radians. Equivalent to writing rotation.
setScale(sx: float, sy?: float): voidSets scale. One-arg form sets both axes to the same value. Two-arg form sets them independently.
setSize(w: float, h: float): voidResize the node. For primitives (Quad, etc.) this scales relative to the authored base size. For sprites it derives a scaling factor from the texture dimensions. The cached scaleX / scaleY fields update to reflect the result.
setSkew(sx: float, sy: float): voidSets local skew on both axes in radians.
worldBounds(): RectReturns this node's world-space bounds as a freshly-allocated Rect. The world transform is refreshed before reading, so a node moved earlier in the same frame returns its post-move bounds.
worldPosition(): Vec2Returns this node's world-space position as a freshly-allocated Vec2. The world transform is refreshed before reading.
worldToLocal(p: any): Vec2Maps a point in world space to this node's local space. Returns a freshly-allocated Vec2. p accepts any { x, y }-shaped value (a Vec2 or a plain object literal), only the numeric x/y fields are read.
on(name: string, handler: Function): FunctionRegisters handler for a custom event named name, the Starling/Pixi.js-style escape hatch, layered ON TOP of the typed setters above (onClick, etc.) rather than replacing them: a typed setter and a generic on("click"...) listener both fire for the same real click. Events emitted on a descendant bubble up through every ancestor's listeners for the same name (in registration order at each level), until either the tree root is reached or a listener calls e.stopPropagation(). Returns handler for chaining, same convention as engine.onFrame.
off(name: string, handler: Function): booleanRemoves a specific handler previously registered for name via on, matched by reference. Returns true if a listener was removed.
offAll(name?: string): voidRemoves every listener registered via on for name. With no argument, removes every listener for every name on this node.
emit(name: string, data?: any): voidDispatches name on this node, then bubbles to every ancestor, see on's bubbling note. data (optional) is forwarded as e.data on the Event every listener receives.
hasListener(name: string): booleanTrue if this node has at least one listener registered for name via on.

Event ​

ts
class Event

Payload handed to a Node.on/emit listener. type is the event name; target is the node emit was originally called on (constant across the whole bubble); currentTarget is whichever node's listener is currently running (changes at each bubble level); data is whatever emit's second argument was, or undefined. stopPropagation() halts the bubble walk before any further ancestor is reached.

Event: properties ​

MemberDescription
type: stringThe event name passed to emit.
target: NodeThe node emit was originally called on. Constant across the whole bubble.
currentTarget: NodeWhichever node's listener is currently running. Changes at each bubble level.
data: anyWhatever emit's second argument was, or undefined.

Event: methods ​

MemberDescription
stopPropagation(): voidHalts the bubble walk before any further ancestor's listeners run.

Align ​

ts
enum Align

Edge or centre for node.alignPivot.

Align: values ​

MemberDescription
LEFTThe left edge (horizontal).
CENTERThe centre (either axis).
RIGHTThe right edge (horizontal).
TOPThe top edge (vertical).
BOTTOMThe bottom edge (vertical).

PauseMode ​

ts
enum PauseMode

How a node treats a pause above it (node.pauseMode); Godot's process modes.

PauseMode: values ​

MemberDescription
INHERITFollows its parent (default).
ALWAYSKeeps running while ancestors are paused: a pause menu or debug overlay inside the game tree.
WHEN_PAUSEDRuns only while an ancestor is paused.
DISABLEDNever runs.