hz-unity-meta-movement-sdk-retargeting

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

MSDK Retargeting Config

MSDK重定向配置

Workflow: always start with the headless API

工作流:始终从无界面API开始

Don't hand-craft the JSON from scratch — the native side computes initial alignment, mappings, and T-pose data that would be tedious to write by hand. The right flow is:
  1. Generate the default config by calling
    Meta.XR.Movement.Editor.MSDKUtilityEditor.RunDefaultRetargetingSetup(GameObject asset, string customDataSourcePath = null)
    via Unity MCP. This is the headless equivalent of clicking through the Retargeting Configuration Editor UI with all defaults (Next×3 → Validate → Done) and produces both artifacts:
    • <asset>.json
      — the retargeting config (this skill targets this file)
    • <asset>-metadata.asset
      — a small ScriptableObject linking the model to the JSON (rarely needs editing)
  2. Inspect the generated JSON to verify joint mappings look right.
  3. Hand-edit only if needed — the rest of this skill describes the JSON structure and the common tweaks.
The same
RunDefaultRetargetingSetup
API also backs the
Assets/Movement SDK/Body Tracking/Run Default Retargeting Setup
editor menu item, so a user who sees a project asset selected can trigger it from the menu too.
不要从头手动编写JSON——原生端会计算初始对齐、映射和T姿态数据,手动编写这些内容非常繁琐。正确流程如下:
  1. 生成默认配置:通过Unity MCP调用
    Meta.XR.Movement.Editor.MSDKUtilityEditor.RunDefaultRetargetingSetup(GameObject asset, string customDataSourcePath = null)
    。这相当于在重定向配置编辑器UI中点击所有默认选项(三次下一步→验证→完成)的无界面版本,会生成两个文件:
    • <asset>.json
      ——重定向配置文件(本指南针对该文件)
    • <asset>-metadata.asset
      ——一个小型ScriptableObject,用于关联模型与JSON(几乎无需编辑)
  2. 检查生成的JSON,确认关节映射是否正确。
  3. 仅在必要时手动编辑——本指南剩余部分将介绍JSON结构及常见调整方法。
同一个
RunDefaultRetargetingSetup
API也支持编辑器菜单选项
Assets/Movement SDK/Body Tracking/Run Default Retargeting Setup
,因此用户选中项目资源后,也可通过菜单触发该功能。

Calling it via Unity MCP

通过Unity MCP调用

The Unity MCP
Unity_RunCommand
script runner can't see the
Meta.*
namespace from the wrapped harness; use reflection to invoke the public static method:
csharp
using UnityEngine;
using UnityEditor;

