MasterNin ECSAnimator
On this page

ECSAnimator API reference

Read unsupported combinations and prohibited operations before integration. A successful Build does not preserve arbitrary Prefab behaviours or automatically compose multiple Animators.

Namespaces: ECSAnimator.Runtime.Product.ECS for runtime/client/commands, ECSAnimator.Runtime.Product.Data for EntityDesc/Settings, and Asterism.Editor.OfflineBuild.Convert for the Editor conversion window/plan. Application code references ECSAnimator.Runtime.Product plus the Unity assemblies it uses: Unity.Entities/Unity.Mathematics for the spawn example, Unity.Transforms for movement, Unity.Burst for Burst jobs and Unity.Collections for NativeArray. Conversion extensions also reference ECSAnimator.Offline.Build and ECSAnimator.Package.Contract. All assemblies set autoReferenced: false, so put consuming code in an asmdef and add the reference explicitly.

First-time users should start with Getting started and the shipped SimpleCharacterExample.cs, which includes setup, movement and cleanup instead of isolated snippets. For step-by-step instructions, see Animation integration and Conversion and component support. Runtime-loaded EntityDesc assets use the same APIs; see resource-manager loading and unloading.

API Purpose
CharacterRuntime.TryGet(World, ...) Gets the single ECSAnimator facade for one live World.
TryPrepare(CharacterRuntimeAsset, ...) Hydrates and retains one reusable character asset.
CharacterPreparedAsset.RuntimeResource Reads compiled public metadata while the prepared bindings remain live; null when stale/released.
TryCreateScope(expectedCount, ...) Creates a caller-owned group of spawned characters.
TrySpawn / TrySpawnBatch Creates character roots from CharacterSpawnRequest; presentation owns their EG render children.
TryResolve(CharacterInstanceHandle, ...) Resolves a stable handle when an Entity is actually required.
TryCreateAnimationClient(...) Resolves declared exact Controller binding keys once and creates one command lane.
CharacterAnimationClient.TryBind(...) Binds one live target to a main-thread CharacterAnimator.
CharacterAnimationClient.Dispose() Stops/disposes the client and releases its prepared-asset hold; do this before Release.
Destroy / DestroyScope Requests instance destruction without exposing renderer ownership.
Release(CharacterPreparedAsset) Returns whether entityDesc/resource retirement was requested.
prepared.ReleaseState / ReleaseDetail Read Active, Pending, Released, FaultedRetainedRequiresWorldRebuild or Invalid directly on the prepared object.

CharacterRuntime, CharacterPreparedAsset, CharacterSpawnScope and handles are World-bound. Do not reuse them after destroying or recreating that World. Repeated TryGet returns the same facade. Removing/replacing internal ECSAnimator systems in a live World is unsupported; rebuild the entire World instead. Detached release tickets and their bounded history are removed; retain the prepared object when release status is needed.

Cleanup order: stop/unregister producers, Dispose animation clients, Destroy/DestroyScope, then Release prepared assets. Continue normal World updates while a release is Pending; never busy-wait. If the World was already disposed, discard its facade/handles and do not call its EntityManager.

if (!CharacterRuntime.TryGet(world, out var runtime, out var error) ||
    !runtime.TryPrepare(entityDesc, out var prepared, out error) ||
    !runtime.TryCreateScope(100, out var scope, out error))
    throw new InvalidOperationException(error);

var requests = new CharacterSpawnRequest[100];
for (var i = 0; i < requests.Length; i++)
    requests[i] = new CharacterSpawnRequest(new float3(i, 0, 0), quaternion.identity, 1f);

if (!runtime.TrySpawnBatch(prepared, requests, scope, null, out var spawned, out error))
    throw new InvalidOperationException(error);

Clothing color at spawn

CharacterSpawnRequest(position, rotation, scale, clothingColor) accepts linear RGB in [0,1]; the fourth channel is tint strength, where 0 preserves the material and 1 replaces the masked cloth hue while retaining its shading. The three-argument constructor preserves existing appearance. For a Unity Inspector Color, convert with .linear before passing its RGB values.

var request = new CharacterSpawnRequest(position, rotation, 1f,
    new float4(0.02f, 0.20f, 0.95f, 1f));
runtime.TrySpawn(prepared, request, scope, null, out var character, out var error);

