VRM 引导移动设计计划
目标
为 NEKO 增加桌宠式“目标选择→自动移动”功能,首要适配两类模型:
- 用户的“人物模型”(当前已知为 VRM 0.x,约 51 个 humanoid 骨骼;表情绑定较少)。
- YUI 使用的 VRM 模型(以运行时实际加载的文件为准,不把模型文件名或路径写死)。
两套模型都必须通过同一套交互和动作流程;模型差异通过 VRM 能力探测、朝向校准和动作兼容性结果处理。
已确定的操作规则
- 按住
F:进入目标选择模式。 F模式下左键点击一次:选定一个目标位置并开始移动。- 松开左键:移动继续。
- 松开
F:只退出目标选择模式,已经选定的移动继续执行。 - 再次按住
F并左键点击:替换目标,角色从当前位置转向新目标。 - 到达目标:停止移动并恢复适用的待机动作。
- 普通模式下的左键拖拽、右键旋转和滚轮缩放保持原有行为。
目标选择状态和移动状态必须分开维护。释放 F 不得清除 moveTarget。
当前实现基础
static/vrm/vrm-interaction.js已有 VRM 射线命中、拖拽平移、边界限制和每帧更新入口。static/vrm/vrm-manager.js已能访问当前 VRM,并提供位置设置接口。static/vrm/vrm-animation.js已能加载和混合 VRMA。static/vrm/motion/player.js已有动作播放所有权和动作生命周期管理。- 当前代码没有目标移动控制器、可行走场景地图或完整的走路动作状态机。
已执行部分(当前代码)
F按下进入目标模式,画布左键一次选定屏幕平面目标;松开F不会取消已确认的移动。- 目标由相机反投影并经过现有可见边界限制,移动在
update(delta)中按帧推进,抵达后吸附到目标并保存位置。 - 移动中再次使用
F+ 左键会替换目标;普通左键命中模型后拖拽会接管并取消自动移动。 - 移动开始尝试播放
world-walk.vrma.gz,移动结束停止该动作并交还NekoMotion待机所有权;锁定、清理和模型销毁路径会释放动作占用。 - 当前只播放循环
world-walk,尚未接入起步、停止和转向 clip 状态机;动作加载或骨骼不兼容时回退到纯位移。没有完整NekoMotion动作恢复运行时的预览页面只移动位置,保留原动作。到达转身使用共享帧调度,转身途中换目标沿用原恢复朝向。两个目标模型的兼容性仍需实测。
状态机
text
普通模式
└─ keydown F → 目标选择模式
目标选择模式
├─ 左键点击 → 保存目标 → 移动中
└─ keyup F → 普通模式(移动不取消)
移动中
├─ F + 左键点击 → 替换目标
├─ 普通左键拖拽 → 用户接管并取消自动移动
├─ 到达阈值 → 停止、恢复待机
└─ 切换/锁定/销毁 → 安全取消建议的运行时字段:
js
targetMode // F 是否按住
moveTarget // 当前目标的世界坐标
isMoving // 是否正在自动移动
movementToken // 使旧目标或旧动画回调失效
movementMaxSpeed // 最大世界单位/秒,范围 0~0.9
movementVelocity // 当前速度,按加速度/刹车动态变化
movementFacing // 是否由移动方向驱动身体朝向目标坐标方案
第一阶段不引入 Hanami 的房间、座位和寻路系统,使用当前桌宠画布的屏幕平面:
- 从画布事件取得点击坐标。
- 用相机反投影得到与屏幕平行的移动平面目标。
- 以模型脚底参考点对齐目标,避免“点哪里就把身体中心挪到哪里”。
- 通过现有边界函数限制可达区域。
- 记录目标一次;鼠标后续移动不改变目标。
屏幕平面允许上下移动,因此不能把世界坐标的脚底高度固定为常数。以后加入真实地面时,再改为地面射线求交和可行走地图。
移动控制
- 在 VRM 管理器已有的每帧更新中按
delta推进位置,保证帧率无关;速度从 0 按加速度提升到最大值,接近目标时按刹车距离减速到 0,不使用固定速度。 - 目标距离小于到达阈值时吸附并结束,避免抖动和越过目标。
- 新目标从当前位置重新计算方向,不瞬移。
- 移动期间让身体朝向移动方向;当移动结束时恢复当前模型的朝向策略。
- 位置在新行程开始、到达结束及模型切换/销毁打断移动时保存;途中目标替换不追加中间快照,避免连续点选堆积保存队列。模型切换前同步捕获旧模型最终姿态,异步保存仍归属旧模型,不能每帧写配置。
- 页面失焦时清除
targetMode,保留并继续执行已确认的移动;模型切换、锁定和销毁时取消移动。
动作资源
已导入 static/vrm/animation/ 的 Hanami/Overte 移动资源:
world-walk.vrma.gz
来源和许可记录在 static/vrm/animation/HANAMI_VRMA_NOTICE.md。该动作保持在移动域,不加入普通聊天动作候选。Hanami 的目录说明 world- 域包含起步、步行、停止和转向动作;动作文件为 VRMA humanoid clip,模型无须专门绑定即可尝试播放,但仍必须对两个目标模型实测。
后续完整状态机的动作播放策略(尚未接入)
当前仓库只保留 world-walk.vrma.gz。下列起步、停止和转向资源已移除,后续实现前需重新导入并记录许可,再完成模型兼容性验证。
text
开始移动 → world-walk-start
开始持续移动 → world-walk
接近目标 → world-walk-stop-small(适用时)
正常停止 → world-walk-stop
方向变化 → world-turn-left / world-turn-right(需要时)
到达后 → 当前模型可用的 rest/idle实现前必须检查:
- 两个模型能否匹配 hips、腿、脚和躯干轨道。
- VRM 0.x 与 VRM 1.0 的归一化骨骼处理是否一致。
world-walk是否循环自然、是否含 hip translation;若动作本身含位移,必须决定由动画还是控制器负责根位移,不能双重移动。- 起步、循环、停止的相位衔接是否出现滑步、脚底穿透或突然跳姿势。
- 两个模型的朝向基准是否相反,并分别记录校准值。
两个适配目标的专门验收
人物模型
- 确认 VRM 0.x humanoid 映射完整性,重点检查 hips、left/rightUpperLeg、left/rightLowerLeg、left/rightFoot、spine、chest。
- 由于表情绑定较少,移动验收重点放在身体动作、脚底接触、朝向和待机恢复;不把表情表现当作移动成功条件。
- 检查当前模型的缩放、脚底高度和屏幕边界校准。
YUI VRM
- 从 YUI 当前加载实例读取 VRM 元数据和 humanoid 映射,不假设与人物模型同版本或同朝向。
- 单独记录模型原点、脚底高度、默认朝向、可匹配骨骼数和动作首尾姿态。
- 验证 YUI 现有 idle、表情、口型和 cursor-follow 是否会覆盖移动动作的骨骼或姿态。
两个模型必须分别完成“静止播放、短距离移动、长距离移动、目标替换、停止恢复、普通拖拽接管”六组测试,不能只用其中一个模型通过后推定另一个兼容。
输入和窗口穿透
- 仅在画布或桌宠窗口拥有焦点时处理
F,输入框、文本框和 IME composing 状态不触发。 - 目标模式开启时允许点击画布空白区域;退出目标模式后恢复原有鼠标穿透策略。
- UI 按钮、聊天区域、右键和滚轮不应改变移动目标。
- 监听器必须在 VRM 初始化、模型切换和销毁时成对注册/清理,防止重复触发和 F 键卡住。
实施顺序
- 增加
F目标模式和点击目标记录,不播放动作。(已完成) - 增加屏幕平面目标计算、平滑位移、边界限制和到达阈值。(已完成)
- 接入普通拖拽抢占、锁定、失焦、切换和销毁清理。(已完成)
- 在两个模型上做骨骼轨道和脚底兼容性探测。
- 接入
world-walk-start → world-walk → world-walk-stop,处理动作所有权和待机恢复。(仅循环走路已接入;起步和停止仍待实现及模型实测) - 增加转向和目标替换处理。
- 分别在 48911 页面和 Electron 桌宠窗口验证穿透、焦点和性能。
- 运行与交互模块和动作播放器相关的最小测试,并记录两个模型的验收结果。
完成标准
- F 只控制“能否继续选目标”,不会取消已开始的移动。
- 两个目标模型都能选点、移动、改目标、到达和恢复待机。
- 移动速度与帧率无关,停止不抖动、不漂移、不双重位移。
- 走路动作与位移方向一致,脚底和身体不出现明显滑步。
- 普通拖拽、旋转、缩放、口型、表情、待机和 YUI 现有交互无回归。
- 锁定、失焦、切换模型和销毁后没有残留移动、动作或事件监听器。