internal class CommandScript : IRunCommand
{
    public void Execute(ExecutionResult result)
    {
        var asset = AssetDatabase.LoadAssetAtPath<GameObject>("Assets/MyChar/MyChar.fbx");
        if (asset == null) { result.LogError("asset not found"); return; }

        System.Type editorType = null;
        foreach (var asm in System.AppDomain.CurrentDomain.GetAssemblies())
        {
            var t = asm.GetType("Meta.XR.Movement.Editor.MSDKUtilityEditor");
            if (t != null) { editorType = t; break; }
        }
        var method = editorType.GetMethod("RunDefaultRetargetingSetup");
        try { method.Invoke(null, new object[] { asset, null }); }
        catch (System.Reflection.TargetInvocationException tie) {
            result.LogError("RunDefaultRetargetingSetup failed: " + tie.InnerException);
            return;
        }
        result.Log("done; config at " + AssetDatabase.GetAssetPath(asset).Replace(System.IO.Path.GetExtension(AssetDatabase.GetAssetPath(asset)), ".json"));
    }
}
Notes:
  • The harness wraps the script in
    Unity.AI.Assistant.Agent.Dynamic.Extension.Editor
    , so a top-level
    using Meta.XR.Movement.Editor;
    won't compile — reflection is the workaround.
  • Avoid
    using System.Reflection;
    at the top of the file (the harness's namespace wrap interacts badly with it). Use fully-qualified
    System.Reflection.MethodInfo
    etc. inline if you need the types.
  • RunDefaultRetargetingSetup
    is non-destructive — if a JSON already exists, it loads and re-runs the per-step replay; user-made
    target.knownJoints
    and
    source.autoMappingJointData
    edits survive (see the "Re-running setup" table at the bottom).
  • The asset must live under
    Assets/
    or in an embedded package. Calling on an immutable package asset via MCP can trigger Unity's "save changes to immutable package?" dialog and fail.
Unity MCP的
Unity_RunCommand
脚本运行器无法从封装的容器中访问
Meta.*
命名空间;请使用反射调用该公开静态方法:
csharp
using UnityEngine;
using UnityEditor;

internal class CommandScript : IRunCommand
{
    public void Execute(ExecutionResult result)
    {
        var asset = AssetDatabase.LoadAssetAtPath<GameObject>("Assets/MyChar/MyChar.fbx");
        if (asset == null) { result.LogError("asset not found"); return; }

        System.Type editorType = null;
        foreach (var asm in System.AppDomain.CurrentDomain.GetAssemblies())
        {
            var t = asm.GetType("Meta.XR.Movement.Editor.MSDKUtilityEditor");
            if (t != null) { editorType = t; break; }
        }
        var method = editorType.GetMethod("RunDefaultRetargetingSetup");
        try { method.Invoke(null, new object[] { asset, null }); }
        catch (System.Reflection.TargetInvocationException tie) {
            result.LogError("RunDefaultRetargetingSetup failed: " + tie.InnerException);
            return;
        }
        result.Log("done; config at " + AssetDatabase.GetAssetPath(asset).Replace(System.IO.Path.GetExtension(AssetDatabase.GetAssetPath(asset)), ".json"));
    }
}
注意事项:
  • 容器会将脚本封装在
    Unity.AI.Assistant.Agent.Dynamic.Extension.Editor
    命名空间中,因此顶级的
    using Meta.XR.Movement.Editor;
    无法编译——反射是可行的解决方法。
  • 避免在文件顶部使用
    using System.Reflection;
    (容器的命名空间封装会与其产生不良交互)。如果需要相关类型,请在代码中使用完全限定名,如
    System.Reflection.MethodInfo
    。
  • RunDefaultRetargetingSetup
    是非破坏性的——如果JSON已存在,它会加载并重新执行每一步流程;用户对
    target.knownJoints
    和
    source.autoMappingJointData
    的编辑会保留(请参阅底部的“重新运行设置”表格)。
  • 资源必须位于
    Assets/
    目录下或嵌入式包中。通过MCP调用不可变包资源可能会触发Unity的“是否保存对不可变包的更改?”对话框并导致失败。

When to hand-edit the JSON

何时手动编辑JSON

The defaults are usually 80–95% right. Common reasons to hand-edit afterwards:
  • The auto-detector picked the wrong bone for a known joint (e.g.
    chest
    mapped to a spine bone, or
    wrist
    mapped to a wrist-twist bone).
  • Source-side joints like
    LeftHandPalm
    or
    *WristTwist
    are bleeding into target hand mappings and causing jitter.
  • The model's bones use a non-standard naming convention (
    mixamorig:Hips
    ,
    bn_pelvis_C_001
    ) and you want to clean up the joint names.
  • You want to manually tune
    weightPosition
    /
    weightRotation
    for a specific joint, or change a mapping
    behavior
    to
    twist
    /
    childAlignedTwist
    .
  • Character floats above ground / penetrates floor (T-pose root height fix).
The JSON drives runtime retargeting. The same skeleton-name strings appear across many sections — edit them all consistently or the native loader will silently produce broken mappings.
默认配置通常能满足80%-95%的需求。常见的手动编辑场景包括:
  • 自动检测器为已知关节选择了错误的骨骼(例如
    chest
    映射到脊柱骨骼,或
    wrist
    映射到腕部扭转骨骼)。
  • 源端关节如
    LeftHandPalm
    或
    *WristTwist
    渗透到目标手部映射中,导致抖动。
  • 模型骨骼使用非标准命名规则(如
    mixamorig:Hips
    、
    bn_pelvis_C_001
    ),需要清理关节名称。
  • 需要手动调整特定关节的
    weightPosition
    /
    weightRotation
    ,或将映射
    behavior
    更改为
    twist
    /
    childAlignedTwist
    。
  • 角色漂浮在空中/穿透地面(修复T姿态根节点高度)。
JSON会驱动运行时重定向。同一骨骼名称字符串会出现在多个部分中——请确保所有部分的名称一致,否则原生加载器会静默生成错误的映射。

