项目演示,4 分 45 秒,带中文字幕和配音:从导入录像到导出,接着在 Unity 6 和 Godot 4 里实际使用导出的脚本。工具画面是真实程序;跟踪和拟合做了快进,框选和按键由脚本执行。引擎里的运行画面按固定帧率逐帧录制,两个引擎用的是同一串按键。配音为 AI 语音合成(高松灯社区语音模型,CC BY-NC-SA 4.0)。
概览
平台跳跃游戏的手感,由最高速度、加速度、起跳速度、上升和下落重力、松键截断、空中控制这十来个参数决定。想做出“像某款游戏”的手感,通常只能对着录像一遍遍手调。这个工具把这一步自动化:给它几段录像,它找出能同时复现所有片段的最简单控制器,可以马上用键盘试玩,再导出成 Unity 或 Godot 脚本。
它不还原原游戏的代码。它找的是一个行为相似、结构简单、参数可读、能直接运行的控制器,并且会说明匹配得多好,以及哪些参数录像确定不了。
references/real/SOURCES.md。SuperTux 关卡录像由 Dabmasterars 和 Dexxor 录制,SuperTux © SuperTux Development Team(GPL)。一条管线
|

所有长度都以角色身高为单位(每段视频用跟踪框高度的中位数作为一个比例尺),导出时再填入角色在引擎里的身高,把长度参数换算过去。
控制器模型:从简单到复杂
工具准备了五级模型,每一级在上一级的基础上加机制:
| 模型 | 增加的机制 | 参数数 | SuperTux 上的轨迹误差 |
|---|---|---|---|
| A | 恒定重力,瞬间达到最高速度 | 3 | 9.1% |
| B | 地面加速 / 减速 | 5 | 8.3% |
| C | 上升和下落用不同的重力 | 6 | 9.6% |
| D | 松开跳跃键时截断上升速度,空中控制 | 8 | 2.0% |
| D2 | 改为松键后加大重力(马里奥式可变跳跃),空中控制 | 8 | 2.0% |
| E | D 再加顶点附近的重力倍率、最大下落速度和转身加速度 | 11 | 2.7% |
选择规则是“够用的最简单模型”。 每个模型按有效样本量计算 BIC(用残差的自相关修正样本数,误差下限设在跟踪噪声的水平),再加一条实用规则:误差不超过最好模型的 1.1 倍再加 0.2 个百分点,就算“一样好”,从中选最简单的。
只靠 BIC 不够:一段片段有几百帧,BIC 会奖励那些只是在吸收跟踪偏差的参数。第一次端到端测试时,工具就因为 0.05% 的改进选了 E;加上实用规则后,才稳定地选回真实的 D。
诚实的报告

拟合完的报告会写出:
- 每项误差:轨迹、速度曲线、顶点时刻、落地时刻、最大高度、水平位移;
- 模型比较:更简单的模型为什么被拒绝,更复杂的模型为什么不值得;
- 置信度和理由:比如 SuperTux 只给到 MEDIUM,因为 D 和 D2 拟合得一样好(录像分不清原游戏用的是哪种可变跳跃),跳跃截断系数和未知的松键时刻互相牵制,减速度约束很弱。
录像里没有出现的动作,对应的参数会被固定并写明原因:没有一短一长两次跳跃,就定不了跳跃截断;空中没有改变过方向,就定不了空中控制。拟合后不确定度超过 100% 的修饰参数会被设回中性值再重拟合,避免导出一个手感奇怪的控制器。
解释不了的片段也会被标出来。SuperTux 速通录像里有一段看起来像小跳,工具报告它无法解释(落地时刻误差 140%)。检查后发现是 Tux 跑过了一个平滑的斜坡,而斜坡不在模型范围内,这段就被移除了,没有硬拟合。
从画面到运动轨迹
![]()
跟踪器和镜头估计的选择,都是在合成基准上实测后决定的。合成基准用一个已知参数的控制器渲染出 7 段视频,带镜头跟随、视差背景、HUD、挤压拉伸的角色和 H.264 压缩:
| 跟踪器 | 脚底位置平均误差 |
|---|---|
| 模板匹配(最终选用) | 0.41 px |
| 光流 | 0.54 px |
| CSRT | 1.07 px |
| MIL | 1.99 px(一次跳到 24 px) |
| KCF | 2.06 px |
真实录像暴露的问题比合成数据多,每一个都记录在 DEVLOG 里:
- 挤压拉伸:固定大小的跟踪框让脚底位置偏了最多 3.5 px,拟合用重力偏差去补偿,结果选错了模型。加入纵向缩放搜索后,误差从 0.41 降到 0.15 px,模型也选回了正确的 D。
- 镜头估计:合成录像开头镜头每帧平移 30 px,关键帧法因此出现 60–150 px 的错误。改成只在预期偏移小于 16 px 时使用关键帧,并和逐帧估计交叉检查,最大误差 0.21 px。
- 视差层:Secret Maryo Chronicles 里缓慢移动的背景山丘占据了大部分特征点,估出的镜头位移只有真实值的四成。改为优先参考角色脚下那一层。
- 15 fps 录像:一开始 89% 的帧跟丢。按帧率放大搜索范围,加上外观库和颜色直方图兜底,降到 5.9%。
推断按键

