## 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 `/.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.