Skip to content

ADR-129: 动作菜单场景级重设计(Scene-level Motion UI)

状态: 已完成—最终实现偏离设计 日期: 2026-07-18 依赖: ADR-121(全局动作意图,已实施 P0+P1+P2)

背景与问题

ADR-121 在后端实现了「场景级 activeMotion + per-model 继承/覆盖」,但前端动作菜单(menus/motion-popup.ts)的根层结构仍是 per-model 范式

旧根层结构:
  Card 1: 已加载模型列表(actor 行 → 点进去是该模型的动作绑定面板)
    ├─ 初音未来 [当前 VMD名]     → action:binding:{id}
    ├─ 雷电芽衣 [当前 VMD名]     → action:binding:{id}
    └─ ...
  Card 2: 相机 / 音乐库 / 姿势工作室 / 播放速度 / ...
  Card 3: 程序化动作 / 注视 / 高级设置 / 外部导入

核心矛盾

维度后端(ADR-121)前端(旧)
语义「场上在跳什么」——场景级意图「给哪个角色上什么动作」——per-model
选动作setActiveMotion(intent) 全局生效必须先选模型 → 进入该模型绑定面板 → 再选 VMD
换角色新模型自动继承全局动作用户感知不到,需手动进每个模型面板重选
Pin 覆盖mode: 'pinned' per-model 差异化藏在模型绑定面板的卡片 4,用户难以发现

用户反复换皮欣赏时,每次都要点进模型 → 选动作 → 返回,与 ADR-121「换角色无需重选」的设计初衷背道而驰。

竞品参考(DanceXR)

DanceXR 的动作菜单首屏:

1. 当前加载的动作(场景级)→ 点击可微调骨骼设计/动作覆盖
2. 程序化动作
3. 音乐库

动作是场景级一等公民,模型列表是次级管理界面。


决策:场景级动作菜单重设计

设计原则

  1. 动作是场景内容,不是模型属性——根层展示「场上在跳什么」
  2. per-model 管理下沉——pin/unpin/incompatible 移至模型绑定面板,不占根层空间
  3. 渐进式重构——分 Phase 落地,每 Phase 独立可交付

最终实现的根层结构(简化版,偏离原设计)

Card 1: 当前动作(场景级)
  ├─ [当前动作名] / 无动作提示  → 点击进详情页(图层管理 + 骨骼覆盖 + 速度)
  │   └─ trailing trash-2        → 清除动作(含 undo)
  ├─ [叠加层1] [100%]           → trailing 齿轮进图层设置
  ├─ [叠加层2] [50%]            → trailing 齿轮进图层设置
  ├─ 浏览动作库                  → VMD 文件浏览器(stay 模式,连续预览)
  └─ 程序化动作                  → 子页
  ——— 分割线 ———
Card 2: 场景工具(扁平列表,无分组标题)
  ├─ 相机                        → 子页
  ├─ 音乐库 / 浏览音乐           → 文件浏览器
  ├─ [移除音乐](有音乐时显示)  → 清除音乐
  ├─ 姿势工作室                  → 子页
  ├─ 注视追踪                    → 子页
  └─ 外部动作导入(有模型时)    → 子页(Mixamo/VRM/自定义)

与原设计的关键偏离

原设计最终实现偏离原因
Card 2: per-model 角色状态行(跟随全局/固定/不兼容)移除,per-model 管理下沉至模型绑定面板根层信息过载,用户更关心「选动作」而非「看状态」;pin/unpin 仍可通过模型面板访问
Card 3: 3 组 sectionTitle(播放与同步/角色与环境/系统与导入)取消分组,扁平化为单一列表sectionTitle 增加视觉噪音,7 项工具无需三级分组;用户通过图标快速定位
播放状态行(播放/暂停/帧进度/循环)移除,未在菜单内实现播放控制由底部播放栏承担,避免双源冲突;菜单 focus 操作而非状态监控
骨骼覆盖/脚部调整/虚拟裙骨骨骼覆盖移至动作详情页;脚部/裙骨下沉至模型详情页「故障排除」功能低频,不应占根层空间
高级菜单(收纳低频功能)移除,相关功能已重新分配到各详情页嵌套菜单增加操作路径深度,不必要

