Node Editor
apps/node_editor is the capstone of the retained-UI work: a visual editor for animation-controller
graphs. It loads a controller .toml (the same AnimController schema the
engine runs — never a new format), lets you rearrange and edit the graph, and saves it back.
The graph is drawn on the retained-UI Canvas (Ui.canvasRect /
canvasLine / canvasBezier / canvasText); the toolbar, node list, and inspector use the
immediate-mode Gui for text entry.
The graph: states, blend trees, and clips
Section titled “The graph: states, blend trees, and clips”The editor shows a controller as a unified graph of three node kinds, so a blend tree is as visible and editable as the state machine that drives it:
- State — an FSM state. Plays a blend node or a clip, and has guarded transitions to other states. The default state is highlighted.
- Blend — a blend tree (
blend1d/blend2d). Blends its entry clips by a parameter; each entry has a threshold. - Clip — a leaf animation clip.
Edges are typed and colour-coded, each anchored on whichever side of a node faces the other — so you can place nodes any which way and the wires still route sensibly (with an arrowhead at the target):
| Edge | From → to | Colour | Label |
|---|---|---|---|
| Transition | state → state | gold | the transition guard |
| Plays | state → its blend/clip | blue | — |
| Blend entry | blend → clip | teal | @threshold |
What it does
Section titled “What it does”- Loads a controller
.tomlviaConfig.loadand extracts the whole graph — clips, blend nodes (with their entries + thresholds), and states (each state’s node/clip reference, guarded transitions, and the default state). Malformed sections are skipped rather than crashing the editor. - Visualizes it on a layered auto-layout (states on top, blend nodes in the middle, clips at the bottom), with the typed edges above.
- Navigates an effectively infinite canvas: wheel to zoom (toward the cursor), middle-drag to pan, and Fit view to frame the whole graph.
- Selects one or many nodes: click, shift-click to add/remove, or drag a box in empty space to marquee-select; drag any selected node to move the whole selection; Delete removes them all.
- Edits with dropdowns wherever the options are known: a transition’s target, a state’s played
blend-node/clip, a blend entry’s clip and param are all pick-lists; a guard is built from a
parameter dropdown + an operator dropdown + a value typed to the parameter (a number for float/int, a
true/false pick for bool, nothing for a trigger, or
END). Plus + State / + Blend / + Clip / + Any State, Link (a transition), rename, a transitionevent, the node/clip ref toggle, and set-default. - Edits layers — a Layers panel lists the implicit base plus each pose-overlay layer, and the inspector edits its authored fields: name, blend mode (override/additive), and the bone mask, alongside a read-only list of the Actions bound to that layer. Renaming a layer re-points every Action bound to it, so the controller stays loadable. (A layer’s weight is a runtime value, not an authored one — drive it in the 3D preview.)
- Deletes prune every reference to a removed node, so a delete never leaves a dangling controller.
- Saves back with a schema-aware serializer that round-trips the whole controller
(
[controller]/[params]/[clips.*]/[nodes.*]with entry arrays /[states.*]with inline transition arrays /[root_motion]) — verified: load → serialize → reload reproduces the graph, blend thresholds and all. Sections the editor doesn’t draw as nodes —[[layers]],[transitions],[actions],[contexts], and any future one — are preserved verbatim, so opening and saving a layered controller never silently loses them.
3D preview
Section titled “3D preview”3D Preview flips the editor into a live viewport that renders the skinned character playing the controller you’re editing. It pushes the current graph to a live AnimController (the engine hot-reloads it, preserving the current state + params, so edits read as live) and drives it:
- Drive it —
WASDto move (Shift to run) → thespeedblend,Space→ thejumptrigger,K→die; so you can walk transitions, blends, and Any State without launching the game. Or use the HUD’s jump / die buttons. - Camera — drag to orbit, wheel to zoom, and a track toggle: on keeps the character centred as its root travels; off pins the camera so you watch it walk across (the root-motion travel).
- Physics — a toggleable floor and a gravity toggle with a grounded readout, for ground-checking root-motion / jump / fall states.
- Model — a dropdown over the
.blendfiles in the app’sModels/directory (viaResource.list). Pick a different model and the preview reloads it and re-discovers its collections — so a converted marketplace character can be previewed alongside the bundled sprite. - Collection — a dropdown that auto-discovers the model’s collections (e.g. the shipped
Character.blendexposes Fire and Bramble sprites) and previews the one you pick, rebuilding the scene and re-attaching the controller live. Both the model and collection picks are remembered across restarts in a tool-owned, gitignored editor-state file — there’s no config file you have to maintain. - Layer weights — a live slider per layer, driving
setLayerWeighton the running controller. This is where an overlay’s mask and blend mode can actually be seen composited over the base pose without launching the game. - Sync graph re-pushes your latest edits; Reset pose recentres the character.
The preview needs a model whose actions match the controller’s clips; the bundled controllers/preview.toml
pairs with the shipped Character.blend (one action, Idle_ArmSwing) so it works out of the box —
richer walk/run/jump content awaits a multi-action rig.
Running it
Section titled “Running it”plume3d apps/node_editor # edits this app's controllers/demo.tomlplume3d --tool apps/node_editor <target-app> # edits the target's controllers/demo.tomlThe bundled controllers/demo.toml is a locomotion FSM with a 1D blend tree (a blend1d over
speed blending idle → walk → run, plus jump/fall states), so every node kind is on screen at launch.
The --tool mode edits a target project’s controller in place (see UI Editor
for the data/script-root split).
Current scope
Section titled “Current scope”The editor round-trips the full controller — states, blend nodes, clips (including tagged
sections), transitions (with guards + event), a Mecanim-style Any State, params, root motion, and the
sections it doesn’t draw as nodes ([[layers]], [transitions], [actions], [contexts]) — faithfully.
blend1d and blend2d are each edited and saved in their own schema (a 1D threshold at, or a 2D
param_x/param_y point per entry); the 2D blend space is edited as a coordinate list rather than on a
2D canvas. Node-position persistence is a follow-up.
See AnimController for the controller schema this edits, and the Ui Canvas example for the drawing primitives it is built on.