Class RigMetrics

Namespace
CodeSmile.AnyMotion.Core.Rig
/
Assembly
ApiSource.dll

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
object
Object
Component
Behaviour
MonoBehaviour
RigMetrics

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

SoleGeometryData

Values

Last measured values. Valid only when IsValid is true.

public RigMetrics.Metrics Values { get; }

Property Value

RigMetrics.Metrics

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
string
objectName
string
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
Transform
root
Animator
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.