Top-level layout

顶层结构

{
  "name": "<character name>",
  "config":  { version, coordinateSpace }
  "source":  { format, joints, manifestations, autoMappingJointData, knownJoints, hierarchy, tposeMin, tposeMax, tpose }
  "target":  { format, joints, knownJoints, hierarchy, tposeMin, tposeMax, tpose }
  "mapping": { min: { <targetJoint>: <mappingEntry> }, max: { ... } }
}
  • source
    — the Meta Quest body tracking skeleton (typically OVR FullBody, ~84 joints). Joint names are fixed (e.g.
    LeftHandWrist
    ,
    Hips
    ); usually leave alone.
  • target
    — the user's character rig. Joint names come from the model's bone hierarchy (e.g.
    Left_Hand
    ,
    Hips
    ,
    Skeleton
    ).
  • mapping
    — for each target joint, a weighted list of source joints that drive it.
    min
    is for the smallest body scale,
    max
    for the largest. The retargeter blends between them at runtime based on the user's height.
{
  "name": "<角色名称>",
  "config":  { version, coordinateSpace }
  "source":  { format, joints, manifestations, autoMappingJointData, knownJoints, hierarchy, tposeMin, tposeMax, tpose }
  "target":  { format, joints, knownJoints, hierarchy, tposeMin, tposeMax, tpose }
  "mapping": { min: { <targetJoint>: <mappingEntry> }, max: { ... } }
}
  • source
    ——Meta Quest身体追踪骨骼(通常为OVR FullBody,约84个关节)。关节名称是固定的(例如
    LeftHandWrist
    、
    Hips
    );通常无需修改。
  • target
    ——用户的角色绑定。关节名称来自模型的骨骼层级(例如
    Left_Hand
    、
    Hips
    、
    Skeleton
    )。
  • mapping
    ——针对每个目标关节,列出驱动它的源关节的加权列表。
    min
    对应最小体型,
    max
    对应最大体型。重定向器会在运行时根据用户身高在两者之间进行混合。

Joint names: where the same string must appear

关节名称:需保持一致的位置

A joint name (e.g.
"Right_UpperArm"
) referenced inconsistently across sections will break the rig. When renaming/removing a target joint, update all of these:
SectionWhat it contains
target.joints[]
The flat list of all joint names
target.knownJoints
Maps semantic role → joint name (e.g.
"hips": "Hips"
)
target.hierarchy
Maps
"<child>": "<parent>"
target.tpose
,
target.tposeMin
,
target.tposeMax
Per-joint position+rotation, keyed by joint name
mapping.min
,
mapping.max
Top-level keys ARE target joint names; nested
target.mappings
keys also reference target joint names
如果关节名称(例如
"Right_UpperArm"
)在不同部分中引用不一致,会导致绑定失效。重命名/删除目标关节时,请更新以下所有部分:
部分包含内容
target.joints[]
所有关节名称的扁平列表
target.knownJoints
语义角色→关节名称的映射(例如
"hips": "Hips"
)
target.hierarchy
"<子关节>": "<父关节>"
的映射
target.tpose
,
target.tposeMin
,
target.tposeMax
按关节名称索引的单关节位置+旋转数据
mapping.min
,
mapping.max
顶层键为目标关节名称;嵌套的
target.mappings
键也引用目标关节名称

What users most commonly tweak

用户最常调整的内容

1. Fix a known-joint binding (most common)

1. 修复已知关节绑定(最常见)