录像里看不到按键,只能从运动推断。用阈值判断时,SuperTux 一段 9 秒的录像被判出 70 次左右切换,因为掉帧和动画会让速度抖动。改成带切换惩罚的三状态 Viterbi 解码后,整段变成“一直按右”,合成数据上的按键时刻误差在 1–3 帧以内。
推断出的按键只是一个可信的解释,不是真实的按键,所以:
- 时间轴上可以右键增删、拖动修改,也可以锁定某段的按键,不让拟合器调整;
- 拟合时,每段片段的按键时刻也是优化变量(方向键 ±0.12 s,跳跃 ±0.05 s,加上松键时刻)。
拟合
所有片段共用一套控制器参数,每段片段有自己的按键时刻,一起用 scipy.optimize.least_squares 求解:带边界的信赖域方法,soft-L1 鲁棒损失,稀疏雅可比矩阵(一段片段的按键只影响它自己)。
- “够得着平台”残差:落地是离散事件,一条弧线如果顶点差一点没到平台,就得不到有用的梯度。对观测到落在高处的跳跃,额外加一项“顶点比平台低了多少”。
- 多起点:精确复现上一级模型的解(所以 D 不会比 C 差)、按跳跃高度排序的松键时刻初值,加几次随机扰动;最后用最好模型的参数把每个模型再拟合一遍。在这之前,SuperTux 上 D 的误差会因为数据的微小变化在 1.9%、5.7%、2.5% 之间跳动。
精确积分器
运动模型的积分是精确的:每一步里找出加速度下一次改变的时刻(达到目标速度、竖直速度过零、进入顶点区、达到最大下落速度、落地),分段用解析式积分。
这样结果不依赖步长:拟合器按视频帧采样,试玩原型按 60 Hz 运行,Unity 按 fixedDeltaTime 运行,三者都一致。轨迹对参数和按键时刻也是连续的,适合用梯度法求解。同样的约 60 行代码移植到了 C# 和 GDScript。
导出到 Unity 和 Godot

Unity C# 是一个完整的组件:
- Kinematic Rigidbody2D,用
Physics2D.BoxCast自己处理碰撞。每一步先自由移动,再处理墙(X),再从新的 X 位置探测地面或天花板;碰到地面就用那个高度重算这一步,让落地发生在精确的时刻; - 同时支持旧的 Input Manager 和 Input System;也可以由外部脚本输入,用于 AI、回放和测试;
- 可选的土狼时间和跳跃缓冲(默认关闭,以保证行为和拟合一致);
- 参数都是 Inspector 字段,也可以用导出的 JSON 在启动时覆盖。
验证方式:对 Unity 2022.3.51、2022.3.62、6000.3.8 的程序集编译,0 错误 0 警告;在 2022.3.62 和 6000.3.8 的批处理模式里,分别用两种输入后端跑“跑动 + 长跳 + 短跳”,x 和模型完全一致,y 误差 ≤ 0.005 单位,顶点相同。

Godot 4 GDScript:在 Godot 4.7.2 里无界面运行,没有脚本错误,跑动速度一致,顶点误差在 0.002 单位以内。不同的是落地发生在物理帧边界(move_and_slide),所以最多晚一帧,实测正好一帧。
结果
合成测试(真实参数已知)。 真实控制器是:最高速度 8,加速度 40,减速度 55,起跳速度 12,上升重力 38,下落重力 52,空中控制 0.55,跳跃截断 0.45。7 段渲染视频经过跟踪和拟合后:
- 选中模型 D;
- 8 个参数都在真实值的 5% 以内,大部分在 2% 以内;
- 在真实按键下,与真实控制器的行为误差为 0.05–0.44%;在拟合时没见过的按键序列上为 0.35–0.61%(目标 ≤ 5%)。
真实录像。
| 素材 | 模型与置信度 | 每段轨迹误差(目标 ≤ 8%) |
|---|---|---|
| SuperTux:两位玩家的 2 段录像,4 个片段 | D,MEDIUM | 1.4%、1.6%、1.9%、3.1% |
| Secret Maryo Chronicles:15 fps 录像,2 个片段 | B,HIGH | 2.6%、3.3% |
范围与局限
- 只支持 2D 横版。蹬墙跳、攀边、冲刺、二段跳、游泳、斜坡和移动平台都不在模型里,含有这些动作的片段会被标为 LOW(无法解释)。
- 没有关卡几何模型:工具知道角色落在了哪个高度,但不知道天花板和墙,所以选片段时要避开顶头和撞墙。
- 只有一个最高速度。有“走 / 跑”两档的游戏,要把走和跑的片段分开拟合。
- 可变跳跃实现了“松键截断速度”(D)和“松键加大重力”(D2)两种,“松键限制速度”和“按住维持计时”两种还没有,用 D / D2 近似。
- 自动识别角色不可靠,只是辅助按钮。手动框选是主要方式。
- 镜头补偿假设镜头只平移,不处理缩放、旋转和大幅震屏。