Loading...
Loading...
Set up and tweak Meta Movement SDK (MSDK) retargeting for a character model. Use this whenever the user wants to retarget a humanoid FBX/prefab for Meta Quest body tracking, generate a retargeting config, or hand-edit the resulting `<asset>.json` (fix known-joint mappings, exclude joints from auto-mapping, rename target joints, adjust per-joint mapping weights, change a mapping behavior to twist/childAlignedTwist, edit T-pose values). The headless entry point is `Meta.XR.Movement.Editor.MSDKUtilityEditor.RunDefaultRetargetingSetup(GameObject asset)` — call it via Unity MCP first, then hand-edit if needed. **Skip** if the user is editing runtime retargeting code, the source `OVRSkeletonData.json`, or non-MSDK files.
npx skill4agent add meta-quest/agentic-tools hz-unity-meta-movement-sdk-retargetingMeta.XR.Movement.Editor.MSDKUtilityEditor.RunDefaultRetargetingSetup(GameObject asset, string customDataSourcePath = null)<asset>.json<asset>-metadata.assetRunDefaultRetargetingSetupAssets/Movement SDK/Body Tracking/Run Default Retargeting SetupUnity_RunCommandMeta.*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.Editorusing Meta.XR.Movement.Editor;using System.Reflection;System.Reflection.MethodInfoRunDefaultRetargetingSetuptarget.knownJointssource.autoMappingJointDataAssets/chestwristLeftHandPalm*WristTwistmixamorig:Hipsbn_pelvis_C_001weightPositionweightRotationbehaviortwistchildAlignedTwist{
"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: { ... } }
}sourceLeftHandWristHipstargetLeft_HandHipsSkeletonmappingminmax"Right_UpperArm"| Section | What it contains |
|---|---|
| The flat list of all joint names |
| Maps semantic role → joint name (e.g. |
| Maps |
| Per-joint position+rotation, keyed by joint name |
| Top-level keys ARE target joint names; nested |
Right_HandTwistRight_Handchesttarget.knownJoints"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"
}
}KnownJointTyperoot, hips, rightUpperArm, leftUpperArm, rightWrist, leftWrist, chest, neck, rightUpperLeg, leftUpperLeg, rightAnkle, leftAnkleMSDKUtilityEditor.RunDefaultRetargetingSetupsource.autoMappingJointDatatarget.autoMappingJointData"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
}
}AutoMappingJointFlagsexcludeFromMapping: trueexcludeFromTwistMappings: truebehavior: "twist""childAlignedTwist"weightPositionweightRotation"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 */ }
}weightRotationmappingsmapping.minmapping.maxbehavior"normal""twist""childAlignedTwist""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 }
}
}
}typebehaviormappingssourcetarget−target.joints[]target.hierarchy"<jointName>": "<parent>"target.tposetarget.tposeMintarget.tposeMaxmapping.minmapping.maxtarget.mappings.<jointName>target.knownJointstarget.jointstposetposeMintposeMaxpositionrotation"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 }
}
}
}tposeMintposeMaxconfig.coordinateSpace.metersToUnitScale: 1.0mixamorig:HipsHipsreplace_all: truetarget.*mapping.minmapping.maxsource.*source.format.skeletonFlags.noRotationCorrectionOnCoordConversiontruefalseAssets/Movement SDK/Body Tracking/Open Retargeting Configuration EditorCharacterRetargeterMovementBodygrep -c "<oldName>"rightWristleftWristtarget.knownJoints.rightWrist.leftWristtarget.joints[]target.hierarchyMSDKUtilityEditor.RunDefaultRetargetingSetupLeftHandPalmRightHandPalmsource.autoMappingJointDataexcludeFromMapping: truetarget.tpose.<root>.position.ytarget.tposeMin.<root>.position.ytarget.tposeMax.<root>.position.yRunDefaultRetargetingSetuptarget.knownJointsbn_pelvis_C_001MSDKUtilityEditor.RunDefaultRetargetingSetup(asset)GenerateMappingsmapping.minmapping.max| Edit | Survives re-run? |
|---|---|
| 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. |
| Yes |
| No — |
| No — regenerated from scratch |
| Partial — values are read into |
| Removing a joint cleanly (all sections) | Yes — |
| Renaming target joints (consistent across all sections) | Yes, same reasoning as above |
RunDefaultRetargetingSetup