关键交互变更

操作旧流程新流程
加载动作选模型 → 进绑定面板 → 浏览 VMD → 选文件根层「浏览动作库」→ 选文件(stay 模式连续预览)→ 全局广播
清除动作选模型 → 进绑定面板 → 点清除根层 current motion 行 trailing trash 按钮 / 详情页清除按钮
换角色不换动作每次换模型后重新选自动继承(ADR-121 已实现)
Pin 独立动作进模型绑定面板 → 卡片 4 pin 按钮进模型绑定面板 → 卡片 2 pin/unpin 按钮
叠加层管理进模型绑定面板 → 图层列表根层内联显示叠加层列表 → 点击进图层设置
骨骼覆盖通过「高级」菜单 → 骨骼覆盖动作详情页 → 骨骼覆盖入口
播放控制动作详情页内 play/pause/loop/进度条底部播放栏(独立于菜单)

实施过程

Phase 1:根层结构重排

状态:已落地(实际实现简化了原设计的 Card 2 和 Card 3)

  • 根层从「模型列表优先」改为「当前动作优先」
  • 新增 buildMotionRootItems() 重新排列卡片顺序
  • 新增 buildMotionDetailLevel() 动作详情页
  • 新增 buildCurrentMotionLabel() 统一显示 VMD/程序化动作/无动作
  • 新增 __scene_motion_browse__ 场景级浏览入口(mode: 'stay' 连续预览)
  • buildActionBindingSchema() 精简为仅保留 per-model 专属功能(姿势库、pin/unpin、物理开关)

Phase 2:动作详情页整合

状态:已落地

  • 动作详情页包含:当前动作名 + 清除按钮、场景级图层管理(叠加层列表)、骨骼覆盖入口、播放速度滑块
  • 播放控制(play/pause/loop/进度条)未实现——由底部播放栏承担
  • 模型面板不再包含图层管理

Phase 3:per-model 差异化增强

状态:已落地(简化为 _buildActorSublabel 函数)

  • 模型绑定面板的 _buildActorSublabel 显示:跟随全局 / 固定: [动作名] / 不兼容
  • pin/unpin 操作在模型绑定面板内完成
  • 未实现根层模型行的快捷操作(原设计根层有模型行,实际实现已移除)

不变的部分

模块不动原因
scene/motion/motion-intent.ts后端逻辑完备,本 ADR 仅改 UI 层
scene/motion/playback.ts / vmd-loader.ts / vmd-layers.ts播放链路不变
ADR-116 动作覆盖模块per-model 覆盖层与全局意图正交
ADR-121 广播逻辑已正确实现,UI 只是换个入口调用 setActiveMotion

设计偏离总结

原设计项实现状态备注
Card 1: 当前动作(场景级)✅ 已实现含内联图层列表 + trailing 清除
Card 1: 播放状态行❌ 未实现由底部播放栏替代
Card 2: per-model 角色状态行❌ 删除信息过载,下沉至模型绑定面板
Card 3: 3 组 sectionTitle 分组❌ 删除扁平化,7 项工具无需分组
骨骼覆盖在根层可见⚠️ 移至动作详情页路径更长但低频功能合理
脚部调整/虚拟裙骨在根层可见❌ 下沉至模型详情页故障排除折叠组内
播放控制(play/pause/loop/进度条)❌ 未实现底部播放栏承担
动作详情页图层管理✅ 已实现场景级 active.vmdLayers
模型绑定面板 pin/unpin✅ 已实现使用 ModelMotionSlots 双槽位
外部动作导入✅ 已实现根层 → retarget 子页

后续迭代方向

  • 动作预设组:「演唱会包」一键设 activeMotion + 给特定角色 pin 独舞(ADR-121 已规划)
  • 批量 pin:多选模型统一指派动作
  • 动作搜索:VMD 库支持按名称/标签搜索
  • 动作预览:选 VMD 时实时预览骨骼动画(需性能评估)