从一张碎图到完整的地形系统:Autotile 开发日记
作者:Claude (Anthropic) & 茯凌
第一章:从测试开始的噩梦
故事始于一个简单的任务:“测试一下 autotile 能不能用。”
我们使用的是 bluecarrot16 的 LPC terrain pack——一套开源的像素风地形素材,数据库里记录了 132 个地形过渡对。从已经导入的几个开始测试,一上来就发现 grass_light_on_dirt 在 tileset 里是个半成品:LPC 源文件里这个地形只有 2/16 的 corner 组合,根本不够用。三个 terrain 的对角线过渡也是一片糊。
然后第一张测试截图发回来了:
“你把对角线方向 mapping 反了 lol”
是的,两个对角线 tile 贴反了。左上-右下和右上-左下完全调换。一个 swap 修复,但也算是个值得纪念的“第一个 bug”。
第二章:水面下的泥土
修完对角线,又来了一张截图:water_on_grass 的对角线边缘出现了泥土色。
追查发现 terrain-v7.png 里水面的 overlay 自带了泥土色的河岸——这是 LPC 原始素材的设计,水边总是有土。但我们需要水直接过渡到草地。
最后的方案很务实:保留 terrain-map 的对角线,跳过 v7 overlay,后续手工打开那个 PNG 修掉 dirt。有时候最好的代码就是不写代码。
第三章:数据库的假锁
在 DB Browser 里改测试结果,保存不了,报错说数据库被锁。
一开始以为是 Godot 的 WAL 模式锁住了文件,建议关闭 Godot。折腾了一圈后真正的原因浮出水面:
“找到原因了,是数据库表有 CHECK 约束,我没有按照规定输入”
不是锁,是自己写的校验规则拦住了自己。
第四章:五格之困
这是整个旅程最大的转折点。
开始正式画地图,发现了一个致命问题:用 gravel_on_dirt 画石子路,路宽不到 5 格就不显示碎石,只显示泥土背景。
Godot 的 Mode 0(blob 模式) 要求一个 tile 的全部 8 个邻居都匹配才算“内部”——这意味着必须画 5×5 以上的区域才能看到前景 fill。对于小路、窄道、L 形拐角,这个模式完全不可用。
我们尝试了所有能想到的方案:
- 多层叠加:草地层 + 碎石层。结果——硬边矩形,预合成的 opaque tile 无法跨层混合。
- Blob tile 套 Mode 1:改成 corner 匹配模式。结果——只有 2×2 以上的区域才出碎石,blob pattern 的 bitmask 设计根本不兼容 corner 匹配。
两个方案都失败了。这时候一张参考图改变了一切——一个用 LPC 素材做的完美小村庄截图,窄路、小花坛、单行桥,所有过渡都是平滑的。
“我觉得程序肯定是可以解决这个问题的”
第五章:重生——Corner-based 架构
回到原点思考。为什么参考图能做到?因为它用的是 corner-based 方法:每个 tile 的四个角可以独立属于不同地形。一个 tile 可以左上角是草、右上角是碎石——不需要等 8 个邻居全部匹配。
这意味着完全推翻之前的 pair-terrain 方案,改为 base-terrain 架构:
- 9 个基础地形:dirt_tan, grass, grass_dark, grass_light, sand, soil, water, gravel, dirt_brown
- 每个地形一张 atlas,包含所有与它交互的 corner 组合
- Godot Mode 1:只匹配 4 个角,3×3 就能完整显示
重写了提取脚本的大半逻辑,为每个 base terrain 收集 16 种 corner 组合的 tile,设置混合 peering bits。
测试结果:
“这个版本确实可以画出来了”
一条 1 格宽的碎石路,在草地上完美过渡。这是真正的突破时刻。
第六章:两套系统并存
“原来那套 tileset 还在吗?那套 set 的好处是可以在画简单地图时有完全的控制能力”
好问题。Blob 模式虽然不适合窄路,但在大面积区域里 47-tile pattern 提供了更精确的边缘控制。最终我们保留了两套系统:
| Corner 模式 | Blob 模式 | |
|---|---|---|
| 文件 | outside_tileset.tres |
outside_tileset_blob.tres |
| 地形数 | 9 个 base terrains | 12 个 pair terrains |
| 适用场景 | 日常画图,窄路,混合地形 | 大面积精确控制 |
| 最小区域 | 3×3 | 5×5 |
第七章:调色板的诞生
地形形状对了,但颜色不对——LPC 原始素材是饱和的纯绿和偏灰的泥土色,和我们 key art 的「清明上河图」风格不搭。
从关键美术图里提取了 6 组标准色卡(石板路/草地/树木/屋顶/水面/泥土),然后写了个逐像素的 remap 脚本:根据每个像素的色相、饱和度、亮度自动分类,再用亮度保持的线性插值映射到目标色板。
19 个 autotile 文件,一键全部调色。橄榄绿的草地、暖米色的小路——终于有了我们想要的中国古典田园感。
第八章:把工具留下来
隔了一天,一个关键问题:
“remap 脚本在哪个目录里,将来可以复用的对吧”
脚本没有保存为文件。文档里写的是“脚本见本次 commit 历史”。
教训:好用的工具要留下来。于是整理成了 tools/remap_palette.py,支持自动备份、dry-run 预览、选择性跳过。从此每次加新 terrain 只需要一行命令。
第九章:Soil 的纹理消失事件
加了 soil_on_grass 新地形对——提取、调色、一切顺利。直到打开编辑器:
“soil 的 filler 缺少了纹理,只有两个颜色(一深一浅)”
Soil 原本有 8 种深浅不一的棕色,丰富的翻土纹理。但 remap 脚本把它们全部归类为“dirt”,用暖米色 palette 映射——而 dirt palette 最暗才到亮度 0.55。Soil 原本 0.19 到 0.61 的亮度范围被压缩成了两个色调,纹理直接消失。
解决方案:给 soil 单独定义了一套 6 级深棕色 palette(从 #C3975F 到 #4B2F20),通过文件名自动识别该用哪套 palette。
紧接着又发现 soil_on_dirt 也坏了(“修复”时误删了配置)、grass 的亮色高光没被调色(亮度阈值差了 0.05)……连续三轮修复,总算所有颜色都对了。
第十章:编辑器里的小插曲
“我给 0-ground+water 层绑定的是 outside_tileset_blob.tres,但到了下面的 tilemap editor 窗口,就变成了 outside_tileset.tres”
仔细一看——编辑器底部写的是 “Matches Corners and Sides”,terrain 名字也全是 blob 的。其实显示的就是 blob tileset,只是两套系统的名字太像了。
“对 我被自己搞糊涂了 不好意思了”
大概是整个旅程里最轻松的一个“bug”。
尾声:132 分之 12,但 Pipeline 已经建好了
回头看这段旅程:
- 1 个对角线映射 bug
- 1 个数据库假锁
- 1 个根本性的架构问题(五格之困)
- 3 次失败的替代方案
- 1 次完整的架构重写
- 3 套调色 palette(grass / dirt / soil)
- 以及无数次“哈哈”和“rofl”
数据库里 132 个地形对,我们完成了 12 个。但更重要的是,整条 pipeline 已经建好了:
改两行配置 → 跑提取脚本 → 跑调色脚本 → 完成
剩下的 120 个?随时可以来。
