Files
av3-animation-as-crab/README.md
T
doloro 2c3b670ab9 Add animation store for pre-made clips
aac.AnimationStore(folder) seeds a clip from an existing .anim asset and
returns a normal ClipBuilder, so pre-made clips work anywhere generated
clips do and can still be edited (looping, keyframes, toggles, blend
shapes). The Unity side clones the asset into the container and applies
the script's changes to the clone, leaving the original untouched.

- ClipData gains optional source; looping becomes Option<bool> so an
  untouched store clip keeps the asset's own looping setting.
- Add rust/examples/animation_store.rhai, a worked store example.
2026-09-18 14:52:02 +01:00

104 lines
4.2 KiB
Markdown

## Animator As Crab
A hard fork of [Animator As Code](https://github.com/hai-vr/av3-animator-as-code) where animator
controllers are written in [Rhai](https://rhai.rs/) instead of C#.
A Rhai script is evaluated by a Rust library (`libaac`), which validates it and emits the whole
controller as JSON. A Unity Editor window deserializes that JSON and builds a real
`AnimatorController` through the original Animator As Code V1 library, which is left untouched.
```
script.rhai --> libaac (Rust) --JSON--> AacCrabWindow (Unity Editor) --> AnimatorController
```
## Layout
| Path | Contents |
| --- | --- |
| `rust/` | The Rhai DSL, graph model, validator, and the `libaac` cdylib. |
| `rust/examples/avatar.rhai` | The reference script, exercising every feature of the DSL. |
| `csharp/dev.doloro.animator-as-crab/` | The Unity package: the untouched Animator As Code V1 library and the bridge in `V1/Editor/Crab/`. |
| `flake.nix` | Dev shell with the Rust toolchain, `dotnet`, `mono`, and `jq`. |
## Building
```sh
nix develop
cargo test --manifest-path rust/Cargo.toml
cargo run --bin aac-dump -- rust/examples/avatar.rhai # print the generated JSON
nix build .#aac # libaac.so + aac-dump
```
Copy `libaac.so` (or `libaac.dll`, `libaac.dylib`) into the Unity project's `Assets/Plugins` folder,
and add `csharp/dev.doloro.animator-as-crab` to the project's packages.
## Writing a script
```rhai
let aac = AnimatorAsCode();
aac.system_name("MyAvatar");
aac.asset_key("AAC_");
let speed = aac.float_param("Speed", 0.0);
let ctrl = aac.new_controller();
let base = ctrl.layer("Base");
let idle = base.state("Idle", 0, 0);
let walk = base.state("Walk", 1, 0);
let walk_clip = aac.clip("walk_anim");
walk_clip.toggle("Body/Props", true);
walk.set_clip(walk_clip);
idle.transition_to(walk).when(speed > 0.1).no_exit_time().duration(0.25);
```
Conditions are written on the parameters themselves (`speed > 0.1`, `is_sitting == true`).
There is no `&&` or `||` — Rhai's are short-circuit built-ins that cannot be overloaded — so
use `when_all([speed < 0.05, is_sitting == false])`.
See `rust/examples/avatar.rhai` for the full surface: parameters, clips, curves, blend trees,
sub-state machines, any-state transitions, and transition settings.
### Pre-made clips (animation store)
An animation store seeds clips from `AnimationClip` assets that already exist in the project.
`store.clip(name)` resolves to `<folder>/<name>.anim` and returns a motion usable anywhere a
generated clip is: as a state motion, as a blend tree child, or through `add_motion_direct`. A store
clip can be edited like any other clip.
```rhai
let store = aac.AnimationStore("Assets/Doloro/Clips");
let walk = store.clip("Walk"); // Assets/Doloro/Clips/Walk.anim
walk.looping(true);
walk.keyframe("Body/Props", "m_IsActive", 0.0, 0.0);
walk_state.set_clip(walk);
locomotion.add_motion(walk, 0.0);
```
The generator loads the asset with `AssetDatabase.LoadAssetAtPath`, clones it into the asset
container, and applies the script's keyframes and looping on the clone: the pre-made asset is never
modified. A store clip with no `looping(...)` call keeps the source asset's own looping setting.
Requesting the same asset twice reuses the one reference, and a missing asset is a clear error.
## Generating
Open *Tools > Animator As Crab* and fill in the window:
- the path to the `.rhai` script (it is watched, so saving the script regenerates the controller),
- the `AnimatorController` to generate into,
- the animator root, the asset container, and the container mode,
- whether states should write defaults.
Nothing is defaulted silently, and the controller is rebuilt with Animator As Code's modification
API: the controller is cleared, and the clips and blend trees of the same asset key are removed
from the container before being recreated. Animator As Code's own C# API is untouched and still
usable on the same controller.
## Limitations
- The Unity side has not been run: there is no Unity project in this repository, so the bridge and
the generator are verified by reading, not by executing.
- Blend trees can only nest a blend tree that was declared earlier in the script.
- The graph carries no avatar masks, layer weights, state behaviours, or parameter drivers yet.