动效验证 · 序列帧与木偶动画
回答"这种风格的小动物能不能做行走动画"。做了两版,第一版不行,第二版行—— 过程和数据都在这里。
下面所有播放器都能暂停、逐帧、调速。判断动画质量靠逐帧看,不是看一遍。
一、第一版:生图序列帧(不够好)#
最直接的做法是让生图直接出一张 6 帧的精灵表。
1.1 实测:帧间位置是不一致的#
把 6 帧逐个量内容包围盒:
| 实测 | 后果 | |
|---|---|---|
| 帧宽差 | 14px | — |
| 帧高差 | 35px | — |
| 内容中心横向漂移 | 48px | 角色在帧间乱跳 |
| 内容中心纵向漂移 | 86px | 同上 |
那 48×86px 不是动画,是噪音。用 tools/art/spritesheet.py --align feet
(把每帧底边中点对齐到同一条地平线)处理后,横向归零、纵向剩 18px。
漂移能修,但下面两个问题修不了。
1.2 修不了的两个问题#
- 帧间形变。模型理解"走路循环"没问题,但不理解"角色必须是同一个"。 鸡冠、翅膀的形状每帧都在变,看起来像在融化而不是在动。
- 帧数不够。6 帧做不出细节。而且腿和脚是一整块——绕髋一转,脚就跟着翘, 永远不像踩在地上。
靠改提示词解决不了帧间形变。这是生图做角色动画的固有上限,不是参数没调好。
二、第二版:木偶动画#
换做法:生图只出零件(身体、头、翅膀、大腿、脚、尾巴分开), 然后用代码装配、用曲线驱动。
同一套帧的自动播放版(纯动态 WebP,不需要 JS,可以直接丢进游戏):

