mcpField
H
hz-unity-meta-movement-sdk-retargeting

hz-unity-meta-movement-sdk-retargeting

github.com/meta-quest开发241147 次安装5 周前更新
代码仓库

关于 hz-unity-meta-movement-sdk-retargeting

此技能指导在 Unity 中为 Meta Movement SDK 角色生成和调整重定向配置。它优先调用原生编辑器 API 创建初始 JSON,再按需修改关节映射、权重、行为和 T-pose 数据。

主要功能

  1. 01调用编辑器 API 生成默认配置
  2. 02检查并修正目标关节绑定
  3. 03排除关节或调整映射权重
  4. 04修改映射行为与 T-pose 数值
  5. 05说明重命名关节时需同步更新的位置

适用场景

  1. 01修正自动检测错误的骨骼绑定
  2. 02减少手掌或扭转关节造成的映射干扰
  3. 03调整角色落地高度或关节旋转
  4. 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 数据,手动编写这些内容会很繁琐。正确的流程是:

  1. 生成默认配置:在运行中的 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(极少需要编辑)
  2. 检查生成的 JSON,确认关节映射正确。
  3. 仅在需要时手动编辑——本指南的其余部分将介绍 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 偏移)。建议优先修正导入缩放。

验证

编辑后,用户应:

  1. 在 Unity 中重新加载——Unity 会自动导入 JSON。如果 JSON 格式错误,控制台会立即显示解析错误。
  2. 在 UI 中重新打开——在模型上选择 Assets/Movement SDK/Body Tracking/Open Retargeting Configuration Editor。如果缺少已知关节,或映射引用了不存在的关节,编辑器会静默回退到默认值,因此请留意警告并检查骨骼折叠栏。
  3. 在运行时抽查——将角色放入带有 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 之后最后进行。对于已知关节和排除映射项的编辑,重新运行没有问题。

评论

支持 Markdown