Godot 小地图战争迷雾:探索记录、图标显隐和存档体积要一起算

讲解 Godot 小地图战争迷雾在开放区域中的网格记录、图标显示、渲染更新和存档压缩策略。

小地图迷雾不是一张黑图盖上去那么简单

开放区域或箱庭关卡里,小地图战争迷雾承担了三个职责:告诉玩家哪里去过,隐藏尚未发现的兴趣点,给探索进度和成就提供依据。很多 Godot 项目一开始用一张半透明黑图覆盖小地图,玩家走到哪里就擦掉哪里。原型阶段很好用,但内容一多问题就来了:地图尺寸变大后纹理更新卡顿,存档里保存整张图片太大,图标不知道按“看见过”还是“当前可见”显示,多层地图和室内区域也很难处理。

我更推荐把迷雾当成数据系统,而不是纯渲染效果。底层用网格或瓦片记录探索状态,渲染层只是把这些状态画成纹理;图标层根据探索规则决定显示;存档层用 bitset 或 RLE 压缩。这样地图 UI、任务系统、成就系统都能读同一份探索记录,不会出现小地图看见了,任务却说没发现的矛盾。

网格精度要从玩法反推

迷雾网格不是越细越好。假设一张 1 公里见方的地图,如果用 1 米一个格子,就是一百万个格子;如果每格只存一个 bit,压缩前也不算大,但运行时刷纹理、合并 dirty rect、同步图标都会变复杂。大多数小地图只需要 4 到 8 米一个格子,室内或迷宫可以更细。关键是玩家在 UI 上能否感知到探索边界,而不是物理位置是否精确到厘米。

Godot 中可以用 FogGrid Resource 保存区域配置:世界原点、格子尺寸、宽高、楼层 id、是否循环地图。角色每隔一小段距离或固定时间刷一次探索圆,而不是每帧刷。刷子半径按角色视野或地图设计决定,骑乘、飞行、登高点可以有不同半径。这样探索记录和实际玩法绑定,后续做侦察塔、望远镜、扫描技能也能复用。

探索状态至少有三层

只用 explored / unexplored 两态往往不够。更实用的是三层:未发现、曾经探索、当前可见。未发现区域完全遮挡,不显示普通图标;曾经探索区域显示地形和已发现的固定点,但不显示临时敌人;当前可见区域显示动态信息。单机探索游戏可以弱化当前可见,多人或潜行游戏则非常需要。

如果只是普通 RPG 小地图,可以把当前可见放在运行时,不进存档;曾经探索进存档。图标也要分类型:传送点可能只要发现过就显示,采集物可能需要当前可见或最近扫描,任务目标可能即使未探索也显示方向但不显示精确位置。把规则写在 POI 数据里,字段如 visibility_policy: discovered/current/quest_override,不要在小地图控件里硬编码。

数据流和渲染流

迷雾系统的关键是把写数据、刷新纹理、筛选图标分开。下面的流程适合 Godot 4 项目:

flowchart TD
    A["World Position"] --> B["FogGrid 坐标映射"]
    B --> C["Explore Brush 写入格子"]
    C --> D["Dirty Rect 合并"]
    D --> E["ImageTexture 局部刷新"]
    C --> F["POI 可见性规则"]
    F --> G["Minimap Icon Layer"]
    C --> H["Bitset 压缩存档"]

角色位置先映射到 FogGrid 坐标,再用圆形或扇形 brush 写格子。写入时收集 dirty rect,下一帧或下一批统一刷新 ImageTexture。POI 系统订阅探索变化,但不需要每个格子变化都重新筛全图,可以按区域或网格桶查询。存档层不保存纹理,只保存 bitset 和版本号。

局部刷新比整图重建重要

Godot 的 Image 和 ImageTexture 可以运行时更新,但如果每次移动都重建整张 2048x2048 迷雾纹理,低端设备会有明显尖峰。更好的做法是把探索变化合并成 dirty rect,只更新受影响区域。若纹理很大,还可以按 chunk 拆成多张小纹理,例如 128x128 或 256x256 一个块。玩家移动时通常只影响附近几个块。