2.1 数据对比#
| 第一版(生图序列帧) | 第二版(木偶动画) | |
|---|---|---|
| 帧数 | 6 | 16 |
| 中心横向漂移 | 48px | 1px |
| 帧间形变 | 每帧都在变 | 0(同一套零件) |
| 踩地的脚 | 跟着身体一起上下动 | 恒在 y=334,一动不动 |
| 抬脚高度 | 1~2px | 32px(画布的 8%) |
| 制作方式 | 生图一张 | 生图零件 + 代码驱动 |
2.2 让动画"像走路"的四个修正#
这四个都不是"调参数",每一个都是结构问题,缺一个就不像。
① 腿拆成两根骨头#
第一版腿和脚是一整块,绕髋旋转时脚跟着翘。拆成 大腿 + 脚掌 两根骨头:
髋(hip) ──大腿绕它转──> 膝(knee) ──脚接在膝上,但保持水平
脚掌角度只有大腿的 0.12 倍,所以踩地的脚基本是平的。
② 侧面视角的腿位#
第一版把两条腿摆在相隔 42px 的位置——那是正面视角的摆法。 但小鸡是侧面的(喙朝右),侧视角下两条腿应该在几乎同一个 x 上前后重叠。 改成相隔 16px(只留一点深度差)之后,迈步才读得出来。
③ 地面锁定(这是分水岭)#
大腿绕髋转 a 度之后,垂直方向的长度从 L 变成 L·cos(a)。 40 度时只剩 76%——腿一摆开脚就往上翘,看起来像在踮脚而不是走路。
所以髋要往下沉 L - L·cos(a),把踩地的脚钉回地面。加上这一项之后:
脚底高度 = base + L - 抬脚量 + 脚高 ← 只跟抬脚量有关
实测结果:
| 帧 | 左腿角 | 左脚底 y | 右腿角 | 右脚底 y |
|---|---|---|---|---|
| 0(过渡) | 0° | 334 | 0° | 302 ← 抬起 32px |
| 4(最大跨步) | −40° | 334 | 40° | 334 |
| 8(过渡) | 0° | 302 ← 抬起 | 0° | 334 |
踩地的脚恒在 y=334,一步都不动。 这才是走路。
④ 地面投影#
第一版评审的原话是"没有地面参照"——没有影子,根本看不出脚有没有离地。 加一个柔和的椭圆投影(高斯模糊 7px、透明度 60)之后,"踩地"立刻就读得出来了。
这不是装饰,是可读性的必要条件。
2.3 动画的"细节"从哪来#
不是帧数堆的,是层次堆的。这个 rig 有六层运动,每层有自己的频率和相位:
| 层 | 运动 | 频率 | 相位滞后 |
|---|---|---|---|
| 根(身体) | 起伏:腿并拢时最高,分开时最低 | 2× | — |
| 大腿 | 前后摆,左右反相 | 1× | — |
| 身体 | 跟随根 + 倾斜 | 2×/1× | 0.3 |
| 头 | 跟随身体 | 2× | 0.55 |
| 翅膀 | 小幅扇动 | 2× | 1.15 |
| 尾巴 | 左右摆 | 1× | 0.9 |
相位滞后是让动画"活"起来的关键——头和翅膀不是跟着身体同步动,而是晚一点动, 这就是真实生物的跟随运动(follow-through)。只有"整体上下晃"就是僵硬感的来源。
另外还有眨眼:第 11 帧换成闭眼的头,大约每 1.3 秒眨一次。这种小动作成本极低,但对"活物感"贡献很大。
2.4 第三版:形象不许改(正面小鸡的摇摆走路)#
上一版犯的错#
第二版的零件是另外生图出来的。这看起来是最省事的做法,实际上把小鸡的形象换掉了:
| 原版设定 | 第二版木偶 | |
|---|---|---|
| 朝向 | 正面 | 侧面(喙朝右) |
| 眼睛 | 两只,左右对称 | 一只 |
| 喙 | 在正中间 | 偏在一侧 |
| 翅膀 | 两侧各一只 | 一只 |
这不是"细节差异",是换了个角色。生图模型每次都会给你一只"看起来像小鸡的东西", 而不是同一只小鸡。
角色形象是已经定稿的资产。任何"再生成一版零件"都是在偷偷改设定。
正确做法是 tools/art/cut_chick.py —— 拿原图切,并且切完要能原样拼回去。
怎么切才能"一个像素都不改"#
原版是一只"一坨"的 Q 版小鸡(头身连在一起),所以只能横着分三层,加两只脚:
| 零件 | 范围(占原图高度) | 说明 |
|---|---|---|
| head | 0 ~ 52% | 顶上那撮毛 + 两只眼睛 + 喙 |
| body | 46% ~ 100% | 与 head 重叠 6%,接缝藏在同一片黄色里 |
| footL / footR | 橙色脚掌 ± 膨胀 | 从身体上"挖"出来 |
三个踩过的坑:
- 身体必须切到底(100%)。只切到 90% 的话,脚底那片黄色绒毛不属于任何零件, 会整片丢失(实测缺 1575 像素)。
- 脚要比坑大一圈。坑小 → 脚的不透明核心盖住坑;脚大 → 脚边缘的半透明像素 和身体原本的像素混合,和原图一致。反过来的话半透明边缘会和补坑色混合,静止就差 4.4%。
- 补坑只补原本就不透明的像素。膨胀后的坑会越过轮廓线,把轮廓外的透明像素 也补成黄色的话,脚周围会多出一圈黄边。
校验方式是硬的:切完按 body → head → 脚 的顺序拼回去,和原图做像素差——
现在是 0.00%。这个数字不达标就说明切法有问题,不要往下走。
正面角色的走路是"摇摆"不是"迈步"#
原版是正面朝向的,所以走路不是前后迈步,而是左右摇摆(waddle):
| 层 | 运动 | 频率 |
|---|---|---|
| 身体 | 左右倾(roll)—— 摇摆的主体 | 1× |
| 身体 | 上下起伏 | 2× |
| 身体 | 左右平移 | 1× |
| 头 | 滞后跟随身体的倾斜和起伏 | 1× / 2× |
| 脚 | 交替抬起 | 1× |
身体往右倾 = 重心压在右脚 = 左脚抬起。反了的话看起来就是在原地抽搐。
另外脚的旋转要小:脚掌转多了会把抬起量吃掉(左脚转的方向正好往下压, 实测设 26px 的抬起只剩 8px)。
幅度要用数字复核#
"感觉不够明显"和"感觉够了"都不可靠。这个 rig 的 --probe 直接打印每帧的曲线值,
另外可以量渲染结果里头顶的横向位置:
| 量 | 数值 | 占画布比例 |
|---|---|---|
| 头顶横向摆动 | 150 ~ 230px(80px 行程) | 画布宽的 20% |
| 质心纵向起伏 | 199 ~ 224px | — |
| 抬脚高度 | 26px | — |
| 身体左右倾 | ±10° | — |
成品#

