关于 hz-unity-meta-movement-sdk-retargeting
此技能指导在 Unity 中为 Meta Movement SDK 角色生成和调整重定向配置。它优先调用原生编辑器 API 创建初始 JSON,再按需修改关节映射、权重、行为和 T-pose 数据。
主要功能
- 01调用编辑器 API 生成默认配置
- 02检查并修正目标关节绑定
- 03排除关节或调整映射权重
- 04修改映射行为与 T-pose 数值
- 05说明重命名关节时需同步更新的位置
适用场景
- 01修正自动检测错误的骨骼绑定
- 02减少手掌或扭转关节造成的映射干扰
- 03调整角色落地高度或关节旋转
- 04适配非标准骨骼命名的模型
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ meta-quest/ agentic-tools --skill hz-unity-meta-movement-sdk-retargeting安装后在 Claude Code 中输入/hz-unity-meta-movement-sdk-retargeting确认已加载。
附带文件references/unity-mcp-fallback.md
详细介绍
MSDK 重定向配置
工作流:始终从无头 API 开始
不要从头手动编写 JSON——原生端会计算初始对齐、映射和 T-pose 数据,手动编写这些内容会很繁琐。正确的流程是:
- 生成默认配置:在运行中的 Editor 中通过
unity-cli调用Meta.XR.Movement.Editor.MSDKUtilityEditor.RunDefaultRetargetingSetup(GameObject asset, string customDataSourcePath = null)。这相当于使用所有默认选项(Next×3 → Validate → Done)依次操作 Retargeting Configuration Editor UI,并会生成两个文件:<asset>.json— 重定向配置(本指南针对该文件)<asset>-metadata.asset— 用于关联模型与 JSON 的小型 ScriptableObject(极少需要编辑)
- 检查生成的 JSON,确认关节映射正确。
- 仅在需要时手动编辑——本指南的其余部分将介绍 JSON 结构和常见调整。
Assets/Movement SDK/Body Tracking/Run Default Retargeting Setup 编辑器菜单项也调用同一个 RunDefaultRetargetingSetup API,因此用户选中项目资源后,也可以从菜单触发此操作。
使用 unity-cli 调用
run_script 会针对所有已加载程序集进行编译,因此可以直接引用 Meta.* Editor 命名空间——直接正常调用该方法,无需反射:
// AgentScripts/RunRetargetingSetup.cs
using Meta.XR.Movement.Editor;
using UnityEditor;
using UnityEngine;
public static class RunRetargetingSetup
{
public static string Run(string assetPath)
{
var asset = AssetDatabase.LoadAssetAtPath<GameObject>(assetPath);
if (asset == null) return $"ERROR: asset not found at {assetPath}";
MSDKUtilityEditor.RunDefaultRetargetingSetup(asset, null);
var jsonPath = System.IO.Path.ChangeExtension(assetPath, ".json");
return $"done; config at {jsonPath} (exists: {System.IO.File.Exists(jsonPath)})";
}
}
unity status --format json # look for state "ready"
unity command run_script --file AgentScripts/RunRetargetingSetup.cs \
--entry RunRetargetingSetup.Run --args '["Assets/MyChar/MyChar.fbx"]' --format json
将 .cs 文件放在 Assets/ 之外(路径相对于项目根目录),这样写入文件就不会触发导入或域重新加载。有关连接和 --project-path,请参阅 unity-cli 技能;基于反射的 MCP 版本见
references/unity-mcp-fallback.md。
注意:
RunDefaultRetargetingSetup不会造成破坏性更改——如果 JSON 已存在,它会加载该文件并重新运行逐步重放;用户对target.knownJoints和source.autoMappingJointData所做的编辑会保留(见文末的“重新运行设置”表格)。- 资源必须位于
Assets/下或嵌入式包中。对不可变包资源调用此方法可能会触发 Unity 的“保存更改到不可变包?”模态对话框,从而阻塞请求——请先将资源复制到Assets/中。
何时手动编辑 JSON
默认配置通常有 80–95% 正确。之后手动编辑的常见原因:
- 自动检测器为已知关节选错了骨骼(例如,
chest映射到了脊柱骨骼,或wrist映射到了腕部扭转骨骼)。 LeftHandPalm或*WristTwist等源端关节混入了目标手部映射,导致抖动。- 模型骨骼使用非标准命名约定(
mixamorig:Hips、bn_pelvis_C_001),你想整理关节名称。 - 你想手动调整特定关节的
weightPosition/weightRotation,或将映射behavior更改为twist/childAlignedTwist。 - 角色悬浮在地面上方/穿透地板(修正 T-pose 根节点高度)。
- 角色缩小时发生倾斜/剪切(骨架尺寸过大——应修正 FBX 导入比例,而不是 JSON;见“修复模型缩小时发生倾斜的问题”)。
JSON 控制运行时重定向。相同的骨架名称字符串会出现在多个部分——请在所有位置保持一致地编辑,否则原生加载器会静默生成错误的映射。
顶层布局
{
"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— Meta VR 身体追踪骨架(通常为 OVR FullBody,约 84 个关节)。关节名称固定(例如LeftHandWrist、Hips);通常保持不变。target— 用户的角色骨架。关节名称来自模型的骨骼层级结构(例如Left_Hand、Hips、Skeleton)。mapping— 针对每个目标关节,用于驱动该关节的加权源关节列表。min表示最小体型,max表示最大体型。重定向器会在运行时根据用户身高在两者之间进行混合。
关节名称:同一字符串必须保持一致
如果同一关节名称(例如 "Right_UpperArm")在不同部分中的引用不一致,就会导致骨架绑定出错。重命名或移除目标关节时,请更新以下所有内容:
| 部分 | 内容 |
|---|---|
target.joints[] | 所有关节名称的扁平列表 |
target.knownJoints | 将语义角色映射到关节名称(例如 "hips": "Hips") |
target.hierarchy | 将 "<child>": "<parent>" 进行映射 |
target.tpose、target.tposeMin、target.tposeMax | 以关节名称为键的每个关节的位置和旋转 |
mapping.min、mapping.max | 顶层键就是目标关节名称;嵌套的 target.mappings 键也引用目标关节名称 |
用户最常调整的内容
1. 修正已知关节绑定(最常见)
自动检测有时会选错骨骼(例如选中 Right_HandTwist 而不是 Right_Hand,或将 chest 绑定到脊柱关节而不是胸部)。通过编辑 target.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"
}
}
这 12 个键是唯一有效的键(与 KnownJointType 枚举一致:root, hips, rightUpperArm, leftUpperArm, rightWrist, leftWrist, chest, neck, rightUpperLeg, leftUpperLeg, rightAnkle, leftAnkle)。编辑后,用户通常还需要重新运行对齐操作——建议他们对该资源重新运行 MSDKUtilityEditor.RunDefaultRetargetingSetup(它会保留 JSON 并重新推导映射)。
2. 从自动映射中排除关节
当自动映射器将源扭转关节或手掌关节绑定到不需要的目标关节时,此操作很有用。添加到 source.autoMappingJointData(若要排除目标侧关节,则添加到 target.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
}
}
AutoMappingJointFlags 中有两个标志:
excludeFromMapping: true— 生成映射时完全忽略该关节excludeFromTwistMappings: true— 该关节仍会正常映射,但生成behavior: "twist"/"childAlignedTwist"条目时会跳过它
此对象中键的迭代顺序不确定(原生无序映射)。不要依赖该顺序;键集合相同即表示配置等效。
3. 调整每个关节的映射权重
映射条目以目标关节名称为键,并列出驱动它的源关节,每个源关节都有 weightPosition(0–1)和 weightRotation(0–1)。两者默认在同级关节间的总和接近 1.0,但运行时会进行归一化——重要的是相对权重,而非绝对权重。
"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 */ }
}
常见调整:
- 如果只想让源关节影响位置(或反之),将
weightRotation设为 0。 - 在
mappings字典中添加源关节,使其对目标关节产生影响。 mapping.min和mapping.max通常相同或接近——不确定时将它们设为相同值。
4. 更改映射行为(normal / twist / childAlignedTwist)
behavior 有三个合法值:
"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 }
}
}
}
请注意,这与“单一”条目的结构不同:没有顶层的 type/behavior/mappings,而是包含一个 source 块和一个 target 块,各自都有自己的 behavior 和 mappings 字典。
5. 从目标绑定中移除关节
如果模型包含源骨架中没有的额外骨骼(例如披风骨骼、武器骨骼),请将它们排除在重定向之外。可以使用 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. 编辑 T-pose 值
tpose(未缩放)、tposeMin(最小比例)、tposeMax(最大比例)以关节名称为键,包含世界空间中的 position(xyz)和 rotation(xyzw 四元数)。所有值都使用根原点空间(父级 = 根关节)。
"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)。四元数的长度为 1——编辑旋转时请保持这一点。
7. 重命名目标关节,以匹配不同的骨架命名约定
如果模型的骨骼命名为 mixamorig:Hips,但下游工具需要 Hips,你可以直接在 JSON 中全局重命名,而不必修改 FBX。使用 Edit 工具的 replace_all: true,但仅限 target.* 和 mapping.min/mapping.max 部分——绝不要修改使用固定 OVR 命名的 source.*。
8. 切换源骨架格式
这种情况很少见:在 true(X-Engine 原生骨架,如 OVR)和 false(FBX/GLTF)之间切换 source.format.skeletonFlags.noRotationCorrectionOnCoordConversion。除非你要换用非 OVR 源,否则不要更改此值。
9. 修复缩小后模型发生变形的问题(骨架过大)
**症状:**角色重定向到的位置大致正确,但身体部位会拉扯、变形——在 Min T-pose(用户身材较小/缩小模型)时最严重。这不是 JSON 映射错误,而是模型缩放问题,修复方法在 FBX 导入设置中,而不在配置文件中。
原因:运行时通过两个独立参数调整角色大小:(a)按关节重定向的位置(肢体长度,从不受限),以及(b)统一的根节点 localScale,用于缩小网格(厚度/蒙皮)。根节点缩放受限于 SkeletonRetargeter._scaleRange,其默认值为 (0.8, 1.2)。如果 FBX 的制作尺寸远高于真人身高,运行时所需的缩放值就会低于 0.8:关节间距会缩至(例如)原来的 30%,而网格最多只能缩至原来的 80%,于是线性混合蒙皮会将过大的网格拖拽到缩短后的骨骼上 → 变形。在 Min 时问题最严重,因为此时所需缩放值远低于 0.8 的下限。
**如何发现:**检查 <asset>.json 中未缩放的 T-pose 高度——查看 target.tpose 中髋部的 Y 坐标和整体跨度。身高正常的人形模型约为 1.5–2.0 m(髋部约为 0.9 m)。如果模型身高约为其 2 倍(例如身高 3.7 m、髋部位于约 2.1 m 处),所需的最小/最大缩放值约为 0.30–0.62,远超 (0.8, 1.2) 的范围。Mixamo 骨架(骨骼前缀为 mixamorig:)是常见的问题来源,而且它们通常会以 Generic 而不是 Humanoid 导入。
修复方法(首选——修正制作时的缩放):在 FBX ModelImporter 中设置 Scale Factor,使骨架达到真人身高(targetHeight / currentHeight,例如 1.7 / 3.7 ≈ 0.46),并将 Rig → Animation Type 设为 Humanoid。然后重新导入模型并重新生成配置——两步都必须执行:
// AgentScripts/FixImportScale.cs
using UnityEditor;
public static class FixImportScale
{
public static string Run(string path, float scale)
{
var importer = (ModelImporter)AssetImporter.GetAtPath(path);
if (importer == null) return $"ERROR: no ModelImporter at {path}";
importer.globalScale = scale; // targetHeight / currentHeight
importer.useFileScale = false; // honor globalScale, not the FBX unit
importer.animationType = ModelImporterAnimationType.Human; // Humanoid
importer.SaveAndReimport(); // re-import with new settings
return $"reimported {path} at globalScale={scale}";
}
}
unity command run_script --file AgentScripts/FixImportScale.cs \
--entry FixImportScale.Run --args '["Assets/MyChar/MyChar.fbx", 0.46]' --format json
然后重新运行 RunDefaultRetargetingSetup(见上文),根据现在缩放正确的骨架重新生成 T-pose/最小值/最大值。
SaveAndReimport() 会触发资源导入,因此应将重新生成作为单独的 run_script 调用执行,而不要在同一个脚本中串联执行。
执行 SaveAndReimport() 后,磁盘上的 T-pose 会发生变化,因此旧的 <asset>.json 已过时——之后必须重新运行 MSDKUtilityEditor.RunDefaultRetargetingSetup。再次确认未缩放的 T-pose 高度现在符合真人尺寸。
**快速权宜之计(仅在无法更改模型时使用):**扩大 CharacterRetargeter 组件上的 _scaleRange(例如 (0.25, 1.5)),使根节点缩放不再受限。这样可以避免变形,但会让骨架在超出其设计范围的情况下运行(注意观察脚部滑动/IK 偏移)。建议优先修正导入缩放。
验证
编辑后,用户应:
- 在 Unity 中重新加载——Unity 会自动导入 JSON。如果 JSON 格式错误,控制台会立即显示解析错误。
- 在 UI 中重新打开——在模型上选择
Assets/Movement SDK/Body Tracking/Open Retargeting Configuration Editor。如果缺少已知关节,或映射引用了不存在的关节,编辑器会静默回退到默认值,因此请留意警告并检查骨骼折叠栏。 - 在运行时抽查——将角色放入带有
CharacterRetargeter组件的场景中(或使用现有的MovementBody测试场景),并确认肢体跟踪正确。然后在头显上使用实时身体跟踪进行验证:部署测试场景,并在移动时运行metavr adb logcat --follow --tag Unity,查看重定向警告。
运行时可以容忍 JSON 中存在额外的未识别字段,因此新增字段是安全的;危险的修改是删除和重命名。始终交叉检查各个部分中的关节名称是否一致(重命名前后使用 grep -c "<oldName>" 确认所有位置均已更新)。
示例工作流配方
“角色在 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 这样的名称。
“缩小后身体部位发生倾斜/剪切(Min T-pose 时最严重)” → 制作的骨架尺寸过大(通常是 Mixamo 的 mixamorig: FBX)。检查 JSON 中未缩放的 T-pose 高度;如果约为人类身高的 2 倍(例如 3.7 m),所需的根节点缩放将低于 (0.8, 1.2) 限制。将 FBX 导入的 Scale Factor 设置为 targetHeight / currentHeight(例如 0.46),并将 Animation Type 设置为 Humanoid,然后执行 SaveAndReimport(),再重新运行 RunDefaultRetargetingSetup。请参阅上文“修复缩小后发生倾斜的模型”。
编辑后重新运行设置
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-pose 调整,都应在任何最终的 RunDefaultRetargetingSetup 之后最后进行。对于已知关节和排除映射项的编辑,重新运行没有问题。