动画与场景设计
所有提示词都从 tools/art/gen_batch.py 里的同一个 STYLE 常量拼出来 ——
线宽、色板、纹理做法一个字都不改。这是这一批"看起来是一套"的原因。
上一轮的教训是:每张图各写各的提示词,出来的东西色相、线宽、明度全不一样, 拼在一起就是拼贴画。
全部走 图生图(/v1/images/edits + gpt-image-2pro):
动物拿参考图拆出来的那只兔子当风格参考,植物拿元素拆离表当风格参考。
文生图传不过风格,只有图生图能。
一、动物动画#
每一张都是一行 N 个连续姿态(一张图一次生成 → 帧与帧之间风格天然一致)。 下面每个都可以暂停 / 逐帧 / 调速度 —— 判断动画好坏要逐帧看,比看一遍循环有用。
① 切件不能用等分格子。
原本用 tools/art/spritesheet.py 按 cols×rows 等分切。这对"姿态乖乖待在自己格子里"
的素材是对的,但生图的姿态会超出格子 —— 实测兔子跳跃那张,6 个姿态里有 2 个左右都顶到格子边界,
也就是兔子被切断了一部分。
而"等分切"本身看不出这件事:它总会切出 6 张图,每张大小一样,只是内容缺一块。
是靠逐帧边界检查(内容贴住单元格最外一列像素)才发现的 ——
修的方法是换 tools/art/pose_sheet.py,走二维连通域切件,跨格子的姿态也能整只切出来。
| 修之前 | 修之后 | |
|---|---|---|
| 兔子跳跃 | 2 帧左右贴边 + 3 帧底部贴边 | 全部正常 |
| 兔子奔跑 | 正常 | 正常 |
| 麻雀 | 3 帧底部贴边 | 全部正常 |
| 萤火虫 | 4 帧底部贴边 | 全部正常 |
| 甲虫 / 蝴蝶 | 正常 | 正常 |
② 没有透明底。
spritesheet.py 里的 normalise() 写死了 Image.new("RGB", ...) ——
于是每帧都被刷上生图那张纸的米白色。在文档的浅色底上看不出来,
一旦叠到真实场景上就是一块块米白方块。
现在默认走 RGBA + 抠底(--no-alpha 可关)。11 张精灵表实测 alpha 范围全是 0~255。
教训:RGB 画布在浅色页面上"看起来没问题",这类 bug 只有换背景/叠图才会暴露。
1.1 小兔子 · 奔跑循环(8 帧)#
1.2 小兔子 · 跳跃(6 帧)#
1.3 小麻雀 · 跳跃扇翅(6 帧)#
1.4 萤火虫 · 飘浮(6 帧)#
1.5 甲虫 · 爬行(6 帧)#
1.6 蝴蝶 · 扇翅(6 帧)#
「兔子奔跑 8 只、腿姿交替能看出循环、形象一致、无重叠无裁切」; 其余 5 张同样过了"数量对、姿态在变、形象一致、画法统一、无重叠"。
但"能看出在动"和"动得对"是两件事。 真正的验收应该是 逐帧看关节有没有跳、脚有没有穿地 —— 这一条还没做。
二、植物(5 条风景带 × 6 种 = 30 株)#

