撰写了文章 发布于 2018-12-05 13:28:40
《真菌洞窟(Fungus Cave)》十一月开发日志
游戏简介
《真菌洞窟(Fungus Cave)》是一款正在开发的单人、回合制 Unity Roguelike 游戏。这个月主要做了三件事情:
- 为演员添加多种功能,比如演示动画里的能力(Power)和感染(Infection)。
- 自动探索地图。
- 使用多个随机数生成器(RNG)。
图 1:演示动画,十一月。
图 2:演示动画,十月。
接下来讲一下自动探索和随机数生成器。
自动探索
从最终效果来看,自动探索是指玩家按一个键,人物在接下来的每一轮自动前往未知区域,满足特定条件时停止移动。具体包括五个步骤:
- 步骤 1:新一轮开始。监听输入事件。
- 步骤 2:新一轮开始。如果出现特定情况,前往步骤 1,否则继续。
- 步骤 3:选择移动方向。
- 步骤 4:移动。
- 步骤 5:本轮结束。前往步骤 2。
不难发现,所谓的“自动”包含两层意思:首先,程序不响应玩家输入;其次,代替玩家发布移动命令。步骤 1 很简单。步骤 2 提到的特定情况,我建议至少包含以下三种:
- 情况 1:已探索全部区域。
- 情况 2:视野内有敌人。
- 情况 3:已经连续自动探索若干轮。
情况 1 是为了避免游戏陷入死循环,2 和 3 是为了改善玩家体验——因为无法完全控制移动方向,有可能沿着对角线闯入橙色的未知区域(见图 3)。及时停止自动探索,这样玩家能够手动调整移动路线。更强大的自动探索功能请参考 DCSS。
图 3:两种移动路线。
如果移除步骤 3,那么自动探索退化成一个更简单的问题:玩家按键,人物沿着指定方向连续移动。实际效果见图 4,核心代码可以参考我做过的 旧游戏,搜索 `Main.system.pcFastMove`。
图 4:连续移动。
选择移动方向时用到了 Dijkstra 算法,《浅谈 Roguelike 游戏中的算法》里面提过。具体来说,首先生成两个二维数组,一个记录“距离”,另一个保存着视野数据。视野的计算方法详见《<真菌洞窟(Fungus Cave)>十月开发日志》。接下来:
- 初始化“距离”数组,把每一格标记为一个足够大的整数(比如 99999)。
- 遍历“视野”数组;找到第一个“未知”格子后停止遍历;在“距离”数组里把这一格的距离改成 0。
- 在“距离”数组里,从上一步找到的位置出发,递归地标记所有格子,遵循以下两条规则——
- 规则 1:如果当前格在“视野”数组里是“未知”的,那么距离为 0。
- 规则 2:否则,它比八个相邻格子当中的最小距离大 1。
- 完成标记后,人物始终向着距离减小的方向移动。如果有多个等距离的方向,随机选择一个。
与自动探索联系最紧密的类(Class)有四个:
- PCActions 调用 AutoExplore 的方法,执行两个任务:启动自动探索,继续自动探索。
- AutoExplore 执行三个任务:决定是否继续探索,寻找移动方向,移动玩家人物。
- 玩家人物每一轮行动前,Sche****ngSystem 调用 InternalClock 的 `StartTurn()`,减少自动探索的剩余轮数。
随机数生成器
随机数生成器(Random Number Generator)能够根据给定的种子,生成固定的随机数序列。从玩家角度来讲,不用种子直接生成随机数,或者只用一个种子,游戏体验都差不多。那么为什么要根据不同用途,比如判定攻击、掉落物品、生成地图,使用不同的生成器呢?因为我喜欢啊。
`System.Random` 创建的 instance 无法保存当前状态。也就是说,这一次游戏生成了十个随机数,下一次进入游戏后,没办法直接生成第十一个随机数。Stack Overflow 上面讨论过这个问题(8188844,19512210),但是看不太懂——
图 5:16 x 55 = 28
于是俺寻思了一个办法:
- 用一个种子生成十个随机数,保存在 queue 里面。
- 需要的时候 dequeue 一个出来。
- 剩下最后一个时,把它作为种子再生成十个随机数。
代码详见 RandomNumber。这个类提供了两个方法:`public double Next(SeedTag tag)`,`public int Next(SeedTag tag, int min, int max)`,输入 enum 类型的种子标签,输出随机数。