Node
Transform hierarchy: position, rotation, scale, children, and optional tags/collision layers. Create with Scene.addNode or Node.new().
Constructor
Section titled “Constructor”Node.new() — construct an empty node.
Local transform
Section titled “Local transform”| Method | Description |
|---|---|
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 |
World transform
Section titled “World transform”| Method | Description |
|---|---|
getWorldPosition() | World-space position |
getWorldMatrix() | World matrix |
getForward() / getRight() / getUp() | Direction vectors from rotation |
lookAt(x, y, z) | Orient to look at a point |
Screen position
Section titled “Screen position”getScreenPosition(camera, viewportWidth, viewportHeight) — returns [screenX, screenY, depth] or null if behind camera.
Hierarchy
Section titled “Hierarchy”| Method | Description |
|---|---|
addChild(child) | Add a child node |
getParent() | Parent node |
detach() | Remove from parent |
nodes | Child nodes |
animations | Animation state map (name → AnimationState) |
Properties
Section titled “Properties”| Method / property | Description |
|---|---|
name / getName() / setName(name) | Node name |
node_id | Node ID |
getTags() / setTags(tags) | Tags |
getCollisionLayers() / setCollisionLayers(layers) | Collision layers |
Entity
Section titled “Entity”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 / property | Description |
|---|---|
node | The 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) |
Passing args and config
Section titled “Passing args and config”Two inputs reach your class in init(config, args), from different places:
args— the value you pass asinstantiate’s 3rd argument, per call. Usually aMapof named values; aList,Num, or object also work. It isnullwhen you use the 2‑argument form.config— the node’s Blender‑authoredplume3d_config_pathTOML, parsed to a Map (ornull). 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 classvar e = blend.instantiate("goblin_1", scene, { "hp": 25 })System.print(e.hp) // 25Gotchas:
- A
String3rd argument is notargs— it is the legacy animation‑action prefix, andargsis thennull. To pass a string as data, wrap it:{ "name": "hero" }or["hero"]. - The 2‑argument form
instantiate(name, scene)givesargs = null— guard it (args == null) or read withargs != null && args.containsKey(k). config(authored, per‑object) andargs(runtime, per‑call) are independent; either may benull.- If
instantiatehands back a plainNodewhen you expected your class, the class’s script isn’t imported —importit yourself (the engine can’t import it for you, so it falls back to aNode). The import string must match the node’splume3d_script_pathminus.wrenexactly and case‑sensitively (Scripts/x≠scripts/x; a mismatch resolves on macOS but not on Linux/Windows). The engine logs aWARNnaming the class and the exactimport "…" for …line to add.