← 回成果库

用一段文本描述一张地图 —— 五个方案对比 + MAPTEXT v0.1 最小可跑实现

日期:2026-08-02 | 项目:M-378 / P577 | 在线试https://metazero-terminal.pages.dev/maptext/


0. 先说结论

没有一种格式能同时伺候 8×8 和 8000×8000。 这不是技术不够,是需求本身矛盾:

8×88000×8000
你想要什么看得见、随手改一句话生成整个世界
该写什么每一格都写出来(数据)一条格子都别写(规则)
文本长度64 个字符6400 万格 → 不可能

所以正确答案不是"选一个格式",而是:

> 让"画出来的数据"和"写出来的规则"变成两层,能叠加。

> 小图全画、大图全写规则、中图混着来 —— 画了的地方以画的为准。

外加第二条,它决定了这门语言能不能规模化:

> "是什么"和"长什么样"必须分开。 格子里只写字符 AA 是 150 米高的青色退台塔,由图例说了算。

> 于是换一行图例 = 整座城换风格,格子一个字都不用动。

我按这两条做了 MAPTEXT v0.1,代码和可试的网页都在下面第 4 节。


1. 先想清楚:一座城到底由哪些东西组成(分层)

这是设计任何地图格式之前必须先回答的问题。我拆成 6 层,层与层之间只有"挂靠"关系,没有互相纠缠 —— 这样每一层都能独立换掉:

`

L5 氛围 天空色 · 调色板 · 雾 · 昼夜 ← 改一行,整张图换季

L4 活物 车 · 行人 · 无人机 ← 只给"在哪种地块上撒多少个",不给坐标

L3 地面附属 树 · 路灯 · 长椅 · 报刊亭 · 广告牌 ← 挂在【地块】上,不挂在楼上

L2 楼身装饰 霓虹带 · 天线 · 塔顶灯 · 阳台 · 招牌 ← 挂在【楼】上

L1 楼体 占地 · 高度 · 边数 · 体块形态 · 屋顶 ← "楼"这个大元素本身

L0 地块 马路 · 人行道 · 广场 · 公园 · 水面 · 空地

`

为什么 L3 和 L2 一定要分开? 因为树不是楼的一部分 —— 拆掉楼树还在。把它们混在一起,以后想"只换建筑风格不动绿化"就做不到了。GTA 那种复杂度的城市,本质就是这 6 层各自往里堆更多种类,不是多出第 7 层。

每层的具体词表(这就是 v0.1 的全部元素):

属性 / 词
L0 地块road walk plaza park water empty build
L1 楼体h 高度区间 | w 占格比例 | sides 边数 | form 体块(方塔/锥塔/退台/双子/圆柱/斜切/桶楼/裙楼塔)| roof 屋顶(平/尖/冠/穹/天线/天台)| colorwin 窗户密度 | rot 朝向
L2 楼饰neon antenna beacon sign balcony crown pipe holo
L3 地饰tree lamp bench kiosk billboard barrier hydrant rock grass
L4 活物car boton= 哪种地块,speed= 区间)
L5 氛围sky palette seed

2. 五个方案(这是主席要的"多几个")

方案 A · ASCII 网格 + 图例 —— "看得见的地图"

`

@legend

A tower h=70..150 form=taper roof=spire color=cyan deco=neon,antenna

b low h=10..22 form=box roof=flat color=amber

. road , walk T park deco=tree*5

@grid

..........

.,bbb,.,,.

.,bAb,.TT.

..........

`

文本本身就是地图缩略图,一眼看懂一个字符一格 → 上限约 200×200,再大人就读不动了
改一个字符 = 改一格,git diff 一目了然字符集有限(约 60 种可打印字符 = 最多 60 种地块)
AI 生成得极好(它本来就擅长画字符画)8000×8000 = 6400 万字符,纯粹不可能

适用: 8×8 到 200×200 的手工关卡、剧情场景、迷宫。


方案 B · 行程编码网格(RLE)—— "同样是画,但压过"

`

@grid_rle

10.

1. 1, 3b 1, 1. 1, 1, 1.

1. 1, 1b 1A 1b 1, 1. 2T 1.

`