The source ECSAnimator/Lit material must enable Clothing Tint and assign a linear _ClothingMask texture (red channel: white = cloth, black = protected). It shares _BaseMap UV scale/offset. _ECSAnimClothingColor also provides the material preview/default color. Rebuild the offline asset after changing these inputs. The renderer sets the color independently on each character's render children; it does not clone materials per faction or update color every frame. Characters without a spawn override have no additional color component. This input sets the initial appearance; runtime color mutation is not exposed by this API.

Animation commands

Create one CharacterAnimationClient per producer/asset, not per character. Declare exact Controller parameter names, layer names or full exported state paths with TryCreateAnimationClient. Main-thread code binds a target and uses those declared keys.

TryBind returns a frame-scoped writer, not a persistent Unity Animator. Bind again each frame, once per client in that frame; do not cache the writer across updates or use the single-target entry for a crowd. Use TryReserveTargets for batch/Jobs. Missing, duplicate or ambiguous keys are rejected at client registration. No Template or gameplay-alias asset is involved.

if (!client.TryBind(character, out var animator, out error) ||
    !animator.TrySetFloat("Speed", speed, out error))
    throw new InvalidOperationException(error);

CharacterAnimator supports TrySetFloat, TrySetInt, TrySetBool, TrySetTrigger, TryResetTrigger, TrySetLayerWeight, TryPlay, TryPlayFixedTime, TryCrossFade, and TryCrossFadeFixedTime. For jobs, cache ResolvedAnimationHandle values with TryGet, reserve once with TryReserveTargets, write through CharacterAnimationParallelWriter.Begin, and call RegisterProducer(JobHandle).

CharacterAnimatorWriter supports:

Use NextRequest() for direct writer one-shot identity, then call TryGetReceipts once per consumption batch and use Matches before acknowledging gameplay intent. Its buffer is read-only and must not be retained across updates. ReceiptCount and TryGetReceipt remain available for individual queries.

Each character has one business writer per frame. Resolve AI/player priorities before writing. Commands from that writer are applied in reservation/append order; repeated continuous values use last-write order. Two writers targeting the same character reject that character's entire command set (Conflict for one-shots). Cross-character producers remain parallel; no global sorting or multi-writer coalescing is performed. Writer identities are process-local counters, not stable-key hashes; recreating a client cannot reuse its old requests. At input-generation exhaustion, all new commands return Fault and the character must be recreated.

Read-only animation state is exposed by CharacterAnimationObservation: TryGetLayer, IsInState, IsInTag, IsActionPlaying, CountEvent, TryGetEventOccurrence, TryGetEventOccurrences, WasTriggerConsumed, and TryGetConsumedTrigger. Animation observations are presentation feedback, not gameplay authority.

For a batch of events, call TryGetEventOccurrences once, then use MoveNext(out occurrence, out metadata). The enumerator validates the binding and generation, preserves occurrence order and duplicate keys, and borrows the current read-only occurrence buffer. Consume it before the next update or structural change.

CharacterDynamicPackingSystem.TryCopyCurrentWords replaces TryCopyPublishedWords. The requested generation must still occupy the CPU work buffer; every new Pack attempt invalidates the prior capture, including failed attempts. Pending data never stands in for an older published generation. LocalResolve palettes remain evaluator-owned. GPU diagnostics capture their own expected data on request and retain the exact GPU lease until readback completes; the runtime keeps no published CPU mirror.

State and transition lifecycle feedback is exposed through CharacterLifecycleOccurrence: StateEnter, StateExit, TransitionStart, and TransitionEnd are emitted without per-instance configuration. Add CharacterLifecycleSubscription only when per-update StateUpdate records are required. Consume the buffer in CharacterEventSystemGroup; this is the ECS bridge for presentation side effects and does not execute arbitrary StateMachineBehaviour managed code.

AI, behaviour tree and movement integration

Move/intent producers belong in CharacterAnimationIntentProducerSystemGroup, before Unity TransformSystemGroup; animation bridges then run before evaluation. The simple sample has a Burst ISystem movement example. Its MonoBehaviour animation writes are consumed by a subsequent ECS update; use the dedicated bridge group when same-Simulation ordering is required.

Project systems own AI state and LocalTransform. A single system in CharacterAnimationBridgeSystemGroup translates project intent into ECSAnimator commands. Presentation-event consumers run in CharacterEventSystemGroup. No AI or movement system needs a GPU buffer, BRG system, Blob layout, or Shader ABI reference.

