我是一名业余开发者,业余时间在开发一款温馨的多人钓鱼游戏,结果发现了一个我一开始甚至没意识到是 bug 的问题。森林看起来……太整齐了!

山谷中的每棵树、每块石头、每根芦苇、每朵花和每只萤火虫都是通过种子散点(seeded scatter)放置的,这样每位玩家看到的世界都是一样的。我的散点函数使用了线性同余生成器(LCG,经典的“乘以、加、取余”)随机数生成器,并把结果对 448 取模得到 x 和 z 的位置。

The catch: an LCG's low bits repeat in very short cycles. Taking it modulo a number only keeps those weak low bits, so the positions weren't nearly as random as they looked. When I measured it, 63% of trees had their nearest neighbour in the same direction. That's why they were lining up in rows.

修复相当简单:

  1. 用 mulberry32 替换 LCG,这是一个体积小的 32 位生成器,仍然有种子且决定性。
  2. 在原始取模网格上改为在四分之一块位置放置物体。
  3. 增加一个测试,测量最近邻方向并检查它们均匀分布,防止问题悄然回归。

两件我没料到的事:

  • 改变生成器会导致散点放置的所有内容(而不仅仅是树木)整体移动。由于仍然有种子,所有人看到的都是一样的,但整个地图会整体平移。
  • 新布局让一棵树和一块岩石紧贴在一个小屋的墙壁旁。于是树木和草地岩石在小屋四周保持了清晰的空地。

我的最大收获是:“随机”和“看起来随机”并不相同,快速的统计测试(最近邻方向效果很好)比肉眼检查截图快得多。

还有其他人也被弱随机数生成器在程序生成中咬过吗?现在你用什么做种子散点?