Skip to content

Node

Transform hierarchy: position, rotation, scale, children, and optional tags/collision layers. Create with Scene.addNode or Node.new().

Node.new() — construct an empty node.

MethodDescription
getPosition() / setPosition(x, y, z)Local position
getRotation() / setRotation(pitch, yaw, roll)Local rotation (Euler radians)
setQuat(x, y, z, w)Local rotation as a quaternion — exact and gimbal-free at every orientation (precise at ±90° pitch, where Euler degrades). The camera system uses this so Camera.worldToScreen stays exact. Any Euler setter (setRotation/pitch/…) reverts the node to Euler.
pitch / pitch=(value)Pitch (radians)
yaw / yaw=(value)Yaw (radians)
roll / roll=(value)Roll (radians)
getScale() / setScale(x, y, z)Local scale
MethodDescription
getWorldPosition()World-space position
getWorldMatrix()World matrix
getForward() / getRight() / getUp()Direction vectors from rotation
lookAt(x, y, z)Orient to look at a point

getScreenPosition(camera, viewportWidth, viewportHeight) — returns [screenX, screenY, depth] or null if behind camera.

MethodDescription
addChild(child)Add a child node
getParent()Parent node
detach()Remove from parent
nodesChild nodes
animationsAnimation state map (name → AnimationState)
Method / propertyDescription
name / getName() / setName(name)Node name
node_idNode ID
getTags() / setTags(tags)Tags
getCollisionLayers() / setCollisionLayers(layers)Collision layers

Entity is a base class for a Blender-authored node class. A Wren class cannot inherit from the foreign Node, so an Entity wraps a Node and forwards its common API — extend it and your instance reads like the node itself:

import "engine" for Entity
class Player is Entity {
construct new(node) { super(node) } // required: bind the node (call super)
init(config, args) { _hp = 100 } // optional hook; see below
heal(n) { _hp = _hp + n }
}

BlendResult.instantiate(className, scene) returns your Entity subclass instance, bound to the instantiated node, when the node carries a plume3d_class_name whose script you have imported and whose class is Entity. A node with no class — or a class that does not extend Entity — returns the plain Node instead (so existing content is unaffected). Because Entity forwards the Node API, player.getWorldPosition() / player.setPosition(…) work directly; use .node for anything not forwarded.

Method / propertyDescription
nodeThe wrapped Node — escape hatch for anything not forwarded below
init(config, args)Lifecycle hook run after construction. config = the node’s plume3d_config_path TOML (or null); args = instantiate’s 3rd argument (or null). Override it; the default is a no-op.
(forwarded Node API)Transform (getPosition/setPosition, rotation & scale, getWorldPosition, getWorldMatrix, direction vectors, lookAt), identity (name/node_id/tags/collision layers), hierarchy (addChild/getParent/detach, nodes/animations), and sockets (attachController, attachToBone/detachFromBone, hitboxes/hurtboxes)

Two inputs reach your class in init(config, args), from different places:

  • args — the value you pass as instantiate’s 3rd argument, per call. Usually a Map of named values; a List, Num, or object also work. It is null when you use the 2‑argument form.
  • config — the node’s Blender‑authored plume3d_config_path TOML, parsed to a Map (or null). Set once per object in Blender — good for authored defaults.
class Enemy is Entity {
construct new(node) { super(node) }
init(config, args) {
// an authored default from the .blend's config TOML, overridden by a per-call arg:
var baseHp = (config == null) ? 10 : config["hp"]
_hp = (args != null && args.containsKey("hp")) ? args["hp"] : baseHp
}
hp { _hp }
}
import "scripts/enemy" for Enemy // import so the engine can resolve the class
var e = blend.instantiate("goblin_1", scene, { "hp": 25 })
System.print(e.hp) // 25

Gotchas:

  • A String 3rd argument is not args — it is the legacy animation‑action prefix, and args is then null. To pass a string as data, wrap it: { "name": "hero" } or ["hero"].
  • The 2‑argument form instantiate(name, scene) gives args = null — guard it (args == null) or read with args != null && args.containsKey(k).
  • config (authored, per‑object) and args (runtime, per‑call) are independent; either may be null.
  • If instantiate hands back a plain Node when you expected your class, the class’s script isn’t imported — import it yourself (the engine can’t import it for you, so it falls back to a Node). The import string must match the node’s plume3d_script_path minus .wren exactly and case‑sensitively (Scripts/x ≠ scripts/x; a mismatch resolves on macOS but not on Linux/Windows). The engine logs a WARN naming the class and the exact import "…" for … line to add.