小地图显示还需要平滑边缘。数据格子是方的,直接画会像马赛克。可以在生成纹理时对边缘做简单羽化,或者用 shader 采样邻近格子。注意,视觉羽化不应该反写到数据层,否则探索面积会被越刷越大。数据层保持离散,渲染层负责好看,这是很多迷雾系统可维护的前提。

存档和版本迁移

探索记录非常适合 bitset。每个格子一个 bit,按行存到 PackedByteArray,再做轻量压缩。地图 256x256 只有 65536 个格子,未压缩也不过 8KB。真正要注意的是版本迁移:地图扩建、原点移动、格子尺寸调整都会让旧探索记录对不上。不要只保存数组,至少保存 map_idfog_versiongrid_widthgrid_heightcell_size

当版本变化时,可以选择丢弃旧探索、按世界坐标重投影,或只保留关键发现点。对正式运营项目,直接清空玩家地图通常会引发投诉。更温和的方案是保留已发现传送点和任务点,迷雾区域重新探索。文章前期把版本字段放进去,后期就有选择余地。

多层地图和室内入口

多楼层是小地图迷雾的难点。不要把所有楼层压在同一张 FogGrid 上,否则玩家在一楼探索会把二楼也点亮。每个楼层或区域应该有独立 grid,入口节点负责切换当前显示层。室内小地图如果和室外比例不同,可以使用不同 cell size,但 POI 的世界坐标映射要清晰。

还有一种常见情况是洞穴在世界地图上只有一个入口图标,进入后是独立室内图。此时室内探索进室内存档,室外只记录入口发现。任务系统不要通过坐标猜图层,应该明确引用 map_idfloor_id。否则玩家站在洞穴里,小地图却显示地表任务点,会非常迷惑。

QA 和调试

开发包里建议加一个迷雾调试面板:显示当前 map_id、grid 坐标、刷子半径、dirty rect 数量、纹理更新时间、存档字节数。再提供几个按钮:全开、全关、导出 bitset、随机探索。QA 可以快速验证边界和存档恢复,而不是每次手动跑完整张图。

测试用例包括:沿地图边界行走是否越界;传送到未探索区域是否正确刷开;切楼层后图标是否串层;重进游戏探索是否恢复;地图版本升级后是否执行迁移;快速骑乘时刷子是否断点;低帧率下 dirty rect 是否堆积;隐藏 POI 是否在探索后出现。迷雾系统看似 UI,实际连接存档、任务和地图资源,越早做工具越省事。

落地建议

先用 128x128 的网格把流程跑通,不要一上来做巨大纹理和复杂 shader。确保探索数据、图标规则、存档恢复都正确后,再提高精度或做边缘羽化。Godot 的 ImageTexture 足够支持多数小地图需求,真正的工程重点是数据边界。小地图迷雾做得好,玩家会觉得世界可靠;做得差,玩家会不断怀疑自己到底有没有来过这里。

图标发现规则的细分

小地图图标常常比迷雾本身复杂。一个宝箱在未探索区域里不该显示,但任务要求玩家去找某个 NPC 时,NPC 方向又应该给线索。可以把图标规则分成几类:hidden_until_cell_exploredhidden_until_interactedvisible_when_quest_activevisible_after_scanalways_edge_hint。这样任务图标、传送点、采集点、敌人营地不会互相套错规则。

图标还要有“已发现”和“已完成”状态。玩家发现宝箱后离开,该图标可以在曾经探索区域显示;打开宝箱后,图标消失或变灰。这个状态不应由小地图保存,而应由 POI 系统或任务系统保存。迷雾只回答某个位置是否探索过,小地图根据 POI 自身状态决定最终显示。

Chunk 化和后台生成

当地图很大时,FogGrid 可以按 chunk 存。每个 chunk 保存自己的 bitset、dirty 标记和纹理。玩家附近的 chunk 常驻,远处 chunk 只保留压缩数据。打开大地图时,再按视口加载需要的 chunk 纹理。这样不会因为一张超大迷雾纹理占满显存。