Prototype and spawn customization

Implement ICharacterPrototypeCustomizer to add unmanaged ECS components or buffers once through CharacterPrototypeBuilder. Its PrototypeCustomizationId must change whenever the resulting configuration changes, including default values as well as the archetype. Implement ICharacterSpawnInitializer to populate those declared values through CharacterSpawnValueWriter for each new instance. ECSAnimator topology, cleanup, transform and renderer-owned material-property components cannot be replaced through this seam.

Attachments and authoring

The custom evaluator intentionally does not claim arbitrary Unity Animator equivalence. Synced and additive layers, transition interruption and state-machine routes, dynamic state speed/mirror/time/cycle-offset, Write Defaults Off history, and 2D/Direct BlendTrees have deterministic runtime implementations, with support bounded by the Unity-oracle qualification matrix for the chosen Unity version. See the current support table and limits; this is not a promise of arbitrary Animator combinations. StateMachineBehaviour, controller IK Pass, state Foot IK and Apply Root Motion are omitted without rejecting the remaining skeletal animation. Inspect preview findings and OfflinePackageBuildResult.OmittedFeatures; the conversion window also displays a completion dialog. Source Prefabs/Controllers are unchanged and state scripts are not executed during sampling. Out-of-envelope Humanoid Hips displacement and arbitrary non-transform/ObjectReference curves still produce actionable errors instead of silently publishing a wrong character. Exact Root and hand/foot goal channels from a compatible Humanoid clip/avatar are Unity offline-sampler inputs: their resulting bone matrices are baked, without adding runtime root-motion or IK execution. The lifecycle occurrence bridge replaces common enter/exit/update side effects but does not execute behaviour classes.

Editor conversion API

Multiple Animator components do not block discovery. A valid Preset Animator Path takes priority; otherwise the first component in prefab hierarchy order (including inactive children) is selected. The other controllers are omitted, and their preview-clone Animators are disabled. The whole Prefab hierarchy/renderers remain in scope; this does not split assets or export independent child animation. Ignored GameObject behaviours are listed in preview and OfflinePackageBuildResult.OmittedFeatures. The window keeps completion warnings inline/in Console without a confirmation dialog. Invalid controller, geometry, shader, LOD and artifact-integrity data still require correction.

CharacterConversionDiscovery.Discover(prefab, preset) is pure preview and returns a CharacterConversionPlan with findings, memory estimates, and CanBuild. Inspect findings for NeedsVerification; the former CanVerify convenience property is removed. Use the plan's Build; on success, load result.AssetPath as CharacterRuntimeAsset using AssetDatabase.LoadAssetAtPath in the Editor. Build rediscovers the current Prefab/preset and captures the full source identity once, then resolves the fixed last-green backup. Do not change source assets or presets during Build, including from progress callbacks; cancellation remains supported. The four-argument Discover overload supplies an explicit product key plus separate allowed/requested output roots for tools such as imported Samples.

The lower-level OfflinePackageBuildApi remains available to custom producers that already own an OfflineBuildInput and attachment array. Both paths preserve the same candidate validation, automatic fixed-backup recovery and last-green behavior. Check OfflinePackageBuildResult.IsCompleted, then inspect its asset path, source/artifact digests, last-green fields, and diagnostics. Cancellation uses the standard CancellationToken; progress uses IProgress<OfflinePackageBuildProgress>. Invoke Build synchronously on the Unity Editor main thread; do not wrap it in Task.Run or run two Editors against the same project checkout.

Public surface index

The following groups account for the package's public top-level types. The first two groups are the ordinary integration contract. Serialized artifact and renderer-diagnostic groups are public so Unity can serialize assets or expose system telemetry; application code should not construct them.

Ordinary integration

Editor integration

Ordinary CharacterConversionDiscovery.Discover(prefab, preset) defaults to the prefab directory and <prefab filename>_EntityDesc.asset. A populated preset Output Root overrides the directory. It locates a moved published descriptor by Product Key; multiple matching _EntityDesc.asset entries are rejected rather than selecting one arbitrarily. Move it through Unity's AssetDatabase with its GUID intact. The explicit identity/root overload retains the legacy published basename.

