近几年,独立游戏中采用了 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一遍,找到最大的连通块,剩下的全部堵上
大佬 如何打通连通区域呢?给个思路呗
这片文章是地图元素是两个的情况下,不知道作者有没有想过多元素的情况下如何编辑?
一直想弄明白饥荒那种随机地图是如何生成的。
有没有源代码呢
厉害了!学习学习!
厉害了!学习学习!
This is the first time I've heard about the Roguelike format, and it's fascinating to see how independent games are increasingly adopting it. https://wordleunlimited.online/
Endless running game full of adventure https://run3online.pro