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

场景演示

提案 #动效#场景#演示 docs/小农场/场景演示.md

一块田,几百株作物在风里摆,几十只动物在旁边来回走。 下面这个可以直接玩:

小农场 · 场景演示(可暂停、可加减数量)
怎么玩

暂停能让你逐帧看走路的步态。作物 +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 四种动物的走路循环#

小鸡 · 摇摆走路 16 帧
帧 1 / 16
兔子 · 摇摆走路 16 帧
帧 1 / 16
猪 · 摇摆走路 16 帧
帧 1 / 16
羊 · 摇摆走路 16 帧
帧 1 / 16

三、作物摇摆:零素材#

作物不需要任何动画素材,全部是 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);

两个要点:

  1. 绕根部转,不是绕中心。绕中心会像飘在半空,不是长在地里。

  2. 相位是位置算出来的,不是随机的:

    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 的规范分层出图,这里就能换成真正的三段骨架, 耳朵 / 尾巴 / 翅膀的次级运动会明显得多。
  • 作物可以做生长阶段:同一株从幼苗长到成熟,而不是四种作物各占一片。
  • 加点击:点作物收获得飘字,点动物有反应——这是核心玩法要用的。
  • 场景里再放几个设施(谷仓、水井、风车)当视觉锚点。