这里出精灵表只是为了在文档里逐帧检查。真进游戏是 4 张小零件图 + 运行时算角度, 比预烘焙序列帧体积更小,参数随时可调。
三、田地收获#
同一套思路。第一版还是生图序列帧:
南瓜大小在帧间跳变、"飞起"是直线、帧数不够。改成木偶做法:

3.1 时间线#
| 阶段 | 帧 | 动作 | 曲线 |
|---|---|---|---|
| 冒出 | 0–4 | 南瓜从土里钻出来 | easeOut |
| 挤压 | 5–6 | 落地压扁再弹回 | squash & stretch |
| 飞起 | 6–12 | 沿抛物线飞向右上,同时旋转缩小 | 抛物线 + 缓出 |
| 收尾 | 12–15 | 金币和星光上飘、淡出 | 线性 |
3.2 两个手法#
① 遮挡代替遮罩。 南瓜"从土里冒出来"不是靠 alpha 遮罩,而是先画南瓜、再画土—— 土自然挡住下半截。飞到天上之后调换绘制顺序。简单,没有边缘瑕疵。
② 挤压拉伸(squash & stretch)。 南瓜出现和落地时横向拉宽 22%、纵向压扁 20%, 弹回时反过来。这是让动画"有重量"最便宜的手法——不加就是一块贴图在平移。
四、田里的动画该怎么分层#
这是比"能不能做"更重要的问题。答案:分层,而且序列帧只用在刀口上。
| 层 | 内容 | 用什么做 | 成本 |
|---|---|---|---|
| ① 交互反馈 | 田地点击压一下、谷仓收钱压弹 | 代码(transform) | 0 素材 |
| ② 飘字与粒子 | +N 升起淡出、粒子炸开 |
代码(opacity) | 0 素材 |
| ③ 待机摆动 | 作物摇摆、草随风动 | 代码(sin(t)) |
0 素材 |
| ④ 蹦出与飞行 | 作物蹦出、飞向谷仓、金色小鸡 | 曲线驱动 | 零件 1 张 |
上面这些动画一共 16 帧 × 2 套,但游戏里根本不需要精灵表—— 零件就 6 张小图,位置和角度在运行时算出来就行。 序列帧是"预烘焙",木偶是"实时算"。后者体积更小、还能随时调参数。
4.1 一次点击的完整时间线#
| 时间 | 发生什么 | 实现 |
|---|---|---|
| 0ms | 田地缩到 0.94 | 代码 |
| 60ms | 南瓜冒出(前 5 帧) | 木偶 |
| 60ms | 8~12 颗粒子从点击点炸开 | 代码 |
| 100ms | +N 飘字升起 |
代码 |
| 120ms | 田地弹到 1.06 再回 1.0 | 代码 |
| 300ms | 南瓜沿抛物线飞向谷仓 | 木偶 |
| 600ms | 谷仓压一下再弹回,金币数字滚动 | 代码 |
| 1200ms | 飘字消失,一切归位 | — |
五、结论#
| 问题 | 答案 |
|---|---|
| 生图能直接出可用的行走动画吗 | 不能。 帧间漂移 48×86px、帧间形变、6 帧不够 |
| 那怎么做 | 木偶动画。 运动全部由代码驱动 |
| 零件从哪来 | 从原图上切,不许重新生图。 重新生图 = 偷偷改角色设定 |
| 效果差多少 | 漂移 48px → 1px;踩地的脚从"跟着晃"变成一动不动 |
| 切零件怎么保证不改形象 | 切完按原顺序拼回去,和原图做像素差——现在是 0.00% |
| 田里的动画怎么做 | 分层,四层里有三层用代码、一层用木偶 |
| 成本 | 一套动画 4~6 张零件图,运行时零精灵表 |
下一步:
- 把四个动物(小鸡 / 小兔 / 小猪 / 小羊)都从原图切零件、都上这套 rig。 小兔的长耳朵、小猪的卷尾巴会有更明显的次级运动,值得单独调。
- 出金色小鸡出现 + 被点中的木偶动画(柔光 + 光晕 + 爆金币)。
- 给 rig 加 squash & stretch(现在只有南瓜有),让身体在落地时压一下。
- 小鸡的眨眼还没做(第二版有,改成正面后要重新定位眼睛位置)。
- 用代码实现 ①②③ 三层,先做出"能点的田"——这一步不需要任何美术素材。
这套 rig 也证明了 P0(结构稳定重绘)为什么必须先做: 上面那张时间线里从 0ms 到 1200ms 有 8 个动作, 在每秒重建整棵 DOM 的架构上,一个都做不了。