近几年,独立游戏中采用了 Roguelike 形式的作品越来越多,这也从侧面证明了玩家对这类游戏需求的增长。如果您还不了解 Roguelike 是怎么一回事,那么可以查阅我们之前转载的本站作者写过的一篇文章:《Roguelike 到底是啥-讲讲 Roguelike 相关知识》。Roguelike 有很多基本要素,那么对开发者来说,有几项是需要花时间去做的。比如随机地图的生成,就是整个 Roguelike 游戏的基础之一。我们也曾经介绍过其它的一些方法,比如《一种 Roguelike 地牢生成算法》以及《Troy: 我是如何设计地牢的》等。那么今天我们就来介绍一种另外一种随机地图生成方法,它可以说非常的简单。这就是今天的主题:随机生成洞穴类的地图。
今天用到的核心方法是:细胞自动机(Cellular Automata),关于它的更多信息可以查阅这里,这个链接有一切关于细胞自动机的内容。
这个教程的内容生成的地图是基于 Tile 的。
实现方法
首先,我们要生成一幅随机地图,我们通过控制随机数的范围分布来实现它。一般情况下,我们采用小于 50% 的几率来生成墙,其余的就是地面。地面是可行走的,而墙则是可碰撞的。
生成后的地图应该是一个二维数组 mapArray
,接下来我们开始遍历整个数组的每个元素,采用的规则(我们叫它 4-5 规则)如下:
- 如果当前元素是墙,那么周围超过 4 个墙就还保持为墙
- 如果当前元素是地板,那么周围超过 5 个墙就变为墙
- 循环直至完成
- 重复循环 4~5 次就可以得到结果
核心循环部分写成伪代码就是这样:
for each element in mapArray { // 循环 count = checkNeighborWalls(element) // 取得元素周围墙的数量 if element == WALL // 取得元素 element = (count >= 4) ? WALL : FLOOR // 如果当前元素是墙,那么周围超过 4 个墙就还保持为墙 else element = (count >= 5) ? WALL : FLOOR // 如果当前元素是地板,那么周围超过 5 个墙就变为墙 endif }
为了便于大家理解,我特地做了演示例子。(按“下一步”可以继续下一步的生成,“重新生成”则重新生成一幅地图的原始数据。不妨多多尝试看看生成的结果。)
怎么样?看了还是挺有趣的吧?生成的确实像是一个洞穴呢~已经可以用这样的地图来做一些游戏了(至于做什么就看您的想象了)。
问题所在
经过多次生成之后,会发现,生成的地图也不是那么尽如人意,比如像这样的还好:
可是还会有很多完全分离的情况:
而且,除了不联通之外,还有很重要的一点:如果空洞太大的话,会很无聊呢。除非找到很适合的游戏玩法,不然,一般情况下我们还是希望洞穴的内容比较复杂,路线比较曲折才好,不是么?
那么,可以想办法改进一下。
改进方法
其实归纳一下规则,其实就是:
- 按照一定几率初始化数据;
Winit(p) = rand(0, 100) < 45%
R(p)
是周围一圈墙的数目;- 如果一个元素周围墙数大于等于 5,那么就是墙。
W'(p) = R(p) >= 5
而如果我们想要让大片空地也放入一些墙,那么,我们可能要做多一些判断。现在采用的一个方法是:再向外扩大一圈,如果这个范围内的墙少于特定的数目(比如 2),那么同样也放一个墙到这个元素位置上。可以写为 R2(p) <= 2
那么规则就会变成:
- 按照一定几率初始化数据;
Winit(p) = rand(0, 100) < 45%
Rn(p)
是周围n
圈墙的数目;W'(p) = R1(p) >= 5 || R2(p) <= 2
当然,上面这个是可行的方法之一,我们可以很灵活的组合判断的方法。目前我采用的一个规则是:
- 按照 40% 几率初始化数据;
Winit(p) = rand(0, 100) < 40%
- 重复四次:
W'(p) = R1(p) >= 5 || R2(p) <= 2
- 重复三次:
W'(p) = R1(p) >= 5
下面这个例子就是我采用的规则得到的结果:(按“下一步”可以继续下一步的生成,“重新生成”则重新生成一幅地图的原始数据。不妨多多尝试看看生成的结果。)
怎么样?是不是看起来有意思多了?当然偶尔也还会出现不联通的现象,那这时候就需要我们用代码来打通它们了,也不是难办的事情,留给大家自己尝试去吧。
对比一下
我将两种方式采用同样的初始数据放在一起做了一下比较,可以看到这两种方式还是有很大不同的:(按“下一步”可以继续下一步的生成,“重新生成”则重新生成一幅地图的原始数据。不妨多多尝试看看生成的结果。)
最后
好了,我们完成了一个简单的 PCG 游戏的基础功能,下面可以在这个基础上做我们自己的 Roguelike 游戏啦。
我还是做了一个演示,如图所示:
手机建议用横屏观看
这篇教程的基础来自于这篇文章:Cellular Automata Method for Generating Random Cave-Like Levels,如果希望从原文中找到线索,不妨去看看。好文章但愿它不会消失……
我们通过对细胞自动机的简单了解可以发现,其实我们可以自行制定很多规则,来让数据变得更加奇妙或者出乎预料,如果您自行实现了很奇妙的算法,不妨让我们也了解一下哦。
感觉很有用啊 先学习学习
类似的教程以后还会有的!
随机地图生成算法 看来还是蛮受欢迎的
最近正琢磨如何做出比较有特色的随机地图,文章非常有帮助
值得借鉴 马上也要写洞穴生成
非常棒,这个栗子举的真好啊,尤其是那个演示,awsome~~~
@llq:谢谢鼓励
学习了
最近由 りご 修改于:2016-09-03 01:51:23真厉害,学习了。还有那个演示也特别好,谢谢楼主。
非常感谢,这个教程非常有用。
不过有一个疑问,楼主其中
按照 40% 几率初始化数据;Winit(p) = rand(0, 100) < 40%
重复四次:W'(p) = R1(p) >= 5 || R2(p)
很赞
真的太棒了,学到了很多
太棒了,刚好需要这个算法
赞!能否给点思路如何判断死胡同
我想问个问题,如果当前位置是墙,周围超过4个墙就保持墙,如果周围没有超过4个墙呢?没有做一个反面的动作;意味着不管周围是不是超过4个墙,反正当前位置就是墙,那这句代码的意义在哪里?
请问有什么办法办到没有大直边呢?就是在边界的时候不出现很长的直路。
然后还有如果有多种地形的话,有没有什么好办法融到一起。
这篇文章太有用了,照着例子用GMS2实现了 :D, 开心~~~ 感谢楼主
你好啊,想请教一个问题
按这个算法生成的地图,有没办法能在地图内部划分出区块?
例如我们生成一张较大的地图,可能中间会有好几块大的空地,也会有一些半岛,想请教一下有没什么思路可以辨认出地图中的这些大的空地和半岛,并标记出来呢?
请问大佬怎么打开不连通区域呢,脑子笨想不出法子
@snakesssyyyy:兄弟 你有思路了么?
@崔希斯跳大刷新大 五杀:随机性腐蚀,即将较小区域的格子进行腐蚀。如果一个格子周围存在墙壁,产生一个随机数判断该墙壁是否能变成空地,一般取60至75之间的整数作为临界值。
将腐蚀成功后的空地标记到该区域,然后重复腐蚀过程,直至接触到其他区域的格子。
@shaigu: 依然无法保证地图联通,我的解决方法是把地图dfs一遍,找到最大的连通块,剩下的全部堵上
大佬 如何打通连通区域呢?给个思路呗
这片文章是地图元素是两个的情况下,不知道作者有没有想过多元素的情况下如何编辑?
一直想弄明白饥荒那种随机地图是如何生成的。
有没有源代码呢
厉害了!学习学习!
厉害了!学习学习!