The auto-detection sometimes picks the wrong bone (e.g.
Right_HandTwist
instead of
Right_Hand
, or attaches
chest
to a spine joint instead of the chest). Fix by editing
target.knownJoints
:
json
"target": {
  "knownJoints": {
    "root": "Skeleton",
    "hips": "Hips",
    "rightUpperArm": "Right_UpperArm",
    "leftUpperArm": "Left_UpperArm",
    "rightWrist": "Right_Hand",          // ← corrected
    "leftWrist": "Left_Hand",
    "chest": "UpperChest",                // ← corrected (was "Chest")
    "neck": "Neck",
    "rightUpperLeg": "Right_UpperLeg",
    "leftUpperLeg": "Left_UpperLeg",
    "rightAnkle": "Right_Foot",
    "leftAnkle": "Left_Foot"
  }
}
The 12 keys are the only valid ones (matches
KnownJointType
enum:
root, hips, rightUpperArm, leftUpperArm, rightWrist, leftWrist, chest, neck, rightUpperLeg, leftUpperLeg, rightAnkle, leftAnkle
). After editing this, the user typically also wants to re-run alignment — recommend they re-run
MSDKUtilityEditor.RunDefaultRetargetingSetup
on the asset (it will preserve the JSON and re-derive mappings).
自动检测有时会选择错误的骨骼(例如
Right_HandTwist
而非
Right_Hand
,或
chest
绑定到脊柱关节而非胸部关节)。通过编辑
target.knownJoints
修复:
json
"target": {
  "knownJoints": {
    "root": "Skeleton",
    "hips": "Hips",
    "rightUpperArm": "Right_UpperArm",
    "leftUpperArm": "Left_UpperArm",
    "rightWrist": "Right_Hand",          // ← 已修正
    "leftWrist": "Left_Hand",
    "chest": "UpperChest",                // ← 已修正(原为"Chest")
    "neck": "Neck",
    "rightUpperLeg": "Right_UpperLeg",
    "leftUpperLeg": "Left_UpperLeg",
    "rightAnkle": "Right_Foot",
    "leftAnkle": "Left_Foot"
  }
}
这12个键是唯一有效的值(与
KnownJointType
枚举匹配:
root, hips, rightUpperArm, leftUpperArm, rightWrist, leftWrist, chest, neck, rightUpperLeg, leftUpperLeg, rightAnkle, leftAnkle
)。编辑完成后,用户通常需要重新运行对齐操作——建议他们对资源重新运行
MSDKUtilityEditor.RunDefaultRetargetingSetup
(该操作会保留JSON并重新生成映射)。

2. Exclude a joint from auto-mapping

2. 排除关节自动映射

Useful when the auto-mapper ties a source twist/palm joint to an unwanted target joint. Add to
source.autoMappingJointData
(or
target.autoMappingJointData
for target-side exclusions):
json
"source": {
  "autoMappingJointData": {
    "LeftHandPalm":         { "excludeFromMapping": true },
    "LeftHandWristTwist":   { "excludeFromMapping": true },
    "RightHandPalm":        { "excludeFromMapping": true },
    "RightHandWristTwist":  { "excludeFromMapping": true },
    "LeftFootAnkleTwist":   { "excludeFromMapping": true },
    "RightFootAnkleTwist":  { "excludeFromMapping": true },
    "Some_Custom_Joint":    { "excludeFromTwistMappings": true }   // ← only twist mapping skipped
  }
}
Two flags exist (from
AutoMappingJointFlags
):
  • excludeFromMapping: true
    — joint is completely ignored when generating mappings
  • excludeFromTwistMappings: true
    — joint is still mapped normally but skipped when generating
    behavior: "twist"
    /
    "childAlignedTwist"
    entries
The keys' iteration order in this object is non-deterministic (native unordered map). Don't rely on it; same set of keys = equivalent config.
当自动映射器将源端扭转/手掌关节绑定到不需要的目标关节时,此操作非常有用。将关节添加到
source.autoMappingJointData
(或目标端排除则添加到
target.autoMappingJointData
):
json
"source": {
  "autoMappingJointData": {
    "LeftHandPalm":         { "excludeFromMapping": true },
    "LeftHandWristTwist":   { "excludeFromMapping": true },
    "RightHandPalm":        { "excludeFromMapping": true },
    "RightHandWristTwist":  { "excludeFromMapping": true },
    "LeftFootAnkleTwist":   { "excludeFromMapping": true },
    "RightFootAnkleTwist":  { "excludeFromMapping": true },
    "Some_Custom_Joint":    { "excludeFromTwistMappings": true }   // ← 仅跳过扭转映射
  }
}
有两个标志(来自
AutoMappingJointFlags
):
  • excludeFromMapping: true
    ——生成映射时完全忽略该关节
  • excludeFromTwistMappings: true
    ——关节仍会正常映射,但生成
    behavior: "twist"
    /
    "childAlignedTwist"
    条目时会被跳过
该对象中键的迭代顺序是非确定性的(原生无序映射)。请勿依赖顺序;键集相同即为等效配置。

3. Tweak per-joint mapping weights

3. 调整单关节映射权重