大片相同区域(海、荒地、郊区)压缩率极高人读不出来了 —— 方案 A 最大的优点没了
仍然是精确数据,不引入随机最坏情况(棋盘格)比 A 还长
好写好解析仍然是 O(面积),8000×8000 还是撑不住

适用: 500×500 到 2000×2000、且有大片同质区域的地图。建议只当导出格式,不当手写格式。


方案 C · 分区 + 规则 —— "写规则不写格子"(能到 8000×8000 的那个)

`

@meta size=8000x8000 seed=42 road_every=5

@zones

core = circle(4000,4000,r=900) h*=1.7 density=.95

mid = ring(4000,4000,900,2400) h*=1.3 density=.86

edge = ring(4000,4000,2400,6000) h*=0.6 density=.62

@rules

near(park,2) => deco+=tree*2

rand<.03 => h*=1.8, roof=antenna, deco+=beacon

`

文本长度和地图大小完全无关 —— 6400 万格也就 30 行你写完看不见结果,必须渲染出来才知道长啥样
天然支持无限生成(换 seed 就是新城)想精确控制某一格做不到
逻辑是自洽的(市中心高、郊区矮是规则推出来的,不是硬摆的)规则多了会互相打架,需要明确的优先级

适用: 1000×1000 以上、开放世界、无限地图。


方案 D · 道路图 + 地块细分 —— "先修路再盖楼"(最像真城市)

`

@roads

main = line(0,0)->(8000,0) w=48

ring = circle(4000,4000,r=1800) w=36

grid = lattice(spacing=220, jitter=.18, curve=.05)

@parcels

subdivide(min=40, max=120, setback=6)

`

先生成道路网(可以是放射状、环状、有机弯曲的),道路围出来的多边形再切成地块,地块上再盖楼。CityEngine / Cities:Skylines 的城市生成走的就是这条路。

街道形态最真实 —— 能做环路、放射路、沿河弯道,不是横平竖直的棋盘实现最重(要做多边形切分、道路相交、地块朝向)
地块自然大小不一,天生不呆板AI 手写这种文本比较吃力(几何参数不直观)
是"从 8×8 玩具走向 GTA"的必经之路不适合手改单个地块

适用: 想要真实城市感的正式项目。建议作为 v0.2 的升级方向,不是 MVP。


方案 E · 图层叠加 —— "像 Photoshop 那样一层层叠"

`

@layers

terrain = noise(scale=.002) -> water<.30, land

height = gradient(center) * noise(scale=.01)

roads = grid(80) + radial(center, 8)

style = voronoi(12) -> palette[i]

props = scatter(density=.4, on=land, avoid=roads, r=6)

`

每一层都是一个"函数",最后合成。

最紧凑 —— 5 行就是一个世界,且天然无限最抽象,写的人要懂噪声/Voronoi 这些概念
换生物群系只是换一层,城市/荒野/雪原一套语法通吃单点可控性最差
和地形(山、河、海岸)结合最自然需要一个函数库,实现量比 C 大

适用: 荒野 / 地形为主的世界;和 C 组合最强(E 管地形,C 管城市)。


五方案横评

A ASCIIB RLEC 规则D 道路图E 图层
人能直接看懂⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
AI 好不好写⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
单格可控⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
能到 8000×8000⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
无限随机但有逻辑⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
实现难度(越少越好)12386

推荐:A + C 混合作为 v0.1(已实现),D 或 E 作为 v0.2 升级方向。


3. MAPTEXT v0.1 语法(A+C 混合,已落地可跑)

`

@meta size=512x512 cell=34 seed=2026 road_every=5 view=62 sky=#060a14

@legend A tower h=70..150 form=setback roof=deck color=cyan deco=neon,balcony weight=1

. road , walk deco=lamp?.3,tree?.45 T park deco=tree*6,bench*2

@grid (可选)直接画的 ASCII 地图

@zones core = circle(256,256,r=52) h*=1.7 density=.95

@rules near(park,2) => deco+=tree*2

@spawn car 90 on=road speed=12..34

`

三个语法细节,都是踩过坑才定的:

1. deco=tree*4,lamp?.5 —— *n 是"放 n 个",?p 是"以 p 的概率放"。既能确定摆放,也能随机点缀。

2. h=70..150 区间随机、h*=1.7 区域倍数、h+=20 规则加成 —— 三种改高度的方式互不冲突,按 图例 → 分区 → 规则 的顺序叠加。

