Roblox动画,无头模式
第一次使用本机? 阅读此文件夹中的
LOCAL.md。如果不存在,请运行roblox-skills-setup技能(或将LOCAL.template.md复制为LOCAL.md并填写)。LOCAL.md保存你的路径和工具位置;它是你的专属文件,永不共享。
将动画作为数据(一个随时间变化的姿势JSON规范)来撰写,将其烘焙到真正的 Blender 动作(Action)中,渲染它,然后查看结果,再相信它。
循环是:撰写 → 渲染每一帧 → 拼贴成联系表 → 查看 → 修正 → 重复。 这里的一切之所以存在,是因为这个循环的某个步骤捕获了其他所有检查都遗漏的故障。
先读这个:最浪费时间的陷阱
大多数Roblox骨骼在Blender中带有一个包含其静态姿势的动作(Action)。 Blender 在渲染时会重新评估该动作。所以,如果你设置了姿势值然后渲染:
- 每个数值探针都报告你的姿势已完美应用
- 每个渲染帧都显示骨骼的默认姿势
- 数据和图像永远无法 reconcile,并且没有报错
清除该动作也无济于事,因为那个动作就是静态姿势——清除它,肢体就会塌陷到躯干里。
解决方法:在该相同动作中插入关键帧,然后每帧使用 frame_set() 并渲染。这样,动画就是这个动作,没有东西可以覆盖它,未接触的骨骼通过恒定外推保持其静态值,你最终得到一个真正的动作——这也是Roblox导出所需要的。scripts/anim_render.py 已经做了这件事。
完整列表见 references/traps.md——共28个。在调试任何东西之前请阅读它。
使用FK,而不是IK
一个Roblox KeyframeSequence 是一个按关节旋转的树,每个 Motor6D 对应一个。这是FK。每个R15骨骼为每个关节配备一个控制骨以匹配。如果使用IK目标驱动角色,就是在一个Roblox从未要求的解算器中进行往返,并且要为此付出代价:
- 仅位置IK目标会让肢体ROLL自由,所以块状的Roblox部件会呈现解算器选择的任意面,并在帧之间跳动。在有纹理的化身上,这表现为颜色瞬间变化——这是使动画看起来有问题的症状
- 解算器在帧之间在同等好的分支之间跳跃,所以肢体会在每一帧仍然完美验证的情况下跳动
- 速度慢
FK没有分支,没有奇点,没有旋转模糊性,在角度上是完全1:1的,并且可以直接导出到Roblox。
大多数骨骼以IK模式加载,这使每个 FK_* 骨看起来是惰性的。切换开关——不要断定FK已死:
"properties": {"ARM_IK_FK.L": 0.0, "ARM_IK_FK.R": 0.0,
"LEG_IK_FK.L": 0.0, "LEG_IK_FK.R": 0.0}
在一个免费的社区R15骨骼上测量,并且令人愉快地统一:每个肢体在休息时都指向正下方;在每个 FK_* 骨上,X负方向向前摆动,Z正方向向角色右侧摆动,角度1:1,两侧相同——没有镜像反转。子角度相对于父级。
无论使用什么骨骼,一般的教训是:如果一个控制骨是惰性的,在放弃之前寻找一个禁用它开关。 类Rigify的R15骨骼带有默认值为 1.0(IK)的 ARM_IK_FK / LEG_IK_FK 自定义属性。将它们设置为 0.0,关键帧(这样 frame_set 就不能把它们翻转回来),然后在FK中撰写。上述方向约定是在一个骨骼上测量的;calibrate_rig.py 和 sweep_control.py 会在你的骨骼上重新测量它们,所以在你没有使用过的任何骨骼上运行它们。你的骨骼的 .blend 文件路径放在 LOCAL.md 中。
仅当一个肢体必须击中一个确切的世界点时,才使用IK。然后阅读陷阱12-15。
让它看起来像Roblox,而不是像参考
- 30 FPS,一次战斗动作持续 0.3–0.7秒。动画编辑器的默认值是30fps。现实世界的时间对于游戏来说太慢了。从动作的真正工作原理中获取力学;从Roblox中获取时间。
- 五个姿势:待机、准备、接触、跟随、恢复。
- 每段缓动——
"ease": "in"表示加速进入打击,"out"表示稳定下来。一个固定的平滑步会减速到每个关键帧,所以打击会在其自身的接触帧处缓动停止,整个动作读起来像糊状。 - 夸张。 真实的Roblox战斗动画远超人类活动范围,并且通过剪影来阅读。
- 渲染为纯灰色(
"plain": true)。一个繁忙的化身纹理会把一个块状骨骼变成一堆无法辨认的混乱,你最终会调试服装而不是姿势。正是因为这个原因,真实的Roblox动画是在一个普通假人上展示的。
references/pose-recipes.md 有实际数值——为防御、准备、蓄力、接触和跟随姿势测量的角度表,时间骨架,以及两个最常见的错误规则(肩膀+肘部总共必须保持在~120以下;高踢需要 UpperLeg X 超过-120,否则腿会与躯干合并)。从这些开始,并根据预览进行调整。不要重新推导它们。
要快——测量,而不是猜测
整个循环是秒级的。如果一轮需要几分钟,那就出问题了。在一台中端PC上测量,一个24-36帧的动画:
| 阶段 | 时间 | 说明 |
|---|---|---|
| Blender加载骨骼 | 0.7s | 启动从来不是问题 |
| 数值探针(整个FK控制图) | 0.1s | 用数字向骨骼提问 |
预览渲染("preview": true) | 4–6.5s | 1个视图,320x240,1个样本 |
| 完整渲染,2个视图 @ 640x560 | 9–17s | 只做一次,在最后 |
| GIF编码 | 0.3s | |
to_roblox.py → Luau | 0.1s | |
| 在Studio中构建并验证 | 一次 execute_luau 调用 |
机器时间,对于一个完成并在Studio中的动画:远低于一分钟。 实际花费时间的是撰写迭代,所以把它们花在预览上,绝不要在完整渲染上。从 references/pose-recipes.md 开始,第二个动作应该在1-3次预览内完成——references/ 中的拳击和挥手都是第一次就成功了。
"report": true 打印每个关节在每个节拍的位置:当一个姿势看起来不对劲时,它会在一次运行中命名关节,而不是五次渲染传递的猜测。
不要为了回答一个探针就能回答的问题而进行渲染。
不可妥协项
每一项之所以存在,是因为跳过它会导致一些看起来没问题但实际上有缺陷的东西被发布:
- 永远不要信任一个退出码为0的运行。
anim_render.py会逐帧断言骨骼完整性,断言帧已写入,否则硬退出并返回非零。 - 查看帧。 每次渲染都要看。
- 如果有参考,构建并排比较并以此为依据进行判断——永远不要只看渲染结果。
- 检查完成的GIF本身,而不是你用来编码它的PNG。
- 永远不要为了把红灯变成绿灯而放宽公差。 修正规范。
工作流程
1. 校准骨骼(任何你未使用过的骨骼)
blender --background <rig>.blend --enable-autoexec --python scripts/calibrate_rig.py
blender --background <rig>.blend --enable-autoexec --python scripts/sweep_control.py -- FK_UpperLeg.R:rot TORSO:rot
calibrate_rig.py 报告变形骨架与控制骨架、动作陷阱、IK/FK切换状态以及哪些控件实际移动了某些东西。sweep_control.py 给出每个轴的方向、符号和可用范围——它测量骨骼的方向以及位置,所以不会遗漏仅旋转的控件。
如果 FK_* 骨读起来是惰性的,那是IK/FK切换的问题,而不是骨骼已死。
2. 撰写规范
fps、duration、w、h、margin、views、properties、plain、preview、report 和 keyframes[] 的 {t, ease, bones: {Bone: {rot|loc|target|aim}}}。
rot 是你几乎总是需要的。loc 用于没有末端效应器的控件(ROOT)。target / aim 是IK的逃生舱口——见陷阱12-15。
三件如果忘记就每次都会出错的事情:
- FK子角度与父级叠加。 肩膀50° + 肘部70° 使前臂从垂直方向倾斜120°,指向肩部上方。对于防御姿势,保持肩膀+肘部在~110°以下。
- 倾斜和扭转放在
UpTorso上,它只驱动上半身。TORSO也驱动腿部,所以倾斜它会使人物倾倒并滑移着地的脚。骨骼还会放大:X = +15会使头部摆动23°。 - 反向旋转支撑脚踝,
FK_Foot.X ≈ -(FK_UpperLeg.X + FK_LowerLeg.X),否则脚会随着腿倾斜,角色看起来像是站在斜坡上。
参见 references/example-fk-roundhouse.json(0.8秒踢)和 references/example-fk-punch.json(0.5秒直拳),以及 references/pose-recipes.md。
2a. 如果手中持有道具,首先要研究它实际上是如何被握持的。
这一步在撰写关键帧之前进行,并且不是可选的。 一旦片段中包含武器或工具,“姿势是否可读”就不再是关于骨骼的问题,而是关于该对象的问题,这是一个关于世界的事实,而不是可以从矩阵推断出来的东西。
这个规则存在是为了防止的一个真实构建故障:一把长柄刀被撰写为刀刃向下,放在使用者臀部。每个下游阶段都完美地完成了它的工作——引擎复现了撰写的握持方式,姿势条与联系表帧对帧匹配,握持误差为0.000,武器没有穿透身体的样本——游戏发布了一把倒着拿的长柄武器。在这里撰写错误的姿势,对之后的所有检查都是不可见的,因为之后的所有检查都是测量与这个姿势的一致性。 当一个人最终查看时,结论是它拿反了。他们是对的,而代价是在这里十秒钟决定的一个决策,导致下游整个会话的浪费。
在撰写之前:
- 搜索该武器类别是如何被携带和使用的。 使用练习者的照片,而不是概念艺术,也不是其他游戏。十秒钟的阅读胜过四轮猜测数字。
- 将答案作为
_intent写入规范,作为关于武器的一句话:“头部向上,杆身越过前肩”,“刀刃从放低的手向上向前倾斜”。这句话是判断联系表的依据——参见步骤4。 - 在搜索之前,先对照下面的列表检查那些明显的。
| 类别 | 静止时 | 挥动时 |
|---|---|---|
| 长柄武器、长柄刀、薙刀、矛 | 头部向上——扛在肩上、尾部杵地、或处于刀刃在胸部高度或以上的防御姿势。绝不垂在脚边 | 头部引导弧线;杆身在身体前方交叉,绝不在与手臂相同的垂直平面内 |
| 剑 | 从放低的手向上向前倾斜,大约+35度。垂直向下读作掉落 | 刃领先;手腕不是铰链,整个手臂带动弧线 |
| 斧、锤 | 头部向上或扛在肩上,重量保持在高处 | 头部是质量——它最后到达,并且它最猛烈地到达 |
| 拳套 | 佩戴,而非握持;拳击手防御姿势,双拳向上 | 臀部驱动它,而不是肩膀 |
以使用者自身身体能够做到的姿势握住道具。 单手携带任何比角色身高还长的东西,不能与手臂在同一平面内,否则会穿模。
3. 渲染并验证
blender --background <rig>.blend --enable-autoexec --python scripts/anim_render.py -- spec.json out/
ffmpeg -start_number 0 -i "out/side_%03d.png" -vf "scale=240:-1,tile=6x6" sheet.png
相机根据整个动画的边界自动取景,并计算其与视野的距离,因此没有什么会离开画面;margin(默认1.10)是唯一的旋钮。渲染至少两个角度——右腿踢击从左面是看不见的。
然后根据参考,而不是根据自身来判断联系表。 打开步骤3a中的 _intent 句子,逐帧检查联系表:头部是否向上,刀刃是否领先,杆身是否在身体前方交叉。联系表只能证明渲染结果与规范匹配。它无法告诉你规范是否正确,并且所有下游阶段都会将其视为事实——所以这是管道中最后一个纠正错误姿势成本低廉的地方。如果联系表和参考不一致,参考获胜,并且规范需要更改。
4. 交付GIF,然后检查GIF
ffmpeg -framerate 30 -start_number 0 -i "out/side_%03d.png" -framerate 30 -start_number 0 -i "out/tq_%03d.png" -filter_complex "[0]scale=420:-1[a];[1]scale=420:-1[b];[a][b]hstack=2,split[s0][s1];[s0]palettegen=stats_mode=diff[p];[s1][p]paletteuse=dither=bayer:bayer_scale=3" -loop 0 anim.gif
以你需要的大小读取联系表一次,不要更大。 联系表是这个管道中最昂贵的对象:一张图片在同一个上下文中每次后续工具调用时都会被重新发送,所以早期读取全分辨率联系表,到运行结束时,会花费成千上万的 token。将其拼贴缩小,当问题涉及特定肢体时裁剪到该肢体,并且不要为了重新检查你可以测量的东西而重新打开它。当一个检查可以是数字——武器是否离手,是否穿过肢体,它处于什么角度——就把它变成一个数字并阅读一行文本。
然后从完成的GIF中提取帧并查看它们——运动与静态帧的阅读方式不同,并且编码设置会无声地失败:
ffmpeg -i anim.gif -vf "select='not(mod(n,2))',tile=4x3" -vsync 0 verify.png
你撰写的片段会破坏你没有撰写的片段
Roblox的默认 Animate 脚本以 Core 优先级播放待机、行走、跑步、跳跃、掉落和攀爬动画。你以 Idle 或更高优先级撰写的任何内容都会在它摆姿势的每个关节上掩盖它们,只要它正在播放。
所以,一个全身待机循环——解决角色拿着武器站在那里问题的正确方法——会阻止角色行走时腿移动,因为你的片段以比行走循环更高的优先级在摆腿的姿势。这看起来完全像“行走动画丢失了”,并且它只出现在你撰写了待机动画的角色上,这使得它读起来像
…
[为翻译截断]