A mapping entry is keyed by the target joint name and lists source joints that drive it, each with
weightPosition
(0–1) and
weightRotation
(0–1). Both default to summing toward 1.0 across siblings, but the runtime normalizes — relative weights matter, not absolute.
json
"mapping": {
  "min": {
    "Left_Hand": {
      "type": "source",
      "behavior": "normal",
      "mappings": {
        "LeftHandWrist": { "weightPosition": 1.0, "weightRotation": 1.0 }
      }
    },
    "Hips": {
      "type": "source",
      "behavior": "normal",
      "mappings": {
        "Hips":          { "weightPosition": 0.234, "weightRotation": 1.0 },
        "SpineLower":    { "weightPosition": 0.256, "weightRotation": 0.0 },
        "LeftUpperLeg":  { "weightPosition": 0.121, "weightRotation": 0.0 },
        "RightUpperLeg": { "weightPosition": 0.121, "weightRotation": 0.0 }
      }
    }
  },
  "max": { /* same shape, different weights for max body scale */ }
}
Common tweaks:
  • Set
    weightRotation
    to 0 if you only want a source joint to influence position (or vice-versa).
  • Add a source joint to the
    mappings
    dict to give it influence over the target joint.
  • mapping.min
    and
    mapping.max
    are usually identical or close — set them the same when in doubt.
映射条目以目标关节名称为键,列出驱动它的源关节,每个关节都有
weightPosition
(0–1)和
weightRotation
(0–1)。两者默认值相加为1.0,但运行时会进行归一化——相对权重比绝对权重更重要。
json
"mapping": {
  "min": {
    "Left_Hand": {
      "type": "source",
      "behavior": "normal",
      "mappings": {
        "LeftHandWrist": { "weightPosition": 1.0, "weightRotation": 1.0 }
      }
    },
    "Hips": {
      "type": "source",
      "behavior": "normal",
      "mappings": {
        "Hips":          { "weightPosition": 0.234, "weightRotation": 1.0 },
        "SpineLower":    { "weightPosition": 0.256, "weightRotation": 0.0 },
        "LeftUpperLeg":  { "weightPosition": 0.121, "weightRotation": 0.0 },
        "RightUpperLeg": { "weightPosition": 0.121, "weightRotation": 0.0 }
      }
    }
  },
  "max": { /* 结构相同,最大体型对应不同权重 */ }
}
常见调整:
  • 如果仅希望源关节影响位置(或反之),可将
    weightRotation
    设为0。
  • 在
    mappings
    字典中添加源关节,使其对目标关节产生影响。
  • mapping.min
    和
    mapping.max
    通常相同或接近——不确定时可设为一致。

4. Change mapping behavior (normal / twist / childAlignedTwist)

4. 更改映射行为(normal / twist / childAlignedTwist)

Three legal values for
behavior
:
  • "normal"
    — direct weighted blend
  • "twist"
    — for source-side twist joints; aligns parent rotation through to the twist
  • "childAlignedTwist"
    — for target-side twist behavior; aligns from twist joint down to children
When a target joint has both source and target sub-mappings (e.g. for arm/leg twist), the entry has a different shape:
json
"Left_UpperArm": {
  "source": {
    "behavior": "normal",
    "mappings": {
      "LeftShoulder":  { "weightPosition": 0.086, "weightRotation": 0.086 },
      "LeftScapula":   { "weightPosition": 0.513, "weightRotation": 0.513 },
      "LeftArmUpper":  { "weightPosition": 0.402, "weightRotation": 0.402 }
    }
  },
  "target": {
    "behavior": "childAlignedTwist",
    "mappings": {
      "Left_LowerArm": { "weightPosition": 0.0, "weightRotation": 0.5 },
      "Left_UpperArm": { "weightPosition": 0.0, "weightRotation": 0.5 }
    }
  }
}
Note the structural difference from a "single" entry: no top-level
type
/
behavior
/
mappings
, instead a
source
block + a
target
block, each with its own behavior + mappings dict.
behavior
有三个合法值:
  • "normal"
    ——直接加权混合
  • "twist"
    ——针对源端扭转关节;将父关节旋转传递到扭转关节
  • "childAlignedTwist"
    ——针对目标端扭转行为;从扭转关节向下对齐到子关节