OfflineBuildInput.PublishedAssetPath optionally selects a canonical .asset path under Assets; null/empty keeps RequestedOutputRoot/CharacterRuntimeAsset.asset. Original output roots still own immutable snapshots and Recovery; the published entry may be moved outside those roots. Do not change the original roots merely to relocate an entry.

Serialized package and asset contract

V6 retains product, source and artifact digests. It stores validated state write masks and fixed attachment transforms for LocalResolve. As in V5, there are no per-source receipts or serialized controller feature declarations; the reader checks actual semantic tables and execution capacities. CharacterRuntimeAsset.EditorInitialize computes and returns the artifact digest once. It no longer accepts a caller-computed digest or receipts. V4/V5 runtime loading is rejected. Rebuilding an existing owned V4/V5 output keeps the old asset and references until the V6 candidate passes validation; cancellation or publication failure preserves/restores the old file. The ArtifactV4 lineage directory name remains stable for Editor ownership/recovery; it does not select the payload reader.

New builds store lossless compiled animation bytes in CharacterRuntimeAsset.AnimationData, a CharacterAnimationData subasset named after the selected controller (ByteCount reports its binary size). Derived Mesh and Material subassets retain source object names. The published asset owns all three; runtime loading does not depend on ArtifactV4 snapshots. Textures and shaders remain ordinary external references. The legacy Payload field and its TextAsset loading API have been removed. AnimationData is required; external-payload assets must be rebuilt from their source Prefabs. The embedded animation bytes still use the V6 binary layout. Do not construct, rename, mutate or remove generated subassets; change sources and rebuild. User assets are not automatically migrated. The distributed Demos have been rebuilt with embedded data and source prefabs beside their descriptors in Resources; their EntityDesc loading keys are unchanged. Resources output roots keep Editor lineage/recovery under the corresponding Editor/ECSAnimatorGenerated directory. These snapshots are not runtime loading dependencies. Distributed samples omit local snapshots. To start rebuilding a sample in another project, use a renamed source prefab and a preset with a new unique Product Key; retain that project's new snapshots for subsequent rebuilds.

Advanced ECS and renderer diagnostics

CharacterRootTag, CharacterDiagnostic, CharacterUpdateBudget, CharacterLodSelection, CharacterOccurrenceA, CharacterAttachmentPose, CharacterEvaluationSystem, CharacterDynamicPackingSystem, CharacterDynamicGpuUploadSystem, CharacterStaticResidencySystem, CharacterEntitiesGraphicsPresentationSystem, CharacterGpuAbi, CharacterGpuRecoveryRequirement, CharacterGpuBufferLease, CharacterStaticBufferLease, CharacterStaticPayloadSet, CharacterStaticPayloadBuilder, and CharacterDrawSubmissionObservation are framework/diagnostic surfaces. They are not required for normal spawning, AI, movement, animation commands, or attachments.

CharacterEntitiesGraphicsPresentationSystem.TryGetCurrentDrawSubmission and TryValidateCurrentShaderPasses are on-demand, main-thread diagnostics. The first scans current render plans and reports logical submissions before view culling plus completed frame retirements; its draw/visible counts are not native GPU draw-call or pixel measurements. Do not poll it per character or use it instead of Profiler/GPU evidence for performance claims.

A character evaluation failure is terminal for that instance: its diagnostic is retained, animation updates stop, render children are hidden, and commands/observations reject it. Destroy and respawn the instance after correcting the cause. Other characters continue; failed events, attachment poses and staged frozen poses are never published. Internal mutable state is not restored for retry.

External RegisterConsumer(GraphicsFence) support has been removed. Buffer leases are diagnostic lifetime pins; external GPU submission through them is unsupported. The package renderer alone owns the completion protocol, using a queue-ordered generation marker readback after detachment. A GPU submission fault stops that World at the first fault and retains resources without proven completion; it no longer continues through spare ring slots. Inspect the reported error and rebuild the World. Fault-retained buffers are never forcibly disposed; normal shutdown still drains proofs.

ECSAnimator/Lit is Stable. The documented handwritten opaque/cutout HLSL seam and package-SubGraph URP 14 Lit/Opaque/Forward profile are Preview/NeedsVerification. Graph D3D/Vulkan source variants compile; Graph Metal cross-compilation has an unresolved URP failure, and mobile device proof remains open. Transparent, Deferred and arbitrary runtime extra passes remain outside the contract; see ECSAnimator Lit deformation seam.