把家乡变成 Minecraft 世界:Arnis 现实城市生成工具实用指南
【免费下载链接】arnisGenerate any location from the real world in Minecraft with a high level of detail.项目地址: https://gitcode.com/GitHub_Trending/ar/arnis
Arnis 是一款开源工具,用真实的 OpenStreetMap 地理数据和海拔数据,把家乡、大城市甚至自然景观复刻到 Minecraft Java 版、基岩版和 Luanti(Minetest)里。本文追踪一份地理数据从经纬度到方块的完整旅程,讲清它内部怎么运作、哪些设计让"1:1 还原现实"成为可能。
先认识 Arnis:一份地理数据的旅行
一句话定位:Arnis 把现实世界"扫描"成一个可以走进去的体素城市。🌍
整个过程像寄一封跨次元的快递:
- 输入:你在地图上圈一个矩形范围(比如你的小区、一座老城),Arnis 向 OpenStreetMap 服务器要这个范围里的地图数据——哪些是建筑轮廓、哪些是道路、哪些是水域;同时下载真实海拔数据。
- 翻译坐标:地球是球面,用经纬度描述;Minecraft 世界是扁平网格,用 x/z 整数格子描述。这一步把每个经纬度点换算成游戏坐标,误差越小,建筑落点越准。
- 逐个要素加工:建筑轮廓被"挤"成有墙有窗的方块楼体,道路按等级生成不同宽度的路面,水域填成深浅不同的水体,树木从内置的树种库里挑合适的栽上。
- 写入世界:所有方块按 Minecraft 的存档格式(Java 版的 Anvil 区块结构、基岩版的 .mcworld 包或 Luanti 世界)逐格落盘。
- 输出:生成完直接把文件夹丢进
.minecraft/saves,开游戏就能看到 1:1 比例的家乡。
下图就是 Arnis 生成的城市预览,密集的建筑群、绿地和路网都来自真实地图数据。
核心链路拆解
从地球一角到数字网格:坐标换算
coordinate_system 下分成两套坐标类:地理坐标(llpoint,经度+纬度的点)和笛卡尔坐标(xzpoint,游戏里的 x/z 平面点)。经纬度之间算距离要用球面三角,而游戏里的计算只需要简单的加减乘除,所以必须先把前者"展平"到后者。Arnis 走的是 Web Mercator 投影(也就是主流网络地图用的那种投影),因为它的网格切割方向和 OSM 瓦片一致,后续按瓦片取数据时可以直接对齐。
这套换算还支撑着一个实用功能:边界框工具。
界面上拖出的矩形,实时换算成min_lat,min_lng,max_lat,max_lng四个数——这正是命令行模式需要的参数。界面上的所见即所得,本质上就是这一层坐标换算在做双向翻译。
取数:一次圈选,三类数据
r(数据获取模块)负责所有网络请求。这里有个容易忽视的取舍:三种生成模式取的数据量差异极大。
geo-terrain(默认):OSM 要素 + 真实海拔,生成带地形的完整城市;geo-only:只取 OSM,地面是平的,适合城市地形不重要的场景;terrain-only:只取海拔,完全不碰 OSM,连公开服务器都不打扰。
海拔数据按瓦片分块下载并缓存到本地,因为同一区域反复生成(或调整参数重跑)时,重新拉一遍海拔纯属浪费。取 OSM 数据时程序还会带上带版本号的 User-Agent 标识自己,这是对公开服务的礼貌——大批量取数时,让服务器管理员知道流量来自哪个版本、哪个用户。
从地图标签到方块:要素加工流水线
OSM 数据本身只是标签:一条建筑轮廓线带height=24,一条路带highway=residential。真正的工作是把标签变成方块布局,这块逻辑集中在 element_processing,按要素类型拆成独立文件:buildings.rs 管楼房、highways.rs 管道路、water_areas.rs 管水域,还有桥梁、树木、历史建筑等二十多个子处理器。
几个关键手法:
- 建筑:把二维轮廓沿高度方向"挤出"成楼体(extrusion,类似剪纸插起来),再根据屋顶标签决定平顶还是坡顶,墙面贴窗、贴门,避免"一整面灰墙"的塑料感;
- 道路:按 highway 等级映射到不同宽度和路面材质,主干道自然比小巷宽;
- 区域填充:像"建筑占住哪几格"这类判断由 floodfill(洪泛填充,从边界点出发逐格扩散标记内部)完成,配合位图缓存避免重复计算——因为一个城市的建筑可能上万栋,每栋都重新做区域判断会慢到不可接受。
插件化的拆分带来实际好处:想加新要素类型(比如新的公共设施),只需要新写一个处理器并注册,不用动建筑或道路的逻辑。
落盘:一套逻辑写三种存档格式
Minecraft Java 版存成 Anvil 格式的 region 文件,基岩版打成单个 .mcworld 压缩包,Luanti 又是第三种结构。如果三种格式各写一套生成逻辑,任何一处修改都要改三遍。所以 world_editor 用 common.rs 定义统一的"世界待修改"数据结构——上层只管"在 (x, y, z) 放一个方块",由 java.rs、bedrock.rs、luanti.rs 分别负责把它编码进各自的区块格式。
值得细看的设计决策
确定性随机数,保证分块计算不"穿帮"。生成大区域时,世界按区块分块并行处理,一栋跨区块边界的楼可能在不同区块里各被处理一次。如果随机数靠全局状态(比如墙的颜色、树的品种),同一栋楼两个区块里会抽到不同结果,拼起来就是半边红墙半边蓝墙。deterministic_rng.rs 的解法很干净:用 OSM 元素 ID 本身做随机种子,同一个元素无论被处理几遍、在哪个线程处理,抽到的"随机值"都相同。代价是几乎没有——一次哈希换来了并行分块下的结果一致性,这对"流式/分块"架构是必要条件而非优化项。
基岩版方块映射表,抹平两个版本的分歧。Java 版和基岩版对同一方块的命名、ID、数据值并不完全一致(比如玻璃、石砖的变体)。bedrock_block_map.rs 维护了一张"Java 方块 → 基岩方块"的映射表,让同一份生成逻辑在两个版本里产出视觉一致的世界。没有这张表,要么为基岩版重写生成逻辑,要么玩家打开世界看到一片"缺失材质"。
内存与并行的工程取舍。大城市生成时同时活跃的方块数据量轻松达到数 GB,主程序用 mimalloc 替代系统分配器,注释里写明了原因:并行分块处理产生大量小对象分配,mimalloc 在这种"高并发小块"负载下表现更好;线程池则限制在 90% CPU 占用,留出余量保证系统流畅。这类选择用户感知不到,但直接决定了"普通硬件能不能跑动一座城市"。
上手与延展
图形界面:下载 release 包运行,用矩形工具在地图上圈选区域,选目标世界格式,点 Start Generation 即可。
可调参数包括世界缩放比例(1:1 或放大)、出生点位置、是否生成建筑内部房间等。
命令行:适合自动化和服务器场景,核心就两个参数——输出目录和范围框。
cargo run --release -- --output-dir="~/.minecraft/saves/worldname" --bbox="min_lat,min_lng,max_lat,max_lng"想只要地形加--mode terrain-only,要平地面加--mode geo-only;加--bedrock则输出基岩版世界。
能往哪走:代码库里已经能看到延伸方向的痕迹——models_3d/与wikidata_3d_models.json表明项目正在用 Wikidata 数据把现实地标(比如雕像、纪念碑)以精细 3D 模型的形式摆进世界,而不是只给个简化方块体;assets/tree-packs/下按大洲组织的数千棵树种模型,说明植被还原也在走"真实物种 → 体素模型"的路线。对贡献者来说,最自然的切入点也是这两处:给某类要素补一个处理器,或者往树库里添模型。作者已用同一套核心逻辑推出了浏览器端生成服务(无需安装、支持更大地图),这也印证了"取数—加工—落盘"严格分层后,换一套运行载体不需要重做核心管线。
【免费下载链接】arnisGenerate any location from the real world in Minecraft with a high level of detail.项目地址: https://gitcode.com/GitHub_Trending/ar/arnis
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考