当目标关节同时有源端和目标端子映射时(例如手臂/腿部扭转),条目结构会有所不同:
json
"Left_UpperArm": {
  "source": {
    "behavior": "normal",
    "mappings": {
      "LeftShoulder":  { "weightPosition": 0.086, "weightRotation": 0.086 },
      "LeftScapula":   { "weightPosition": 0.513, "weightRotation": 0.513 },
      "LeftArmUpper":  { "weightPosition": 0.402, "weightRotation": 0.402 }
    }
  },
  "target": {
    "behavior": "childAlignedTwist",
    "mappings": {
      "Left_LowerArm": { "weightPosition": 0.0, "weightRotation": 0.5 },
      "Left_UpperArm": { "weightPosition": 0.0, "weightRotation": 0.5 }
    }
  }
}
注意与“单一”条目的结构差异:没有顶层的
type
/
behavior
/
mappings
,而是包含一个
source
块和一个
target
块,每个块都有自己的行为和映射字典。

5. Remove a joint from the target rig

5. 从目标绑定中移除关节

If a model has extra bones the source skeleton doesn't have (e.g. cape bones, weapon bones), exclude them from retargeting. The UI's
−
button on a leaf joint does this. To do it manually, remove the joint name from all of these:
  • target.joints[]
    — drop the string
  • target.hierarchy
    — drop the
    "<jointName>": "<parent>"
    entry, AND drop any entry whose value points to the removed joint (orphan check)
  • target.tpose
    ,
    target.tposeMin
    ,
    target.tposeMax
    — drop the entry
  • mapping.min
    ,
    mapping.max
    — drop the entry keyed by the removed joint name; also drop any nested
    target.mappings.<jointName>
    reference
  • If the joint appeared in
    target.knownJoints
    , you've removed something semantically important — pick a replacement first
The runtime ignores joints absent from
target.joints
, so leaving stale entries elsewhere produces silent dead data. Always clean them up.
如果模型包含源骨骼中没有的额外骨骼(例如披风骨骼、武器骨骼),请将其从重定向中排除。UI中叶子关节的
−
按钮可实现此操作。手动操作时,请从以下所有部分中移除关节名称:
  • target.joints[]
    ——删除该字符串
  • target.hierarchy
    ——删除
    "<jointName>": "<parent>"
    条目,同时删除所有值指向该关节的条目(检查孤儿关节)
  • target.tpose
    ,
    target.tposeMin
    ,
    target.tposeMax
    ——删除该条目
  • mapping.min
    ,
    mapping.max
    ——删除以该关节名称为键的条目;同时删除所有嵌套的
    target.mappings.<jointName>
    引用
  • 如果该关节出现在
    target.knownJoints
    中,说明你移除了具有重要语义的关节——请先选择替代关节
运行时会忽略
target.joints
中不存在的关节,因此其他部分中残留的条目会产生静默无效数据。请务必清理干净。

6. Edit T-pose values

6. 编辑T姿态数值

tpose
(unscaled),
tposeMin
(smallest scale),
tposeMax
(largest scale) are keyed by joint name and contain world-space
position
(xyz) +
rotation
(xyzw quaternion). All in root-origin space (parent = root joint).
json
"target": {
  "tpose": {
    "Hips": {
      "position": { "x": 0.0, "y": 0.981, "z": -0.016 },
      "rotation": { "x": 0.0, "y": 0.0, "z": 0.0, "w": 1.0 }
    }
  }
}
Common tweaks:
  • Adjust hip Y-position to fix character ground penetration / floating
  • Re-orient wrist rotation to match controller grip pose
  • Make
    tposeMin
    and
    tposeMax
    deliberately different to give the runtime a wider scale range
The unit is meters (per
config.coordinateSpace.metersToUnitScale: 1.0
). Quaternions are unit-length — preserve that when editing rotations.
tpose
(未缩放)、
tposeMin
(最小缩放)、
tposeMax
(最大缩放)以关节名称为键,包含世界空间的
position
(xyz)+
rotation
(xyzw四元数)。所有数据均位于根节点原点空间(父节点为根关节)。
json
"target": {
  "tpose": {
    "Hips": {
      "position": { "x": 0.0, "y": 0.981, "z": -0.016 },
      "rotation": { "x": 0.0, "y": 0.0, "z": 0.0, "w": 1.0 }
    }
  }
}
常见调整:
  • 调整臀部Y轴位置,修复角色地面穿透/漂浮问题
  • 重新调整腕部旋转,使其匹配控制器握持姿态
  • 故意设置
    tposeMin
    和
    tposeMax
    不同,为运行时提供更宽的缩放范围
