场景演示
一块田,几百株作物在风里摆,几十只动物在旁边来回走。 下面这个可以直接玩:
暂停能让你逐帧看走路的步态。作物 +100 / 动物 +8 能压到一千多株作物, 右上角实时显示 fps 和"每帧多少次绘制"——数字变了画面还跟不跟得上,一眼能看出来。
一、这一版做了什么#
- 从生图结果里切出 4 种作物(幼苗 / 南瓜 / 胡萝卜 / 玉米)和 4 只动物(小鸡 / 兔 / 猪 / 羊)
- 给 4 只动物做了走路动画,按显示尺寸烘焙成序列帧
- 作物的摇摆纯代码算,零素材
- 一个能跑几百个物体的场景渲染器
二、走路动画:怎么让"一坨"动起来#
2.1 先说做不到的:切片#
按原计划,走路动画应该切成"头 / 身体 / 脚"三段分别驱动。但这四只动物切不了。
看图得到的结论是一致的:
| 动物 | 头身分界 | 腿的形态 |
|---|---|---|
| 小鸡 | 没有明显分界,头和身体是连续的圆团 | 两条细腿 |
| 兔子 | 有轻微收窄,但头胸体基本连续 | 腿和身体连在一起 |
| 猪 | 分界不明显,头和圆身体连成一体 | 短粗腿,和身体下缘连接较紧 |
| 羊 | 渐变连接,不是硬分界 | 短腿较粗,部分被毛遮住 |
硬切一条直线会怎样:抬脚的时候会露出一条笔直的横切口,比不动还难看。
这印证了已经写进 AGENTS.md 的那条——
想让角色能拆,必须在出图阶段就头/身体/四肢分开画(规范见 tools/art/rig_layers.py)。
已经生好的"一坨"图,代码救不回来。
2.2 所以改用连续变形#
不切片,改成对整张图做三种连续变形。关键是第三种:
| 层 | 做什么 | 为什么 |
|---|---|---|
| 绕脚底旋转 | 绕脚底接触点转 ±6°~10° | 绕中心转会像被吊着,不是在走 |
| 上下起伏 | 两倍频(一个循环里上下两次) | 左右各迈一步,所以起伏是倾斜的两倍频 |
| 弯曲 + 跟随延迟 | 按行做水平位移,越靠上位移越大、相位越滞后 | ★ 见下 |
| 抬脚 | 身体线以下按列做垂直位移,一侧抬一侧落 | ★ 见下 |
弯曲 + 跟随延迟是让单张图"活"起来的关键。第一版只有整体刚体运动(倾斜 + 起伏 + 平移), 评价原话是"更像原地抖动,不太像走路"——因为整体同步运动没有层次。
做法是逐行给一个水平位移:
dx(y) = A · u^1.35 · sin(ω + lag · u) // u = 1 在头顶,0 在脚底
越靠上的行位移越大、相位越滞后。这是单张图拿到 follow-through 的唯一办法, 而且因为它是连续变形不是拼接,不会留接缝——正好适合这种头身连成一坨的角色。
抬脚同样用连续变形做:位移量随高度从 0 长到满,所以在身体线上是 0、到脚底最大, 中间没有接缝;透明区域位移后还是透明,肉眼只看到两只脚在交替抬起。
身体往右倾的时候要抬左脚。同相的话两只脚和身体一起动,看着就是在原地抽搐。 代码里就是抬脚的相位多加一个 π。
2.3 幅度必须打印出来看#
肉眼看不出 3% 和 10% 的差别。--probe 会打印实际位移:
python3 tools/art/walkers.py --probe
| 动物 | 水平摆幅 | 上下 | 倾角 | 抬脚 |
|---|---|---|---|---|
| 小鸡 | ±8.6px(画布宽 23%) | ±5.4px | ±9.8° | ±13.9px(帧高 27%) |
| 兔子 | ±5.4px(18%) | ±4.2px | ±7.7° | ±11.3px(22%) |
| 猪 | ±6.7px(16%) | ±3.6px | ±6.6° | ±10.3px(20%) |
| 羊 | ±5.9px(14%) | ±3.2px | ±6.0° | ±9.8px(19%) |
大动物的幅度故意小一点(体量大、惯性大),小鸡最活泼。
2.4 四种动物的走路循环#
三、作物摇摆:零素材#
作物不需要任何动画素材,全部是 sin 算的:
const a = Math.sin(T * (0.8 + w * 0.4) + phase * 6.4) * amp * wind;
ctx.translate(crop.x, crop.y);
ctx.rotate(a); // ★ 绕**根部**转
ctx.drawImage(img, -w / 2, -h, w, h);
两个要点:
绕根部转,不是绕中心。绕中心会像飘在半空,不是长在地里。
相位是位置算出来的,不是随机的:
phase = x * 0.010 + y * 0.005;这样风会横着扫过整块田,形成一波一波的浪。相位纯随机的话,每株各晃各的, 看着像在抽搐而不是刮风。
再叠一层慢速主波做风力强弱变化:wind = 1 + sin(T*0.55)*0.3 + sin(T*1.3)*0.14。
四、性能:几百个物体怎么做#
| 决定 | 依据 |
|---|---|
| 动物用按显示尺寸烘焙的序列帧 | 每只每帧 1 次绘制。跑骨骼是每只 4 次,几百个物体差 4 倍(第六节实测) |
| 作物摇摆零素材 | 不占包体、不占显存,纯 CPU 算 |
| 画家算法按 y 排序 | 作物位置是定死的,排序只做一次;每帧只按序画 |
| 画布只画可见范围 | 场景本身就限制在画布内,不用额外剔除 |
场景右上角实时显示 fps 和每帧绘制次数,可以直接加减数量看它能扛到哪。
五、这一版踩过的坑#
第一版有三条道铺在 y=0.20/0.27,那是地平线以上。 结果看图评价是"像是在漂浮"、"动物直接出现在天空区域,没有明显地面或阴影支撑"。 动物道必须全部铺在地平线以下,而且要有接触阴影。
[!warning] 作物不能混着随机撒 第一版每株随机一种作物,出来是"一片糊在一起的色块",田垄结构完全看不出来。 改成一垄一种(再掺 18% 别的作物打散色带)之后才像田。
[!warning] 幼苗不能和南瓜一样大 给所有作物统一高度,前排那排幼苗排成了"一堵横向贴图墙",把整个田压扁了, 分数从 7 掉到 6.5。幼苗是苗,高度系数给 0.42,南瓜 1.0。 现在是 9。 教训:"作物"不是一种东西,尺寸要按类型给。
[!warning] 不能种满 每个格都种上,画面是"一堵作物墙",没有呼吸感。 改成 34% 的格子空着露出裸土,再加每株 0.72~1.18 倍的生长差异,画面立刻透气了。
六、下一步#
- 下一批动物出图时按
rig_layers.py的规范分层出图,这里就能换成真正的三段骨架, 耳朵 / 尾巴 / 翅膀的次级运动会明显得多。 - 作物可以做生长阶段:同一株从幼苗长到成熟,而不是四种作物各占一片。
- 加点击:点作物收获得飘字,点动物有反应——这是核心玩法要用的。
- 场景里再放几个设施(谷仓、水井、风车)当视觉锚点。