TriLib Humanoid Player
This module lets a Unity Humanoid character run transform-driven Legacy or Generic clips without replacing its current AnimatorController. Because Unity cannot construct Humanoid clips at runtime, the module converts animations imported by TriLib into data that the Humanoid animation system can consume.
- Namespace:
TriLibCore.LegacyHumanoidPlayer - Assembly:
TriLibCore.LegacyHumanoidPlayer(referenced automatically) - Sample:
TriLib/TriLibSamples/AvatarLoader, especiallyAvatarLoader.csandMannequin.controller
Quick start
1. Set up the target
The destination character must have an Animator, a valid Humanoid Avatar, and an AnimatorController assigned through runtimeAnimatorController.
2. Configure an Animator state
Create a state such as Custom Animation and assign any Humanoid clip as its placeholder Motion. Although the imported clip supplies the visible movement, Unity may stop updating body transforms when the state has no Motion.
Attach PlayLegacyAnimationBehavior and set Legacy Animation Name to the baked clip name, Layer Index to 0 for a full-body clip, and Timing to ClipDriven when the placeholder is unrelated. Choose CrossFade and provide an exit state such as Idle, or use None when controller transitions manage the exit. Finally, add a transition into the state; the example below expects a PlayCustomAnimation trigger.
3. Attach the manager
Place LegacyAsHumanoidPlayablesManager on the Animator's GameObject. It builds its graph during Awake, so assign the Avatar and controller before adding the component at runtime:
var manager = animator.gameObject.AddComponent<LegacyAsHumanoidPlayablesManager>();
One manager layer is sufficient for full-body playback.
4. Convert and register
using TriLibCore.LegacyHumanoidPlayer;
var builder = HumanRetargeter.ConvertLegacyIntoHumanoidAnimationClip(
source, source.transform, sourceAvatar, legacyClip);
if (builder == null) return;
builder.Name = "CustomAnimation";
builder.WrapMode = WrapMode.Once;
var humanoidClip = builder.Build();
manager.AddAnimationClip(animator, humanoidClip, 0, takeOwnership: true);
source must be a scene instance animated by legacyClip, while sourceAvatar must describe that source skeleton. The builder name must equal the behavior's animation name. Loop and PingPong wrap modes are also available.
5. Start playback
animator.SetTrigger("PlayCustomAnimation");
On entry, the state behavior rewinds the clip and fades it in. Leaving the state fades it out.
6. Replace or free a clip
Every manager layer can contain one clip:
manager.RemoveAnimationClip(0);
manager.AddAnimationClip(animator, otherClip, 0, takeOwnership: true);
When ownership is enabled, removal or manager destruction disposes the clip. Otherwise, disconnect it before disposing it yourself because animation jobs access its native memory each frame.
Troubleshooting
| Problem | What to verify |
|---|---|
| Hips stop or tilt while limbs move | Give the state a Motion and use manager layer 0 for full-body playback. |
| Entering the state does nothing | Ensure its layer and registered name match the behavior. |
| All controller animation stops | Assign runtimeAnimatorController before manager initialization. |
| Movement scale is incorrect | Convert a scene instance and check PositionScale and RootScale. |
| Conversion produces one static pose | Keep every source parent active and validate the root and Avatar. |
| Root motion does not move the object | Extract it, check bake-into-pose settings, and use Apply Root Motion or OnAnimatorMove. |
| The requested layer is occupied | Remove its existing clip before registering another. |
Internals and advanced use
HumanRetargeter samples the source skeleton and turns each pose into Humanoid muscle curves. LegacyAsHumanoidPlayablesManager blends that result with the controller through a PlayableGraph, and PlayLegacyAnimationBehavior ties playback to an Animator state.
Full-body content belongs on manager layer 0, which cross-fades with the controller. Reserve layers 1 and above for masked or additive overlays. Each driving state needs a placeholder Motion, and its name/layer pair must match the registered clip. Always convert an instantiated source whose parent hierarchy is active, and supply the Avatar belonging to that source rig.
Conversion API
HumanoidClipBuilder ConvertLegacyIntoHumanoidAnimationClip(
GameObject sourceGameObject, Transform root, Avatar avatar,
AnimationClip legacyAnimationClip,
bool extractRootMotion = false,
float sampleRate = DefaultSampleRate);
HumanoidClipBuilder ConvertLegacyIntoHumanoidAnimationClip(
GameObject sourceGameObject, Transform root, Avatar avatar,
AnimationClip legacyAnimationClip,
RootMotionSettings rootMotionSettings,
bool extractRootMotion = false,
float sampleRate = DefaultSampleRate,
AdditiveReferencePose additiveReferencePose = default,
bool mirror = false);
Both overloads sample the Legacy clip frame by frame. Invalid inputs or a non-Humanoid Avatar produce null. The source object receives each sampled pose; root is normally source.transform. The input clip supplies default name, duration, and wrap mode. A higher sampleRate improves precision at a memory cost. mirror reflects the body on X and swaps left/right muscles, while additiveReferencePose identifies the pose removed from every sample.
Root motion and additive clips
RootMotionSettings mirrors the relevant model-importer controls. Rotation, vertical movement, and horizontal movement can each remain baked into the pose or be extracted. Their “based upon” options choose absolute body/center-of-mass values or offsets from the first frame, and rotation/Y offsets adjust the extracted result. Defaults extract horizontal motion and Y rotation while retaining vertical movement. Character movement still requires Apply Root Motion or OnAnimatorMove.
AdditiveReferencePose.Clip selects the reference animation and Frame selects a frame using that clip's frame rate, clamped to its duration. Play additive results on layer 1 or higher with Additive enabled.
Clip storage
HumanoidClipBuilder is the editable form returned by conversion. Configure Name, Length, WrapMode, CycleOffset, and Interpolation before calling Build. It also reports additive/root-motion flags and per-cycle root displacement and angle.
HumanoidClipData owns the packed native arrays used by jobs. Its wrap mode, cycle offset, and interpolation remain adjustable during playback. IsCreated reports allocation state, and Dispose() releases the memory. Do not rename it after registration.
Playback controls
LegacyAsHumanoidPlayablesManager requires an Animator on the same object. Its Layers collection must be sized before Awake; each layer has a name, mask, and additive flag. PositionScale changes baked body/hip position per axis, and RootScale scales extracted root movement after humanScale.
Use AddAnimationClip and RemoveAnimationClip to manage layer contents, SetLayerWeight to blend, and SetLayerMask or SetLayerAdditive for overlay layers. TryGetBehaviour looks up playback by layer and name; GetBehaviourOnLayer returns the layer behavior, and LayerCount reports configured layers. Destroying the manager releases its graph, muscle handles, and owned clips.
PlayLegacyAnimationBehavior manages rewinding, time, weight, and cleanup around state entry and exit. Timing may be Auto, ClipDriven, or StateDriven. Auto follows state time when lengths differ by at most 2%; otherwise it follows elapsed clip time. Exit behavior can rely on transitions (None), cross-fade once, or set a trigger once per cycle. ExitLeadTime starts an exit early enough for its blend to finish at clip end.
LegacyAsHumanoidBehaviour exposes Time, Speed, SelfAdvance, and TimeSource. Manual timing uses its own time; Playable timing follows the playable. Rewind seeks without a root-motion jump. The behavior also exposes its clip and layer index; standalone playables use their own position/root scales, while a manager overwrites those scales every frame.
manager.AddAnimationClip(animator, clip, 0);
var behaviour = manager.GetBehaviourOnLayer(0);
behaviour.Rewind();
behaviour.SelfAdvance = true;
manager.SetLayerWeight(0, 1f);
Raise the weight gradually when a smooth entrance is required.
Custom PlayableGraph
HumanoidClipPlayable.Create builds a playable without the manager:
ScriptPlayable<LegacyAsHumanoidBehaviour> Create(
PlayableGraph graph, HumanoidClipData clip, Animator animator,
HumanoidClipPlayableSettings settings);
Connect its single animation output to an AnimationMixerPlayable, AnimationLayerMixerPlayable, or AnimationPlayableOutput. Settings control position scale, root scale, time source, and clip ownership; use HumanoidClipPlayableSettings.Default, because a newly constructed settings value has zero scales.
Blend full-body clips with AnimationMixerPlayable; layer mixers should use higher inputs only for masks or additive overlays. The Animator must be Humanoid, and clip data must remain allocated until every consumer is destroyed. Use OwnsClip or dispose it only after destroying the playable/graph. Seek through SetTime or Rewind; on Unity versions before 2022.1, prefer Rewind to prevent root-motion spikes.