3. 所有随机都是 hash(seed, x, z, 用途) —— 不是 Math.random()。所以同一份文本永远长出同一座城;只重算一个区域也不会影响别处。

优先级(冲突时谁说了算): @grid 画的 > @rules > @zones > @legend 默认值


4. 已经做出来的东西(不是提案,是能跑的)

在线试:https://metazero-terminal.pages.dev/maptext/

左边写文本,右边直接长出 3D 城市。带 4 个模板 + 一个「🎲 随机生成」按钮(每点一次吐一份新的合法 MAPTEXT,直接就是 AI 可以模仿的样例)。

文件是什么
web/terminal/maptext/maptext.js纯逻辑:解析器 + 求值器 + 生成器 + 模板。没有任何渲染依赖,可以直接搬到别的引擎(Unity/Godot/Three.js 都行)
web/terminal/maptext/index.html编辑器 + three.js 渲染器

核心 API(三个函数):

`js

import { parse, build, generate } from './maptext.js';

const model = parse(文本); // 文本 → 结构(带逐行报错)

const world = build(model, {view: 96}); // 结构 → 一格一格的具体结果(含随机求值)

const 新文本 = generate({size:[128,128]}); // 随机吐一份新的 MAPTEXT

`

build() 返回的 world.cells 就是渲染端要的一切:每格的类型、高度、颜色、形态、屋顶、装饰清单、朝向。换渲染引擎只要重写这一步。


5. 实测数据(全部是真跑出来的,不是估的)

检查项结果
8×8 手画 → 3D10×8 格 → 17 栋楼 · 120 件装饰 · 18 个活物 · 0 报错
改 ASCII 一个字符把 2 个 T(公园) 改成 A(塔) → 楼数正好 17 → 19
同种子可复现连跑两遍,前 400 格的类型+高度完全一致
换种子seed 2026 → 2027,同样规则长出不同的城
512×512 一格不画全图 262,144 格,只靠 4 条分区 + 5 条规则 长出城
分区效果对不对得上实测楼高比 core:mid = 1.30(文本写 1.31)、mid:edge = 1.98(文本写 2.17)→ 误差 <12%
规则真的加东西「公园旁多种两棵树」开:3195 棵 → 关:893 棵
8000×80006400 万格,文本 28 行 / 1090 字;解析+求值 23ms,连建几何一共 108ms(只渲染镜头附近的窗口)
换图例 = 换世界荒野模板语法一字未改,只换 @legend(松/橡/岩/湖)→ 长出 5,029 个自然物
随机生成器连生成 12 份,全部 0 报错、都长出了城(每份约 27 行)
写错了故意写 4 处错 → 逐行报出第几行错在哪,页面不崩

18 / 18 通过。


6. 红队:这套方案哪里可能是错的

最强反对:「分区+规则」生成的城市,一定是"统计上合理、局部上无意义"的。

规则能保证"市中心高、郊区矮",但保证不了"这条街有故事"。GTA 之所以是 GTA,恰恰是那些手工摆的地标

回应(也是设计里已经处理的): 这正是为什么 @grid@zones 必须能叠加而不是二选一 —— 主城手画、郊区交给规则。v0.1 已经支持:画了的格子以画的为准,没画的按规则长。

可证伪锚: 如果做到 v0.2 时发现"手画区和规则区的接缝处明显违和、必须额外写一堆过渡规则",那说明这个叠加模型不够,需要升级到方案 D(道路图先行,地块再分配给手画/规则)。这一条到时能被现实检验。

已知的缺口(诚实列出,没有掩盖):


7. 建议的下一步

1. 先用起来:让 AI 按第 3 节的语法生成 10 张不同风格的城,粘进网页看看哪种风格对味 —— 这一步能最快暴露语法缺什么。

2. 加地形层(方案 E 的一小块):@terrain height=noise(...),让地面有起伏。

3. 升级道路(方案 D):把 road_every=5 的棋盘换成真正的道路图,这是最像 GTA 的一步。

4. 把它接回知识城市:现在的知识城市是硬编码生成的,改成读 MAPTEXT,就能用一段文本换掉整座城。


*支撑文件:web/terminal/maptext/maptext.js(纯逻辑)· web/terminal/maptext/index.html(编辑器+渲染器)*