Godot 的主线程资源更新要谨慎。数据层 bitset 可以在后台线程准备,但 ImageTexture 更新通常要回主线程。可以在后台合并 dirty 数据,主线程每帧只处理有限 chunk。调试面板显示待刷新 chunk 数量,如果玩家高速移动导致队列积压,就降低刷新频率或扩大刷子间隔。

探索刷子的形状

圆形刷子适合普通探索,但并非所有玩法都合适。潜行游戏可能需要视锥刷子,登高点可能刷开大范围扇形,扫描技能可能刷开一条射线或矩形区域。FogGrid 写入接口不要只支持 circle,可以抽象成 brush:给定中心、方向、半径、角度和强度,返回受影响格子。

刷子强度还能支持“模糊发现”。例如远处高塔只把区域标为 hinted,小地图显示淡色轮廓,玩家真正走近才变成 explored。这样探索层次更丰富。若当前项目不需要 hinted,也可以先保留枚举扩展位,避免以后从 bitset 两态升级时大改存档。

美术表现和可读性

迷雾边缘不要为了好看牺牲信息。太重的羽化会让玩家误以为某块区域已经探索,太锐的边缘又显得廉价。可以用数据格子控制真实探索,用渲染 shader 做视觉扩散,并在大地图上显示明确边界,在小地图上显示更柔和的边界。两种比例下同一套纹理可能不合适,需要不同采样或不同材质参数。

颜色也要和地图底图配合。暗色地图上的黑色迷雾不明显,浅色地图上的灰雾又容易盖住道路。最好让迷雾材质读取主题配置:未探索 alpha、已探索去饱和程度、当前可见高亮。地图 UI 是高频工具,不是单纯装饰,可读性优先。

服务端校验和客户端自由度

单机项目里探索记录完全在本地,联机或带排行榜的项目则要考虑作弊。玩家探索进度如果影响奖励、成就或地图交易,服务端应保存权威记录,客户端只做表现缓存。客户端上报探索事件时,不要每格都发,按区域或关键点上报即可。服务端根据玩家位置轨迹验证是否合理。

即使服务端权威,客户端仍需要本地 FogGrid 以保证 UI 顺滑。网络延迟不能让玩家走过区域后小地图半秒才亮。可以本地先点亮,再等待服务端确认;若服务端拒绝,通常不需要立刻熄灭普通迷雾,因为探索表现不是高风险资产,但奖励结算要以服务端为准。项目要清楚哪些探索结果只是体验,哪些会发奖励。

大地图打开时的性能峰值

玩家按 M 打开全屏大地图时,系统可能同时加载底图、图标、迷雾纹理和任务路径。若此时重建所有迷雾 chunk,就会卡。可以在平时后台维护缩略迷雾纹理,打开大地图只做显示;或者打开时先展示低分辨率迷雾,再分帧细化。小地图和大地图不一定共用同一分辨率。

图标也要分页或分层。已探索区域里有几百个采集点时,不要一次性创建几百个 Control。可以按缩放级别显示聚合图标,玩家放大后再展开。迷雾系统负责可见性过滤,地图 UI 负责 LOD。两者分开,性能和规则都更好控制。

路径指引与迷雾冲突

任务路径穿过未探索区域时,是否显示完整路线?开放世界通常只显示到已探索边界,或显示大方向不显示具体道路;线性任务可以直接显示。这个策略要在 QuestMarker 或 NavigationHint 里配置。若小地图无条件画出导航线,会提前泄露地图结构。

Godot 的 NavigationServer 可以算路径,但路径结果不应直接画到未探索地图上。先按 FogGrid 裁剪或模糊路径,再交给 UI。对玩家来说,这种限制能保持探索感,也避免地图系统和任务系统互相泄底。

数据检查工具

为每张地图生成 FogGrid 时,最好检查底图比例、世界边界和 grid 配置是否一致。常见错误是美术更新了地图底图,程序没改 world_to_map 映射,导致玩家位置点偏移。编辑器工具可以把若干 Marker3D 映射到地图图片上,人工确认城门、传送点、Boss 房位置对齐。

还可以检查 POI 是否落在 grid 范围内,是否有 visibility_policy,是否引用了不存在的 map_id。小地图问题一旦上线,很难靠玩家描述定位。工具化检查比人工点图可靠。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页