关于我们

质量为本、客户为根、勇于拼搏、务实创新

< 返回新闻公共列表

【SLG游戏服务器开发手册】战争迷雾(Fog of War)存储设计

发布时间:2025-10-16 10:46:52

SLG游戏(如《率土之滨》、《万国觉醒》等)的战争迷雾(Fog of War)存储是一个经典且有趣的设计问题。它需要在数据准确性存储空间计算效率之间找到一个精妙的平衡。

image.png


接下来我们将从核心思想、具体存储方案、优化技巧以及示例几个方面来详细阐述。

核心思想

战争迷雾的本质是:记录一个玩家对于游戏地图的“探索”和“视野”状态

 探索:一旦被探索,地图区域就会永久打开(即使单位离开,也不再是全黑,通常是半透明的“已探索但无视野”状态)。

 视野:由于己方单位存在而提供的实时可见区域。单位离开,视野随之消失。

因此,存储方案通常需要处理这两种状态。

 

常见存储方案

没有一种方案是完美的,需要根据你的游戏地图大小、玩家人数、游戏类型(全球同图 vs. 分区小地图)来选择或组合使用。

方案一:位图/比特位数组 (Bitmask/Bit Array) - 最常用

这是最直观和常用的方法,将地图网格化,用每一个比特位(bit)来表示一个最小单位区域(通常是一个格子)的状态。

 如何存储:

1.  将地图划分为网格:例如,你的地图是 1000x1000 格。

2.  为每个玩家创建两个位图

 explored_mask: 记录已探索区域。某位为 1 表示已探索,0 表示未探索。

 vision_mask: 记录当前视野区域。某位为 1 表示可见,0 表示不可见。这个 mask 需要每帧/每秒根据单位位置重新计算

1.  最终显示:客户端根据两个 mask 的组合来决定显示状态:

vision_mask[i] == 1 -> 完全可见vision_mask[i] == 0 && explored_mask[i] == 1 -> 已探索但不可见(战争迷雾)vision_mask[i] == 0 && explored_mask[i] == 0 -> 未探索(全黑)

 优缺点:

 优点:查询和更新效率极高(位操作非常快),数据结构紧凑(1000x1000的地图,每个 mask 仅需 ~125KB 内存 (1000*1000/8/1024))。

 缺点:当地图极大时(例如全球同图的大型SLG),每个玩家都存一个完整的位图,内存消耗会线性增长(1万玩家 * 125KB = 1.25GB,这还只是基础内存)。同步整个位图给客户端流量较大,需要做差分同步。

方案二:区块化 (Chunking)

这是对位图方案的优化,旨在减少存储和同步开销。将大地图划分为更大的区块(Chunk),例如 16x16 或 32x32 格为一个区块。

 如何存储:

1.  地图被划分为 M x N 个区块。

2.  为每个玩家存储两个信息:

 explored_chunks: 一个记录了所有已探索区块ID的集合(Set)。或者也是一个位图,但尺寸小了很多(M*N bits)。

 vision_chunks: 一个记录了当前拥有视野的区块ID的集合。这个集合需要频繁更新。

 

 优缺点:

 优点:极大减少了存储和网络同步的数据量。同步“哪些区块可见”比同步一个庞大的位图要节省得多。

 缺点:精度是区块级别的,不够细腻。一个区块内只要有一个格子被看到,整个区块都会被标记。对于需要精确视野的游戏(如MOBA、RTS)不适用,但对于大型SLG来说是完全可接受的折衷。

方案三:坐标集合 (Set of Coordinates) - 适用于稀疏视野

如果你的游戏地图巨大,但每个玩家的单位很少,视野范围相对整个地图非常稀疏(例如早期游戏阶段),这种方法可能有效。

 如何存储:

1.  为每个玩家存储两个集合:

 explored_set: 一个存储了所有已被探索过的区块坐标(或格子坐标)的集合。

 vision_set: 一个存储了当前所有可见的区块坐标(或格子坐标)的集合。

 

 优缺点:

 优点:在视野极其稀疏时,非常节省内存。

 缺点:随着探索和扩张,集合会越来越大,查询“某个点是否可见”的效率(O(n))会远低于位图的O(1)。通常不作为主存储方案。

 

混合方案与最佳实践

在实际的大型SLG项目中,通常会采用一种混合方案

1.  两层存储结构

 底层(基础存储):使用区块化(Chunking) 的位图来记录玩家的 explored_mask。例如,将一个 2000x2000 的地图划分为 500x500 个区块(每个区块 4x4 格)。这样 explored_mask 只有 500*500/8 ≈ 15.6KB 每玩家。

 上层(实时计算):服务器不为每个玩家存储完整的 vision_mask,而是在需要时(如同步给客户端时)动态计算

 根据这个玩家所有单位的当前位置和视野范围,实时计算出哪些区块处于视野内(vision_chunks)。

 vision_chunksexplored_chunks 进行合并,得到最终需要同步给客户端的视野信息。

1.  差分同步 (Delta Sync)

 不要每帧都把完整的视野信息发送给客户端。

 服务器端缓存玩家上一帧的 vision_chunks_old

 每帧计算新的 vision_chunks_new

 只同步变化的部分chunks_that_became_visible = vision_chunks_new - vision_chunks_oldchunks_that_became_invisible = vision_chunks_old - vision_chunks_new

 客户端根据这些增量信息来更新本地的视野显示。这能减少90%以上的网络流量。

1.  探索状态的更新

 当一个新的区块进入视野(chunks_that_became_visible)时,服务器会将这些区块在玩家的 explored_mask 中永久标记为 1

 explored_mask 是一个只增不减的数据,只需要在玩家初次探索某个区块时写一次数据库,之后可以常驻内存。

1.  数据库存储

 玩家的 explored_mask(区块位图)需要持久化到数据库。可以将其转换为一个二进制Blob(如MySQL的BLOB类型)或者一个很长的二进制字符串(BIT类型)存储。

 当玩家下线再上线时,从数据库读出他的 explored_mask 加载到内存。

示例流程

1.  玩家登录:服务器从DB加载该玩家的 explored_mask(区块位图)到内存。

2.  游戏运行

 每10秒(或其他节奏),服务器遍历该玩家的所有单位,计算它们视野所能覆盖到的所有区块,得到 current_vision_chunks

 对比上一轮的 last_vision_chunks,得到需要移除视野和添加视野的区块列表。

 current_vision_chunks 中新出现的区块,在 explored_mask 中标记为已探索。

 区块变化列表(差分数据)发送给客户端。消息体非常小:{remove: [chunk_id_1, chunk_id_2], add: [chunk_id_3, chunk_id_4]}

1.  客户端:根据收到的消息,更新本地地图的渲染状态。

2.  玩家下线:服务器将更新后的 explored_mask 写回数据库。

总结

方案

适用场景

优点

缺点

精细位图

中小型地图,RTS/MOBA

精度高,速度快

存储和同步开销大

区块化位图

大型SLG(首选)

存储和同步开销小

精度为区块级

坐标集合

超大地图,极稀疏视野

极端稀疏时效率高

集合大了以后效率骤降

对于你正在设计的SLG游戏,采用“区块化混合方案”并结合“差分同步”,是最稳健和高效的选择。它完美地平衡了内存、网络和CPU计算资源。




/template/Home/Hsroot/PC/Static