Class RigMetrics
Measures the body dimensions of a Humanoid rig, so that animation parameters (step length, knee height, hip sway) can be authored as fractions of the character's own proportions rather than as absolute metres. One preset then produces the same visible motion on rigs of different sizes.
public sealed class RigMetrics : MonoBehaviour
Expand Details ...
- Inheritance
-
objectObjectComponentBehaviourMonoBehaviourRigMetrics
Remarks
Measurement rules:
- Bone-to-bone lengths are world-space distances, so the character's Transform scale is included.
- Heights are measured along the character's own up axis, so a rotated character measures the same as an upright one.
- Measurement is taken in the T-pose by default (all Mecanim muscle values set to 0). The rig's current pose is restored afterwards, so calling Measure() does not change the visible pose.
- Left and right values are averaged into the reported length; the difference is reported separately as an asymmetry value.
Fields
CurrentMeasurementSchemaVersion
Which set of fields Measure() writes today. Raise it by one in the same change that adds a field to RigMetrics.Metrics, so a measurement saved before that field existed is recognised and taken again.
public const int CurrentMeasurementSchemaVersion = 1
Field Value
- int
Remarks
The same schema check the preset format already makes (IVersionedData in
Core.SaveLoad, specs/serialization-format-decision.md), applied to a serialized measurement instead of
to a saved file. It is needed for the same reason: Unity reads a field that is absent from the
saved data as that type's default, and a default is indistinguishable from a measured value.
HasChestBone false is the case that forced this — it is what a rig with no Chest bone
legitimately measures, so the booleans themselves cannot say whether a measurement is stale.
Version 1 is the first to carry HasChestBone and
HasUpperChestBone; every measurement saved before 2026-09-11 carries 0.
Settled, so that it is not reopened a third time. Those two booleans could instead
be resolved from the live Animator at bind time, which needs no serialized field and would
remove this constant, NeedsMeasurement, the check in GaitDriver.Bind
and RigMetricsMeasurementSchemaVersionTests with it. That was weighed on 2026-09-11
and again on 2026-09-12, and the serialized form was kept for two reasons: the work is
built and correct, so its cost is already paid rather than still owed; and nobody has
measured what live resolution costs where RigMetrics.Metrics is read with no Animator
available, so switching now would trade a known paid cost for an unmeasured one.
A third reason was given on 2026-09-12 and withdrawn as false on the same day: that
the full measure a stale character runs at its first bind happens once, so it is not a
recurring cost. It recurs. GaitDriver.Bind calls plain Measure() and
not MeasureAndRecord(), so that opening a scene does not mark it dirty; that
call stamps MeasurementSchemaVersion in memory and writes nothing
back to the prefab or the scene file. The saved asset still reads 0 the next time Unity
loads it from disk, so the measure runs again: on every scene open, on every play-mode
enter that reloads the scene, and on every Editor restart. Outside play mode that path
also reaches Rig.SoleGeometry.Measure, which copies a mesh's vertices and bone
weights and runs the 2048-direction reduction, once per stale character.
_Development/Assets/Scenes/SampleScene.unity carries 10 characters whose saved
measurement reads 0. Re-measuring those assets and saving them is what ends the cost
(backlog/engineering.md §25); nothing animates wrongly meanwhile, because the bind
measures before anything reads the values.
Properties
Animator
The Animator describing this rig, resolved but never created. Returns null when the character has no Animator and none has been manufactured yet.
public Animator Animator { get; }
Property Value
- Animator
Remarks
Safe to call from Inspector code, which must not add a component. Callers that need one to exist — measuring, binding — call EnsureAnimator() instead.
Avatar
This character's Humanoid Avatar. Assigning one is enough to drive the character; the Animator component is not required.
public Avatar Avatar { get; set; }
Property Value
- Avatar
Remarks
Assigning moves Version, which is what a driver that gave up on an
Avatar-less character watches. Without that, code following the console message and assigning
an Avatar at runtime would leave the character dead, because OnValidate does not run
for a script assignment.
NeedsMeasurement
True when this component has to measure before its values can be read: it has never measured successfully, or it carries a measurement written with an older set of fields than CurrentMeasurementSchemaVersion names.
public bool NeedsMeasurement { get; }
Property Value
- bool
RigRoot
The transform this rig's frame of reference sits on, resolved but never created. Never null.
public Transform RigRoot { get; }
Property Value
- Transform
Remarks
Every driver space expresses its rotations in this object's frame, and Measure() reads its scale, so anything reporting on the rig's transform has to read the same one. It is the Animator's transform when an Animator is resolvable, and the derived rig root otherwise — which is the transform EnsureAnimator() would put a manufactured Animator on, so the answer does not change when that Animator comes and goes across a domain reload.
SkeletonRootFaultWasLogged
True when the last Measure() failed on the skeleton-root fault and wrote ValidationMessage to the console itself, so a reader of that result must not print it a second time.
public bool SkeletonRootFaultWasLogged { get; }
Property Value
- bool
Remarks
This is the one measurement failure Measure() reports for itself; every other one is silent here and is reported by whoever asked for the measurement. False after any measurement that did not end in that fault, including one that failed earlier for another reason, so a message nobody printed is never suppressed. Not a field on RigMetrics.Metrics: it describes what this session printed rather than a property of the rig, and the console does not survive a scene save.
SoleGeometry
L05: both feet's reduced drawn geometry, for the sole-height compensation. Never null; IsValid says whether anything was measured. See SoleGeometry for what it holds and why it is stored rather than read per frame.
public SoleGeometryData SoleGeometry { get; }
Property Value
Values
Last measured values. Valid only when IsValid is true.
public RigMetrics.Metrics Values { get; }
Property Value
Version
Increments whenever anything a driver's bind depends on may have changed.
public int Version { get; }
Property Value
- int
Remarks
A driver that failed to bind must not retry on every frame, and must still retry the moment the user fixes what was wrong. Comparing this number is how it tells those two apart. It is deliberately not serialized: a freshly loaded driver has not bound yet and attempts its first bind whatever this value is.
Methods
DescribeSkeletonRootFault(string, string)
The fault text for CodeSmile.AnyMotion.Core.Rig.RigMetrics.FindSkeletonRootFault(UnityEngine.Animator), so the setup command and the
measurement report the same fault in the same words. Returned without the
AnyMotion: product prefix on purpose: the two console call sites add it themselves,
which keeps it out of the Inspector's error box and out of the setup command's dialog,
both of which already identify the product.
public static string DescribeSkeletonRootFault(string objectName, string fault)
Parameters
| Type | Name | Description |
|---|---|---|
objectName |
||
fault |
Returns
- string
EnsureAnimator()
Returns an Animator describing this rig, creating a hidden one from Avatar when the character has none. Returns null when neither an Animator nor an Avatar is available.
public Animator EnsureAnimator()
Returns
- Animator
Remarks
Call this from Awake, from Measure(), and from a driver's bind. Never call it from OnValidate. Unity does allow AddComponent there, but logs "SendMessage cannot be called during Awake, CheckConsistency, or OnValidate", and it refuses DestroyImmediate outright — so a component created there could not be taken back.
This modifies the scene, and it can run without the user doing anything.
GaitDriver is ExecuteAlways, so opening a scene or a prefab saved before this
change runs its OnEnable, which binds, which calls this. On a character that carries
an Animator holding an Avatar this component does not have, this back-fills the
Avatar field. That write marks the scene or the prefab dirty, so the user
sees unsaved changes on a scene they only opened. It happens once: every later call skips
the back-fill on an Avatar that is already assigned.
This never changes an Animator's enabled state. An Animator the character already
carries stays exactly as the user left it. Removed on 2026-08-24: the automatic disable
made Unity's Animation Rigging package unusable, because a rigging constraint has no
effect at any point in the frame while the Animator is off and RigBuilder requires
an Animator to exist. The Inspector warns instead when an enabled Animator has a
controller assigned — see AnimateHumanoidEditor.UpdateAnimatorControllerNotice.
FindSkeletonRootFault(Transform, Animator)
Same test against a named root, for a character being set up before its
Animator is known. Returns null when animator is null or is not humanoid:
only a Humanoid Avatar can say which transforms are bones, and no other source is consulted.
public static string FindSkeletonRootFault(Transform root, Animator animator)
Parameters
| Type | Name | Description |
|---|---|---|
root |
||
animator |
Returns
- string
Remarks
A fault can only be reported when root is the Animator's own transform or
one below it. Animator.GetBoneTransform resolves the Avatar's bone paths relative to the
Animator's own transform, so it returns that transform or a descendant of it and never an
ancestor: passing an ancestor of the Animator returns null however the character is built.
Public because the setup command, which lives in another assembly, has to refuse the same characters this refuses.
Measure()
Measures the rig and stores the result in Values. Safe to call in the Editor outside Play mode.
public bool Measure()
Returns
- bool
True when the rig could be measured.
MeasureAndRecord()
Measures the rig and writes the result durably: to Undo, to a prefab instance's modification list, and to disk.
public bool MeasureAndRecord()
Returns
- bool
True when the rig could be measured. False when the measurement failed; the reason is in ValidationMessage.
Remarks
The one definition of what a durable measurement writes, so that the manual and the automatic
paths cannot drift apart on it. Every durable measurement goes through here: the Measure
button and Measure All In Scene on this component's Inspector, Reset on this component,
and AnimateHumanoid.Reset. The two Reset callers are what make adding the
components the whole setup, so that the user never has to find this component and press
Measure by hand.
Measures every time it is called and never checks whether measurements already exist.
An existing measurement is not evidence that it was ever written durably:
GaitDriver.Bind measures in memory only, so on a character it has already bound
to, skipping on IsValid would leave the measurement out of the prefab
modification list and out of the file, and leave nothing for Ctrl+Z to undo. Measuring a
second time is cheap and produces the same values.
Undo.RecordObject, Measure(),
PrefabUtility.RecordPrefabInstancePropertyModifications and
EditorUtility.SetDirty, in the order the other editor-time writes in this file use.
The prefab call is what puts the measurement in a prefab instance's modification list;
without it the next prefab sync reverts it to the asset's unmeasured state and the
character stops animating again.
Called only from a user action, because those three recording calls are what make the
measurement durable and therefore what mark the scene dirty. Measuring from a callback
the user did not trigger would dirty a scene the user did not edit, which is the defect
recorded in engineering/known-issues.md for the Avatar back-fill.
GaitDriver.Bind covers the remaining paths with a plain Measure()
call, which writes the field in memory only and dirties nothing.
The recording is Editor-only and compiles out of a player build, where this is
Measure() and nothing more. The method itself is not behind
#if UNITY_EDITOR, for the same reason
ResolveAvatarFromChildren() is not: a caller in an Editor assembly resolves
it against this assembly as a player build compiles it.
ResolveAvatarFromChildren()
Fills Avatar from a model in this object's children, and says so when more than one child could have answered.
public void ResolveAvatarFromChildren()
Remarks
Called when the component is added and from AnimateHumanoid's own Reset. It fills the
visible field on the path most users take. It is not the only resolution path: the search in
EnsureAnimator() still runs when this field is empty, which is what keeps every
scene and prefab that already exists working.