| 风景带 | 植物形态 |
|---|---|
| 苔原草坡 | 低矮垫状植物、细硬短草、微小高山花、灰色地衣、小型莎草 |
| 石楠台地 | 石楠状灌木(密生小铃花)、低矮木质茎、细针叶 |
| 溪谷林地 | 蕨类、阔叶林下植物、苔藓团、细高的耐荫草 |
| 砾石滩 | 微小景天、匍匐垫状植物、细弱草丛、灰叶小植物 |
| 高地野花田 | 高杆野花、种子头、三叶草、带刺蓟状植物、羽毛状草 |
出图原稿(每带一张,6 株一排,由程序切开):
| 带 | 原稿 |
|---|---|
| 苔原草坡 | ![]() |
| 石楠台地 | ![]() |
| 溪谷林地 | ![]() |
| 砾石滩 | ![]() |
| 高地野花田 | ![]() |
视觉复核对这批的意见是:「多数有茎、叶和花头,但蕨类、叶状植物及部分禾草的花头不明显或缺失」。 这一条不是问题 —— 蕨类和草本来就不开花。提示词里写的是"有种子的地方要有种子头", 模型按植物学常识处理是对的。不要为了"每株都要有花头"去改,那会做出假的植物。
2.1 植物摇摆动画(连续变形,不是生图序列帧)#
五条带各挑一株做摇摆,8 帧:
第一版是 t = 1 - y/(h-1)(顶=1)之后又算 bend = (1-t)**1.6 ——
两次取反正好抵消,结果变成底部摆得最大、顶端不动,
整个像地里长出来的东西在扭腰。正确的写法是 bend = t**1.8(t 就是"离根部有多远")。
验法也不能靠肉眼:逐帧的像素差会把"哪儿植物多"和"哪儿在动"混在一起 (内容多的地方差值天然大)。要量每 10% 高度带的水平重心位移:
| 高度带 | 位移 |
|---|---|
| 顶 0~10% | +7.7 px |
| 10~20% | +13.2 px(最大) |
| 50~60% | +4.1 px |
| 80~90% | +0.5 px |
| 底 90~100% | 0.00 px(根部锁定) |
现在脚本落在 tools/art/plant_sway.py。
做法: 绕根部旋转 + 按行水平位移 + 相位滞后 —— 底部固定,越靠上摆幅越大, 再叠一层两倍频的小抖动让叶子不是整体硬摆。
为什么不生图:一株植物只有一个姿态可参考,让它"再摆一个姿势", 出来的就是另一株植物了(生图序列帧的老毛病:帧间形变)。 连续变形是同一张图在动,帧与帧之间只差一个显式参数 —— 改一次摆幅,8 帧一起变。
这条和 小农场 那边"走路动画在图是一坨的时候用连续变形"是同一套办法
(tools/art/walkers.py)。
三、场景背景#
三轮的失败方向正好相反,这是这一节最值得记的东西:
| 轮次 | 平均明度 | 色度 | 元素内暗部 | 结论 |
|---|---|---|---|---|
| 第一轮 | 0.738 ~ 0.838 | 4 ~ 12 | — | 太亮(基准 0.601),发白像蒙雾 |
| 第二轮 | 0.374 ~ 0.485 | 14 ~ 26 | 27 ~ 48% | 太暗,色度和暗部对了但整体压黑 |
| 第三轮(gamma 标定) | 0.588 ~ 0.597 | 9 ~ 17 | 3.4 ~ 21% | 3/5 达标 |
第一轮为什么亮:我在提示词里写了「远处更亮更朦胧」,却没说整幅该压在哪个明度上, 它就把天空和白雾一路推到接近纯白。另外第一轮还是横构图, 而这是个竖屏 9:16 的游戏 —— 一起改成竖构图重出。
第二轮为什么暗:我为了修"太亮",加了一段硬性明度约束 (天空最多占上面 30%、约 40% 的像素暗于 30% 明度、明确写"发白的高调画面视为失败")—— 然后模型就往反方向冲过头了。
第三轮为什么只过 3/5:我用 gamma 把明度拉到基准 0.601, 但 gamma 提亮是把暗部一起提掉的 —— 石楠台地原本暗部 47.5%, 提亮后只剩 3.8%。于是"明度"和"暗部"两条判据互相打架。
解法是换掉提亮曲线:改成分段曲线,knee(本次取 0.08~0.16)以下的像素
完全不动,只提亮中高调。这样暗部保住了,明度也能上去。
溪谷林地靠这一条拿到 暗部 8.9% / 色度 14 / 明度 0.611,全部达标。
[!warning] 这里踩的是我自己在 4.1.6 写过的坑,而且踩了两次 第一次:只有"不许加饱和"这条上限,没有色度下限 —— 于是第二轮出来的背景色度掉到 3(要求 ≥13),画面是纯中性灰, 而明度和暗部全部合格。"太灰"和"太艳"是两个方向,各要一条判据 —— 这条是项目里白纸黑字记着的,我还是漏了。 第二次:"太亮"和"太暗"也是两个方向,我修第一个的时候直接把第二个做出来了。
3.1 五条风景带(竖屏)#
第三轮成品(竖构图 + 明度标定):
| 苔原草坡 | 石楠台地 | 溪谷林地 |
|---|---|---|
![]() |
![]() |
![]() |
| 砾石滩 | 高地野花田 |
|---|---|
![]() |
![]() |
五带横向拼在一起,检查"是不是同一个世界":