单位为米(根据
config.coordinateSpace.metersToUnitScale: 1.0
)。四元数为单位长度——编辑旋转时请保持这一点。

7. Rename target joints to match a different skeleton naming convention

7. 重命名目标关节以匹配其他骨骼命名规则

If the model's bones are named
mixamorig:Hips
but downstream tooling expects
Hips
, you can globally rename in the JSON without touching the FBX. Use
replace_all: true
on the Edit tool, but only within the
target.*
and
mapping.min
/
mapping.max
sections — never touch
source.*
, which uses the fixed OVR naming.
如果模型骨骼命名为
mixamorig:Hips
,但下游工具期望
Hips
,可在JSON中全局重命名而无需修改FBX。使用编辑工具的
replace_all: true
,但仅在
target.*
和
mapping.min
/
mapping.max
部分中操作——切勿修改
source.*
,因为它使用固定的OVR命名。

8. Switch the source skeleton format

8. 切换源骨骼格式

Rare: changing
source.format.skeletonFlags.noRotationCorrectionOnCoordConversion
between
true
(X-Engine native skeletons like OVR) and
false
(FBX/GLTF). Don't change unless you're swapping in a non-OVR source.
罕见场景:在
true
(X-Engine原生骨骼如OVR)和
false
(FBX/GLTF)之间切换
source.format.skeletonFlags.noRotationCorrectionOnCoordConversion
。除非要替换非OVR源,否则请勿更改。

Validation

验证

After editing, the user should:
  1. Reload in Unity — Unity auto-imports JSON. If it's malformed, the console shows JSON parse errors immediately.
  2. Reopen in the UI —
    Assets/Movement SDK/Body Tracking/Open Retargeting Configuration Editor
    on the model. The editor will silently fall back to defaults if a known joint is missing or a mapping references a non-existent joint, so look for warnings + inspect the bone foldout.
  3. Spot-check at runtime — drop the character into a scene with a
    CharacterRetargeter
    component (or use the existing
    MovementBody
    test scene) and verify limbs track correctly.
The runtime tolerates extra unrecognized fields in the JSON, so additive edits are safe; the dangerous edits are removals and renames. Always cross-check joint name consistency across all sections (use
grep -c "<oldName>"
before and after to confirm a rename touched everything).
编辑完成后,用户应:
  1. 在Unity中重新加载——Unity会自动导入JSON。如果格式错误,控制台会立即显示JSON解析错误。
  2. 在UI中重新打开——在模型上选择
    Assets/Movement SDK/Body Tracking/Open Retargeting Configuration Editor
    。如果缺少已知关节或映射引用了不存在的关节,编辑器会静默回退到默认值,因此请查看警告并检查骨骼折叠面板。
  3. 运行时抽查——将角色拖入带有
    CharacterRetargeter
    组件的场景(或使用现有
    MovementBody
    测试场景),验证肢体追踪是否正确。
运行时会容忍JSON中额外的未识别字段,因此增量编辑是安全的;危险的编辑是删除和重命名。始终交叉检查所有部分的关节名称一致性(重命名前后使用
grep -c "<oldName>"
确认所有引用都已修改)。

Sample workflow recipes

示例工作流方案

