Blacksite/docs/editor/animation-authoring.md

6.8 KiB

Animation Authoring

Blacksite's first production animation path supports skinned glTF 2.0 models (.gltf and .glb). It normalizes imported rig and clip metadata into generated manifests, keeps authored scene data independent of Bevy's runtime graph/player components, and uses the same controller contract in editor preview, PIE, and standalone builds.

Import And Placement

Place animated sources under assets/ and refresh or reimport them from the Asset Browser. The registry writes assets/animations/generated/<asset-id>.animation.ron beside the existing static mesh artifact. Expanding a model row shows:

  • skeleton entries with joint count and signature summary;
  • animation clips with duration and event count;
  • mesh, material, and texture entries from the static import contract.

Model Import Settings also exposes Default animation. Choosing a clip stores its stable sub-asset ID and makes normal renderable-model placement create a one-state controller for that exact clip. The editor samples the clip at its authored range start and pauses it as the edit-mode rest presentation. Imported rest pose creates no controller and preserves the source node pose; Blacksite never guesses the first clip. Generic Scene Instance placement remains animation-free.

Skeleton rows are inspect-only. Dragging an animation clip into the viewport creates one SkinnedMesh actor with a stable SkinnedMeshRenderer and AnimationControllerDesc and selects it for immediate inspection. When a skinned-mesh actor is selected, the same drag assigns the clip as a new controller state instead. Assignment is one undoable command and requires an exact skeleton-signature match.

Skinned primitive rows place their owning model through SkinnedMeshRenderer because their vertex data requires the source skin hierarchy. Their thumbnails render the complete source scene; no drag, details action, or context-menu action can create an invalid detached static-mesh slot. Unrigged primitives continue to place independently through StaticMeshRenderer.

Controller Inspector

The Animation Controller component exposes the selected skeleton, default crossfade, named states, default state, clip, loop, speed, and optional playback range. State IDs are gameplay keys and must be non-empty and unique; labels are presentation only. Component add, remove, reset, copy, paste, and property changes use typed editor history and participate in scene dirty tracking.

The Preview section selects a state, plays or pauses it, stops playback, and scrubs within the authored range. Preview requests use the game runtime adapter and do not change the controller, history, or scene dirty state. Entering PIE clears any paused edit-mode scrub so the authored default state can run; leaving PIE clears transient playback while retaining authored data. The runtime captures imported descendant transforms and morph weights before evaluating animation. Stop, controller removal, invalid graph rebuild, and PIE exit restore that baseline before the configured edit-rest state is sampled again, so a preview cannot strand the actor in its last pose.

Runtime API

Gameplay changes state by sending a validated request through game::animation:

game::animation::request_animation_state(
    world,
    game::animation::AnimationStateRequest::new(actor, "run").with_crossfade(0.15),
)?;

An omitted crossfade uses the controller default. Invalid actor or state requests return an error and preserve current playback. If edited controller data cannot rebuild its runtime graph, the old graph is stopped and cleared instead of continuing stale playback. AnimationGraph, AnimationPlayer, transitions, preview state, and hydrated skinned-mesh roots are runtime-only and never serialize into authored scenes.

Stable Identity And Moves

SkinnedMeshRenderer, skeleton references, and clip references carry the model registry UUID. Generated artifacts are keyed by that UUID; source paths are cached resolution and repair hints. When one model is moved or renamed on disk, catalog refresh reconciles it by the registry's shared content-addressed source fingerprint, preserves the UUID, and regenerates both manifests with the new source path. The same registry policy covers textures and audio clips. An identical copy is not treated as a move while the original still exists. Ambiguous content matches intentionally receive a new identity rather than silently binding the wrong asset.

Scene schema v3 migrates legacy animated actors that paired ModelRef with AnimationControllerDesc to the dedicated skinned renderer. Generic ImportedModel + ModelRef scene instances remain readable and intentionally do not become animation actors.

Skeleton compatibility hashes the exact Bevy 0.19 glTF node-name segments and bind poses, including whitespace and slash characters. The readable joint paths shown by the editor are deliberately not used as identity. Animation-manifest schema v1 artifacts must be reimported to generate schema v2 signatures; schema v3 adds the explicit default-clip ID. Schema v2 deserializes as Imported rest pose and never receives an inferred default.

Source identity uses exact byte length plus BLAKE3 and never checkout mtime. Existing animation schema-v3 files may retain a legacy modified_unix_secs field in their exact bytes; the current reader ignores it, and an equivalent refresh preserves the whole file unchanged. Static-mesh schema v4 uses the same content fingerprint. See ADR 0043.

Validation And Format Boundary

Interactive diagnostics and cargo validate-levels resolve registry UUIDs, generated manifests, stable subasset IDs, source dependencies, default/state IDs, playback ranges, speeds, crossfades, runtime format support, explicit default-clip resolution, exact source-content fingerprints, and exact rig compatibility. Findings identify the owning actor and controller property and include a repair action. Packaging includes the same source files and generated artifacts checked by validation.

FBX remains supported for static model import only. An FBX containing skin deformers or animation stacks emits a blocking diagnostic directing the author to convert it to glTF. Animation v1 does not support one glTF asset containing animation channels under multiple top-level roots, because Bevy creates a separate AnimationPlayer for each root. Split those rigs into separate assets or export them beneath one common root. Animation v1 also does not provide implicit retargeting, blend trees, a visual state graph, root motion, IK, or motion matching.

See ADR 0031 for the controller/runtime boundary, ADR 0033 for renderer ownership, and the working implementation plan for acceptance gates.