- 天空最多占上面 30%,而且天空本身是灰蓝(
#808090~#B8B8BE),不能是白/米白; - 景深只用明度做:远处更亮更糊、近处更暗更实;不用强透视,地平线不在正中;
- 底部约 25% 必须是简洁干净的地面带 —— 角色要站在上面,那一带不能有杂物, 否则角色读不出来。这条和场景演示里"动物道必须在地平线以下"是同一个道理。
四、验收结果(跑的是项目自己的脚本,不是我目测)#
python3 tools/art/check_tone.py --scene docs/野花笔记/art/bg2/*.png
| 图 | 平均明度 | 色度 | 元素内暗部 | 饱和 P90 | 判定 |
|---|---|---|---|---|---|
| 苔原草坡 | 0.591 | 15 | 21.4% | 13.8% | ✓ |
| 石楠台地 | 0.614 | 13 | 10.9% | 14.8% | ✓ |
| 溪谷林地 | 0.610 | 14 | 8.9% | 13.0% | ✓ |
| 砾石滩 | 0.597 | 13 | 6.9% | 14.4% | ✓ |
| 高地野花田 | 0.590 | 17 | 11.3% | 14.6% | ✓ |
| 五带拼接条 | 0.600 | 14 | 8.6% | 14.1% | ✓ |
| 判据 | 0.55~0.72 | ≥13 | ≥6% | ≤22% | |
| 基准参考图 | 0.601 | 12 | 9.9% | 15.2% |
6/6 全部达标。
第一版石楠台地色度只有 10、暗部只有 3.8%(要 ≥13 / ≥6%)—— 图本身画得太平, 没有真正的暗块也没有可辨认的冷色调。这一条调 gamma 修不了: 提亮只会让明度合格而把暗部打得更低(实测提亮后暗部从 47.5% 掉到 3.8%)。
重出了一张之后,它又冲到了另一个极端:色度 22(对)、暗部 25%(对), 但饱和 P90 = 32%,超过 ≤22% 上限 —— 从"太灰"直接跳到了"太艳"。
最后是联合标定(先向亮度混合降饱和,再分段提亮)才同时满足四条:
k=0.62(降饱和系数)g=0.52 knee=0.10。
[!danger] 这一轮我一共在同一个坑里摔了四次,全是同一个错误
4.1.6写着「"太灰"和"太艳"是两个方向,各要一条判据」。 我嘴上引用它,手上却每一次都只推一个方向:
方向 第一次 反方向 结果 明度 太亮 0.84 太暗 0.38 两个都要判据 彩色 太灰 色度 3 太艳 饱和 32% 两个都要判据 修"太亮"的时候把"太暗"做出来了;修"太灰"的时候把"太艳"做出来了。 教训不是"下次小心",是加约束时必须同时把反向的界也写上 —— 只写一边,模型(或你自己调参)必然冲到另一边。
[!note] 这 6 张的明度都是标定出来的,不是生图一次命中 生图原始明度在 0.374~0.481,全部偏暗。用分段 gamma + 降饱和后处理拉到基准。 严格说这 6 张的明度是"我调的"而不是"模型给的"。 记在这里是为了下次不要以为一次生图就能命中。
五、场景元素素材(场景是拼出来的)#
整图生成有两个问题: ① 每次出来的东西不一样,改一处要重生成整张; ② 背景和前景焊死,没法做视差滚动 —— 而视差是横版游戏的核心。
所以背景拆成两层来做: 远景用整张画(云、天光这种没法拼的),中近景全部用元素拼。
5.1 六类元素,每类 6 个变体 = 36 件#

| 类别 | 6 个变体 |
|---|---|
| 石头 | 圆巨石 / 平板石 / 棱角中石 / 卵石堆 / 半埋的长苔石 / 磨平的踏石 |
| 树木 | 高窄针叶 / 圆阔叶 / 分层塔形 / 幼树苗 / 枯树 / 低矮灌木树 |
| 灌木 | 圆密灌丛 / 散枝矮丛 / 匍匐垫状 / 带小浆果 / 发白霜的 / 带刺棘丛 |
| 草丛 | 高细草丛 / 短硬草簇 / 宽叶莎草 / 苔藓块 / 小蕨丛 / 匍匐地被 |
| 枯木 | 倒木带断枝 / 树桩 / 扭曲枯枝 / 小枝堆 / 空心木段 / 翻起的根盘 |
| 远山 | 圆缓丘 / 尖脊 / 崖边 / 远处树线 / 缓坡 / 平顶台地 |
每个类别里的 6 个是同一族的不同变体(提示词里明写"look like they belong to the same landscape — a set of variants of the same family of objects"), 所以它们摆在一起不会互相打架。
它是视差的最远层,提示词里要求"FLAT SILHOUETTES only, LIGHT hazy blue-grey, clearly LIGHTER in value than the other props"。摆进场景时还会再压一层透明度。
5.2 可玩的走路测试场景(先玩这个)#
按住屏幕左半边 → 兔子转身往左走;按住右半边 → 往右走;松开就停。 手机和电脑都能用(电脑也可以按 ← → 键)。
场景里是四层视差 + 角色:远山几乎不动、中景树慢慢移、地面和兔子同速、前景草比兔子快。 兔子用的是真实的 8 帧奔跑精灵表(左走时水平翻转)。
搭建方式:tools/art/build_walk_demo.py,用第 5.1 节那 36 件元素拼出来,
四层各导出成一张宽 2 屏的透明图,运行时无缝平铺 + 按视差因子滚动。
| 层 | 视差 | 内容 |
|---|---|---|
| L0 | 0.20 | 天空渐变 + 远山 |
| L1 | 0.45 | 中景树 + 灌木(整体压暗到 0.74) |
| L2 | 1.00 | 地面 + 碎石 + 小径 + 近处石头枯木 |
| L3 | 1.35 | 前景草(最高点不超过走线) |
每次改完必须在无头浏览器里实测,不能只看代码:
node /tmp/walk_check.mjs # 起 Chrome + CDP,按住半屏 1.4s,截图 + 读状态
- 截图视口高度不是窗口高度。窗口设 600×1000,实际视口只有 600×861(少了浏览器 chrome), 于是 CSS 缩放系数从 1.11 变成 0.897。按错的系数去裁剪"兔子在哪", 裁到的是背景 —— 然后就会得出"兔子没画出来"这种错误结论。
- 视觉模型对整图的评价会偏严,对局部放大的评价才准。 整图它说"兔子被遮挡 60~70%",放大裁剪后说"轮廓清晰,没有和背景糊在一起"。 判断"某个东西清不清楚"要放大看,不要看全景。
5.3 拼出来的五带场景#
用 tools/art/compose_scene.py 拼,540×960 竖屏:
| 苔原草坡 | 石楠台地 | 溪谷林地 |
|---|---|---|
![]() |
![]() |
![]() |
| 砾石滩 | 高地野花田 |
|---|---|
![]() |
![]() |
图层顺序(从远到近):天空渐变 → 远山 → 中景树 → 近景灌木 → 地面 → 石头/枯木 → 前景草 → 角色
每层都带自己那一档的透明度和明度衰减(远山 0.55、中景树 0.78、近景 1.0), 景深是用透明度+明度做的,不是用颜色做的 —— 这样换配色时层次不会塌。
第一轮:「底部横向硬切线明显;下方留白过大,像一整块空黑地面;前中后景层次偏平」。
第二轮(加了地面渐变 + 碎石点之后):「横切线仍很直、明暗跳变明显; 还是偏"一大块空黑",纹理不足」。
第三轮(改起伏地平线 + 密铺地被之后):「基本不是直线了;不再是空的了; 能读成自然场景」。
三处修改分别是:
- 地平线改成起伏的。直线地平线是"拼贴感"最大的来源 —— 一眼就能看出地面是一块贴上去的矩形。用三个频率叠加的正弦做边缘(振幅 9 / 5 / 2.5 px)。
- 地面自身要有明度层次。上段暗、下段亮(
0.84 → 1.18),否则就是一块死黑。 - 地被要密铺到地面上,不能只摆在地平线上。 原来的元素全都贴着地平线站,够不到下面,所以下半幅是空的。 现在把草丛和碎石缩小了铺 46 个到地面带里,越靠下越小越淡(近大远小的简易版)。
[!tip] 这一条是可以推广的 "拼贴感"通常不是元素画得不好,是接缝太规则。 直线边界、均匀间距、同一尺寸 —— 这三样才是"一看就是贴上去的"的原因。
剩下的问题(已记录、暂未改):「前景土层仍偏平且纹理均匀; 部分植物和山体像贴上去的,尺度/透视统一性不足」。
六、验收结果(跑项目自己的脚本,不是目测)#
python3 tools/art/check_tone.py --scene docs/野花笔记/art/bg2/*.png
6/6 全部达标(5 张背景 + 1 张拼接条)。判定口径:明度 0.55~0.72 / 色度 ≥13 / 元素内暗部 ≥6% / 饱和 P90 ≤22%。
生图原始明度在 0.374~0.481,全部偏暗。用分段 gamma + 降饱和后处理拉到基准 0.601。 严格说这 6 张的明度是"我调的"而不是"模型给的"。 记在这里是为了下次不要以为一次生图能命中。
本节详细的失败数据已移到 docs/野花笔记/美术规范.md 第十节,
因为那是画风基准文档,判据本身该记在那里。
七、还没做的#
- 帧数偏少。奔跑只有 8 帧、其余 6 帧。10
13fps 下能看, 但要更顺需要 1216 帧 —— 生图一次出不了那么多(多了每格会糊)。 - 植物只挑了 5 株做摇摆(每条带 1 株),剩下 25 株还是静态的。
- 背景还没合成进运行时场景。演示页
notes.html里的场景仍然是代码画的几何图形 —— 这批生图素材目前只出现在本文档的图片里。 --align用的是none。奔跑和跳跃这两组位置会漂(生图的帧间位置本来就不一致), 没做feet对齐 —— 因为这两组的姿态本身就在上下移动,feet对齐会把跳跃抹掉。 正确的做法是手动给每组一个对齐基准,还没做。- 动物素材还没过
check_sat.py/check_tone.py,只过了视觉复核。 背景是过了的(见第四节,6/6 达标)。 - 植物摇摆脚本还在
/tmp,按工程约定应该挪进tools/art/并提交。














