🌻 设计文档小农场 · 野花笔记
← 全部文档/小农场/美术

动效验证 · 序列帧与木偶动画

现行 #美术#动画#动效 docs/小农场/动效验证-序列帧.md

回答"这种风格的小动物能不能做行走动画"。做了两版,第一版不行,第二版行—— 过程和数据都在这里。

下面所有播放器都能暂停、逐帧、调速。判断动画质量靠逐帧看,不是看一遍。

一、第一版:生图序列帧(不够好)#

最直接的做法是让生图直接出一张 6 帧的精灵表。

第一版 · 生图序列帧 · 6 帧(速度拉到 2~4 能看清问题)
帧 1 / 6

1.1 实测:帧间位置是不一致的#

把 6 帧逐个量内容包围盒:

实测 后果
帧宽差 14px —
帧高差 35px —
内容中心横向漂移 48px 角色在帧间乱跳
内容中心纵向漂移 86px 同上

那 48×86px 不是动画,是噪音。用 tools/art/spritesheet.py --align feet (把每帧底边中点对齐到同一条地平线)处理后,横向归零、纵向剩 18px。 漂移能修,但下面两个问题修不了。

1.2 修不了的两个问题#

  • 帧间形变。模型理解"走路循环"没问题,但不理解"角色必须是同一个"。 鸡冠、翅膀的形状每帧都在变,看起来像在融化而不是在动。
  • 帧数不够。6 帧做不出细节。而且腿和脚是一整块——绕髋一转,脚就跟着翘, 永远不像踩在地上。
结论:这条路走到头了

靠改提示词解决不了帧间形变。这是生图做角色动画的固有上限,不是参数没调好。

二、第二版:木偶动画#

换做法:生图只出零件(身体、头、翅膀、大腿、脚、尾巴分开), 然后用代码装配、用曲线驱动。

第二版 · 木偶动画 · 16 帧 · 12fps
帧 1 / 16

同一套帧的自动播放版(纯动态 WebP,不需要 JS,可以直接丢进游戏):

小鸡行走 · 木偶版动态 WebP
小鸡行走 · 木偶版动态 WebP

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 橙色脚掌 ± 膨胀 从身体上"挖"出来

三个踩过的坑:

  1. 身体必须切到底(100%)。只切到 90% 的话,脚底那片黄色绒毛不属于任何零件, 会整片丢失(实测缺 1575 像素)。
  2. 脚要比坑大一圈。坑小 → 脚的不透明核心盖住坑;脚大 → 脚边缘的半透明像素 和身体原本的像素混合,和原图一致。反过来的话半透明边缘会和补坑色混合,静止就差 4.4%。
  3. 补坑只补原本就不透明的像素。膨胀后的坑会越过轮廓线,把轮廓外的透明像素 也补成黄色的话,脚周围会多出一圈黄边。

校验方式是硬的:切完按 body → head → 脚 的顺序拼回去,和原图做像素差—— 现在是 0.00%。这个数字不达标就说明切法有问题,不要往下走。

正面角色的走路是"摇摆"不是"迈步"#

原版是正面朝向的,所以走路不是前后迈步,而是左右摇摆(waddle):

层 运动 频率
身体 左右倾(roll)—— 摇摆的主体 1×
身体 上下起伏 2×
身体 左右平移 1×
头 滞后跟随身体的倾斜和起伏 1× / 2×
脚 交替抬起 1×
重心和抬脚必须反相

身体往右倾 = 重心压在右脚 = 左脚抬起。反了的话看起来就是在原地抽搐。

另外脚的旋转要小:脚掌转多了会把抬起量吃掉(左脚转的方向正好往下压, 实测设 26px 的抬起只剩 8px)。

幅度要用数字复核#

"感觉不够明显"和"感觉够了"都不可靠。这个 rig 的 --probe 直接打印每帧的曲线值, 另外可以量渲染结果里头顶的横向位置:

量 数值 占画布比例
头顶横向摆动 150 ~ 230px(80px 行程) 画布宽的 20%
质心纵向起伏 199 ~ 224px —
抬脚高度 26px —
身体左右倾 ±10° —

成品#

正面小鸡摇摆走路 · 16 帧(零件取自原图,形象未改)
帧 1 / 16
正面小鸡摇摆走路循环(自动播放)
正面小鸡摇摆走路循环(自动播放)
木偶动画在游戏里不需要精灵表

这里出精灵表只是为了在文档里逐帧检查。真进游戏是 4 张小零件图 + 运行时算角度, 比预烘焙序列帧体积更小,参数随时可调。

三、田地收获#

同一套思路。第一版还是生图序列帧:

第一版 · 生图序列帧 · 6 帧
帧 1 / 6

南瓜大小在帧间跳变、"飞起"是直线、帧数不够。改成木偶做法:

第二版 · 木偶动画 · 16 帧 · 10fps
帧 1 / 16
田地收获 · 木偶版动态 WebP
田地收获 · 木偶版动态 WebP

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 的架构上,一个都做不了。