"The character's wrists are bent backwards in MR" → Wrong
rightWrist
/
leftWrist
known joint. Open the asset's JSON, fix
target.knownJoints.rightWrist
and
.leftWrist
to point to the actual hand bones (look at
target.joints[]
+
target.hierarchy
to find the right names), then re-run
MSDKUtilityEditor.RunDefaultRetargetingSetup
.
"Fingers are jittery" → The source has
LeftHandPalm
/
RightHandPalm
mapped into the target. Add them to
source.autoMappingJointData
with
excludeFromMapping: true
, then re-run setup.
"Character floats above ground" → Adjust
target.tpose.<root>.position.y
and
target.tposeMin.<root>.position.y
and
target.tposeMax.<root>.position.y
downward (often the rig has the root at hip-height instead of floor).
"Need to retarget a humanoid with non-standard bone names" → After running
RunDefaultRetargetingSetup
, fix
target.knownJoints
to point to the actual bone names. The auto-detector uses heuristics that fail on naming like
bn_pelvis_C_001
.
“MR中角色手腕向后弯曲”→
rightWrist
/
leftWrist
已知关节错误。打开资源的JSON,将
target.knownJoints.rightWrist
和
.leftWrist
修改为指向实际手部骨骼(查看
target.joints[]
+
target.hierarchy
找到正确名称),然后重新运行
MSDKUtilityEditor.RunDefaultRetargetingSetup
。
“手指抖动”→源端
LeftHandPalm
/
RightHandPalm
映射到了目标端。将它们添加到
source.autoMappingJointData
并设置
excludeFromMapping: true
,然后重新运行设置。
“角色漂浮在空中”→向下调整
target.tpose.<root>.position.y
、
target.tposeMin.<root>.position.y
和
target.tposeMax.<root>.position.y
(通常绑定的根节点位于臀部高度而非地面)。
“需要重定向具有非标准骨骼名称的类人角色”→运行
RunDefaultRetargetingSetup
后,修改
target.knownJoints
指向实际骨骼名称。自动检测器使用的启发式算法在遇到
bn_pelvis_C_001
这类命名时会失效。

Re-running setup after edits

编辑后重新运行设置

MSDKUtilityEditor.RunDefaultRetargetingSetup(asset)
reuses the existing JSON when one is present (it doesn't recreate from scratch), but it does run
GenerateMappings
for the MinTPose/MaxTPose steps, which overwrites
mapping.min
/
mapping.max
. Survival summary:
EditSurvives re-run?
target.knownJoints
(any of the 12 keys)
Yes, if the joint name actually exists in the model's transform hierarchy. If you typo a name, the next re-run silently sets it to empty.
source.autoMappingJointData
with
excludeFromMapping: true
Yes
source.autoMappingJointData
with
excludeFromTwistMappings: true
No —
SkeletonData.GenerateAutoMappingExcludedJointDataFromJointNameList
only round-trips the
Exclude
flag. The twist-only exclusion gets dropped on re-run.
mapping.min
/
mapping.max
weight tweaks
No — regenerated from scratch
target.tpose*
position/rotation edits
Partial — values are read into
SkeletonData
, but every step's
UpdateConfig
re-reads scene transforms via
JointAlignmentUtility.UpdateTPoseData
. Edits stick only if the model itself has those poses on disk, otherwise the scene values overwrite them.
Removing a joint cleanly (all sections)Yes —
target.joints
is the source of truth
Renaming target joints (consistent across all sections)Yes, same reasoning as above
So: do mapping-weight tweaks, twist-only exclusions, and ad-hoc T-pose tweaks last, after any final
RunDefaultRetargetingSetup
. For known-joints and exclude-from-mapping edits, re-run is fine.
MSDKUtilityEditor.RunDefaultRetargetingSetup(asset)
会在JSON已存在时重用现有文件(不会从头重建),但会为MinTPose/MaxTPose步骤运行
GenerateMappings
,这会覆盖
mapping.min
/
mapping.max
。编辑内容保留情况总结:
编辑内容重新运行后是否保留?
target.knownJoints
(12个键中的任意一个)
是,前提是关节名称确实存在于模型的变换层级中。如果输入错误名称,下次重新运行会静默将其设为空。
带有
excludeFromMapping: true
的
source.autoMappingJointData
是
带有
excludeFromTwistMappings: true
的
source.autoMappingJointData
否——
SkeletonData.GenerateAutoMappingExcludedJointDataFromJointNameList
仅往返
Exclude
标志。仅排除扭转的设置会在重新运行时丢失。
mapping.min
/
mapping.max
权重调整
否——会从头重新生成
target.tpose*
位置/旋转编辑
部分保留——数值会被读入
SkeletonData
,但每一步的
UpdateConfig
都会通过
JointAlignmentUtility.UpdateTPoseData
重新读取场景变换。只有当模型本身在磁盘上保存了这些姿态时,编辑内容才会保留,否则场景值会覆盖它们。
干净移除关节(所有部分)是——
target.joints
是数据源
一致重命名目标关节(所有部分)是,原因同上
因此:映射权重调整、仅排除扭转的设置和临时T姿态调整请最后进行,在最终运行
RunDefaultRetargetingSetup
之后。对于已知关节和排除映射的编辑,重新运行设置是安全的。