OpenFrontIO 地图制作全指南:用 Go 版 MapGenerator 从 PNG 生成可玩地图
OpenFrontIO 地图制作全指南:用 Go 版 MapGenerator 从 PNG 生成可玩地图
导读
OpenFrontIO(在线浏览器端 RTS 游戏)的全部游戏地图——从世界地图、大洲图到自定义虚构岛屿——都由仓库中的 Go 工具 MapGenerator 生成与维护。本文以 docs/Maps.md 为主线,系统讲解地图管线:如何从一张 PNG 灰度/蓝通道图片出发,配置 info.json 元数据,运行生成器产出二进制地图与缩略图,以及如何让新地图进入游戏。读完你将掌握创建一张可玩地图的完整实操流程、info.json 全部字段的语义、像素到地形的编码规则,以及生成器的命令行与日志调试技巧。
地图管线总览:PNG 像素 → 二进制地形
OpenFront 的地图制作遵循“源素材在 map-generator/assets/maps/,产物在 resources/maps/”的单向管线:
- 每个地图是一个文件夹
map-generator/assets/maps/<map_name>/,内含image.png(地形图)与info.json(元数据); go run .读取 PNG,主要依据蓝色通道(Blue channel) 把像素映射为Land / Water / Impassable三种地形类型,并为陆地/水域计算 Magnitude(幅度);- 生成器输出三档二进制地图数据(全尺寸、1/4、1/16)与 WebP 缩略图到
resources/maps/<map_name>/; - 同时自动重写 src/core/game/Maps.gen.ts(
GameMapType枚举与maps列表)和 resources/lang/en.json 的map段——无需任何手工步骤,地图即可在游戏中启用。
由于生成只依赖蓝色通道,“灰度图和其他格式完全受支持”,map-generator/assets/maps 下大量真实地图(如 fourislands、world)本身就是灰度 PNG。下图即一张真实的源地形图:深蓝底色代表海洋,四块灰度陆地轮廓的高低明暗对应不同海拔(Magnitude)。
安装与环境准备
前置条件:本机安装 Go(参考官方安装文档),并从仓库根目录进入 map-generator 目录执行:
cd map-generator
go mod download # 安装依赖
go run . # 为所有地图运行生成器
仓库根目录下执行
npm run gen-maps(见 package.json 的"gen-maps": "cd map-generator && go run . && npm run format")等价于对所有地图执行go run .,并额外跑一次格式化。
创建新地图:四步走
地图是自动发现的——生成器启动时通过 discoverMaps() 扫描 assets/maps/ 与 assets/test_maps/ 下的所有子目录(见 map-generator/main.go),info.json 就包含了游戏需要的全部信息。创建流程:
- 新建文件夹
assets/maps/<map_name> - 放入
assets/maps/<map_name>/image.png - 创建
assets/maps/<map_name>/info.json(格式见下文) - 运行生成器,只处理这一张地图:
go run . --maps=fourislands
默认不带 --maps 时处理全部地图;也支持逗号分隔的子集:
go run . --maps=northamerica,world
产物输出到 ../resources/maps/<map_name>(相对 map-generator 目录,即仓库根下的 resources/maps/)。
源码佐证:
parseMapsFlag()会校验传入的地图名是否真实存在(见 map-generator/main.go),未知地图名会直接报错map "xxx" is not defined;--workers(默认 4)控制并发处理的地图数,可调低以降低峰值内存(见 map-generator/main.go)。
创作 image.png:像素 → 地形编码规则
源图建议直接裁剪世界地图(超大文件需下载后使用 Gimp 等工具裁剪)。制图者应熟读 map-generator/map_generator.go 顶部文档中的完整映射表:
| 输入条件 | 地形类型 | Magnitude | 说明 |
|---|---|---|---|
| Alpha < 20 | Water | 距陆地距离 | 透明像素变为水域 |
| Blue = 106 | Water | 距陆地距离 | 专用的水色关键值 |
| #000(纯黑) | Impassable | 31(固定) | 虚空区域,不可占领/攻击/核爆 |
| Blue < 140 | Land(平原) | 0 | 钳制到最小幅度 |
| Blue 140–158 | Land(平原) | 0–9 | 低海拔 |
| Blue 159–178 | Land(高地) | 10–19 | 中海拔 |
| Blue 179–200 | Land(山地) | 20–30 | 高海拔 |
| Blue > 200 | Land(山地) | 30 | 钳制到最大幅度 |
实现细节(见 map-generator/map_generator.go):每个像素取 8 位 RGBA,先判断 alpha < 20 || blue == 106 记为水域,再判断纯黑记为不可通行,其余按公式 Magnitude = (clamp(blue,140,200) - 140) / 2 计算。红/绿通道完全不影响地形分类。
陆地 Magnitude 表示海拔(0–30),水域 Magnitude 表示距最近陆地的曼哈顿距离——生成器用 BFS 从海岸线水格向外扩散计算(processDistToLand,见 map-generator/map_generator.go)。
不可通行地形(Impassable Terrain)
纯黑像素(#000000 / rgb(0,0,0),alpha ≥ 20)被编码为不可通行地形,它在游戏中表现为:
- 不可被占领、攻击或核爆;
- 核弹轨迹不能越过它(与不能飞出地图边界同理);
- 渲染为地图背景色,使地图呈现非矩形轮廓。
可用它雕刻非矩形地图形状,或在不使用水域的前提下划分区域屏障。二进制编码为 isLand=1 + magnitude=31(即 0b10011111),且不计入陆地格数(见 packTerrain,map-generator/map_generator.go)。另外,紧邻不可通行地形的海域会被强制设置为深水幅度(setImpassableNeighborWaterDepth,同文件 L409-L429),避免边缘出现浅水渐变。
游戏内渲染与缩略图配色
- 游戏内每格颜色由 Terrain Type + Magnitude 动态计算:海洋默认色定义在
src/client/render/gl/render-settings.json(可在设置中修改),地形颜色计算见 src/client/render/gl/utils/ColorUtils.ts; - 缩略图则使用独立的配色(
getThumbnailColor,map-generator/map_generator.go):不可通行与水域为透明、海岸陆地rgb(204,203,158)、平原/高地/山地为绿色到灰白色的渐变。
编写 info.json:地图的灵魂
info.json 决定了国家出生点、起始坐标、旗帜、频率、特殊规则等全部地图元数据。以下为 README 中的完整示例:
{
"id": "MySampleMap",
"name": "My Sample Map",
"translation_key": "map.mysamplemap",
"categories": ["europe", "featured"],
"multiplayer_frequency": 4,
"featured_rank": 5,
"layers": [
{
"id": "roads",
"placement": "land",
"nukeable": true
}
],
"nations": [
{
"coordinates": [396, 364],
"name": "United States",
"flag": "us"
},
{
"coordinates": [512, 280],
"name": "Canada",
"flag": "ca"
}
]
}
仓库中真实地图 fourislands/info.json 展示了更丰富的用法:4 个虚构国家(Korinthal、Lunareth 等,旗帜为空串)、team_frequency: 30、special_team_count: 4、forced_modifiers: ["isWaterNukes:50"] 以及 teamGameSpawnAreas 分区域出生配置。
字段总表
| 字段 | 必填 | 描述 |
|---|---|---|
id |
✅ | 地图的 CamelCaseName。必须与 assets/maps/<map_name> 文件夹名匹配(忽略大小写),会成为 GameMapType 枚举键 |
name |
✅ | 规范名称——GameMapType 枚举值。地图发布后绝不能修改(属于 wire 格式,写入游戏记录) |
translation_key |
✅ | resources/lang/en.json 中的键,必须为 map.<foldername> |
categories |
✅ | 一个或多个分类字符串(见下),不能重复,至少一项;在选图器中分组 |
multiplayer_frequency |
地图在公共多人播放列表中的出现权重,0(或省略)表示不进入轮换;作为各模式频率未设置时的回退值 |
|
ffa_frequency |
FFA 大厅轮换权重,覆盖 multiplayer_frequency;0 表示不进入 FFA;省略则回退 |
|
team_frequency |
团队大厅轮换权重,覆盖 multiplayer_frequency;0 表示不进入团队;省略则回退 |
|
special_frequency |
特殊大厅轮换权重,覆盖 multiplayer_frequency;0 表示不进入特殊;省略则回退 |
|
disabled_modifiers |
特殊对局中永不为此图掷出的修饰器数组(如 ["isRandomSpawn"]) |
|
forced_modifiers |
特殊对局中总是强制开启的修饰器数组。纯键(如 "startingGold5M")或 "key:percentage"(如 "goldMultiplier:75" = 75% 概率)。计入 3 修饰器上限 |
|
display_name |
写入 resources/lang/en.json 的英文显示名,默认取 name。仅当显示名与规范名不同时设置(如 "MENA"、"Europe (Classic)") |
|
featured_rank |
精选网格中的位置(1 = 第一)。要求 categories 含 "featured";未排名的精选图按字母序排在已排名之后 |
|
special_team_count |
团队/特殊对局的偏好队伍数——见 src/server/MapPlaylist.ts 中的 SPECIAL_TEAM_MAPS。必须为 0 或 ≥ 2(不允许 1) |
|
custom_tribes |
部落名数组,或 { "name": "...", "coordinates": [x, y] } 对象数组。坐标为可选(省略则随机出生)。部落名必须唯一且不得与 nations[].name 冲突,空串被拒绝 |
|
themes |
用于部落名生成的主题字符串数组 | |
layers |
渲染在地形与领土之间的图层定义数组(见下) | |
nations |
国家对象数组(见下) |
校验证据:所有字段的合法性(id 大小写、translation_key 前缀、频率 ≥ 0、修饰器白名单、layer id 字符集与
placement取值、自定义部落重复/冲突等)都由loadMapInfos()逐项验证,见 map-generator/codegen.go。合法修饰器键全集:isRandomSpawn、isCompact、isCrowded、isHardNations、startingGold1M/5M/25M、goldMultiplier、isAlliancesDisabled、isNukesDisabled、isSAMsDisabled、isPeaceTime、isWaterNukes、isDoomsdayClock。
Categories 合法取值
categories 的每一项必须是下列之一(对应 map-generator/codegen.go 中的 categoryOrder,同时也是选图器的分类显示顺序):
featured, new, world, continental, europe, asia, north_america, africa, south_america, oceania, antarctica, countries, cosmic, fictional, arcade, tournament
横跨多个区域的地图(如黑海、白令海峡)可以列出多个分类;加入 featured 可使地图显示在选图器的精选区。
Nations 国家对象
| 字段 | 必填 | 描述 |
|---|---|---|
name |
✅ | 国家显示名 |
coordinates |
[x, y] 出生点坐标。原点在左上角,x 向右延伸,y 向下延伸。省略则随机出生 |
|
flag |
ISO 3166 国家代码(如 "us"、"ca"、"de")。支持的完整代码列表见 resources/countries.json,对应 SVG 旗帜存放于 resources/flags |
Layers 地图图层
图层是渲染在地形与领土之间的 PNG 叠加层,适合做装饰(道路、地标等)。每个图层是 assets/maps/<map_name>/ 下的独立 PNG 文件,命名为 <id>.png:
| 字段 | 必填 | 描述 |
|---|---|---|
id |
✅ | 图层唯一标识,也是 PNG 文件名(不含扩展名)。必须为字母数字(允许连字符),不得为 "image"(保留名) |
placement |
✅ | "land" 或 "water"——图层位于陆地(及不可通行地形)还是水域格上 |
nukeable |
若为 true,图层在核弹影响半径内被永久摧毁。默认 false |
图层显示名存放在 resources/lang/en.json 的 map_layers 段(以图层 id 为键);玩家可在图形设置中关闭图层。
实现佐证:
processMap()会逐图层校验placement、PNG 是否存在,以及图层 PNG 尺寸必须与地图尺寸完全一致(见 map-generator/main.go),随后把图层 PNG 复制到输出目录。
输出文件清单
每次运行(即便只指定 --maps)都会重新生成全部产物:
| 文件 | 说明 |
|---|---|
resources/maps/<map_name>/manifest.json |
JSON 元数据,含所有缩放下地图的宽高与陆地格数 |
resources/maps/<map_name>/map.bin |
全尺寸二进制地图数据(地形类型 + 幅度打包) |
resources/maps/<map_name>/map4x.bin |
1/4 缩放(边长减半)二进制数据,用于小地图 |
resources/maps/<map_name>/map16x.bin |
1/16 缩放(边长再减半)二进制数据,用于小地图 |
resources/maps/<map_name>/thumbnail.webp |
地图 WebP 缩略图 |
src/core/game/Maps.gen.ts |
生成的 TypeScript(GameMapType 枚举与 MapInfo 对象列表),由所有地图的 info.json 构建 |
resources/lang/en.json |
map 段被重写为每张地图的显示名 |
二进制打包格式
packTerrain() 将每个格子打包为一个字节(见 map-generator/map_generator.go):
- Bit 7:陆地(1)/ 水域(0)
- Bit 6:海岸线(Shoreline)
- Bit 5:海洋(Ocean)
- Bits 0–4:Magnitude(0–31)。水域取
距离/2
不可通行格编码为 0b10011111(isLand=1、magnitude=31),不计入陆地格数。数据按行主序(y*width+x)排列,与游戏二进制地图格式的扫描顺序一致。
多缩放层级
createMiniMap() 将 2×2 区块合并为 1 格输出,优先级 Water > Impassable > Land——水域永远优先,以保证小地图上的窄河道完整保留(寻路在小地图上运行,需要准确的水体),见 map-generator/map_generator.go。缩略图则基于 1/4 缩放数据以 0.5 比例进一步缩放,并以 WebP(质量 45、方法 4)编码(同文件 L184-L191、L235-L259)。
命令行参数与日志调试
go run . --maps=world,eastasia,big_plains # 逗号分隔的待处理地图列表
go run . --log-level=debug # 显式设置日志级别
go run . -v # 追加日志,并以 [mapname] 前缀标识
go run . --workers=2 # 并发数(默认 4),调低可降低峰值内存
日志级别取值:ALL、DEBUG、INFO(默认)、WARN、ERROR。生成器基于 slog 输出标准级别的日志,另加一个 ALL 级别。
--verbose/-v:--log-level=DEBUG的别名;--debug-performance(代码中注册名--log-performance):追加基于性能建议的日志,同时设置--log-level=DEBUG;--debug-removal(代码中注册名--log-removal):追加被移除的岛屿/湖泊位置与尺寸日志,同时设置--log-level=DEBUG。
debug-performance 与 debug-removal 是在 DEBUG 级别之上的可选输出(内容可能很长):即使日志级别为 DEBUG,也必须显式传入对应 flag 才能看到相应日志。而 --log-level=ALL 会输出所有日志,包括全部 DEBUG 层级,无论是否传了具体 flag。相关 flag 定义见 map-generator/main.go。
生成后的自动格式化与代码生成
go run . 的最后一步会用仓库的 Prettier 配置格式化每个写出的文件(调用 npx prettier --write),因此输出直接符合 npm run format 的规范,无需额外格式化。若 npx prettier 不可用(如尚未执行 npm ci),生成仍会成功但打印警告,此时需要手动在根目录运行 npm run format(见 map-generator/main.go)。
代码生成的两项关键产出(见 map-generator/codegen.go):
- src/core/game/Maps.gen.ts:头部声明“由 map-generator 生成,禁止手改;用
npm run gen-maps重新生成”。内容包含GameMapType枚举(每个成员带注释可直接跳转到对应 info.json)、MapCategory联合类型、SpecialModifierKey联合类型、MapInfo/MapLayer/CustomTribe接口,以及按categoryOrder排列的maps列表。枚举示例:FourIslands = "Four Islands"、AmazonRiver = "Amazon River"; - resources/lang/en.json:
map段以文件夹名(小写)为键写入每张地图的display_name(默认name),非地图键(如featured、random)会被保留;map_layers段同步收录所有图层 id。整个文件以排序键往返序列化,与tests/EnJsonSorted.test.ts的排序要求保持一致(见 map-generator/codegen.go)。其他语言的翻译通过 Crowdin 平台管理。
在游戏中启用:零手工步骤
一切都由 info.json 在生成器运行时推导,没有手工步骤:
GameMapType枚举与maps列表(每图一个MapInfo)写入 src/core/game/Maps.gen.ts——不要手工编辑该文件;- resources/lang/en.json 的
map段被重写为每张地图的display_name(或name)。
地图尺寸与性能建议
生成器的自动清理与尺寸规范化规则(见 map-generator/map_generator.go 的常量定义):
- 小于 30 格(像素)的岛屿自动移除;
- 小于 200 格的水体自动移除;
- 地图宽高被规范化为 4 的倍数,超出
Width - (Width % 4)/Height - (Height % 4)的像素被裁剪(这是小地图整数降采样所需)。
水域处理还有一个重要细节:最大水体被标记为 Ocean(海洋),其余小水体若低于阈值则按相邻格多数类型填充回填(majorityNeighborType,平局优先级 Water > Impassable > Land),见 map-generator/map_generator.go。
出于性能考虑:
- 地图面积建议在 200 万到 300 万像素之间(常量
minRecommendedPixelSize/maxRecommendedPixelSize,超出会在 DEBUG 日志中提示); - 陆地格数超过 300 万的图不建议使用(
maxRecommendedLandTileCount); - 平均陆地格数约为 100–200 万。
地图在多人播放列表中的频率权重最终由 src/server/MapPlaylist.ts 消费(SPECIAL_TEAM_MAPS 读取 special_team_count,见该文件 L62、L261、L679)。
开发辅助工具
- 格式化生成器代码:
go fmt . - 输出生成器文档(CLI 工具需
-cmd,且不暴露 API,故加-u -all显示全部未导出值):go doc -cmd -u -all - 已知缺陷:
go doc的-http模式不尊重其他 flag,只渲染 README。
收尾:CREDITS 与旗帜
- 更新 CREDITS:在地图发布前,向 CREDITS.md 添加许可证与署名信息;不确定某地图素材的许可证能否使用时,先发起 issue 或在 Discord 询问再动手;
- 添加新旗帜:SVG 放入
resources/flags/<iso_code>.svg,并在 resources/countries.json 登记该国代码,即可在nations[].flag中引用。
参考资料
- 地图入口文档:docs/Maps.md
- 生成器完整手册:map-generator/README.md
- 生成器源码:map-generator/main.go、map-generator/map_generator.go、map-generator/codegen.go
- 真实配置样例:map-generator/assets/maps/fourislands/info.json 及 map-generator/assets/maps 下全部地图
- 生成产物:src/core/game/Maps.gen.ts、resources/lang/en.json
- 播放列表逻辑:src/server/MapPlaylist.ts;地形渲染配色:src/client/render/gl/utils/ColorUtils.ts
