随机生成 Tile Based 地图之——洞穴

作者:eastecho
2015-05-01
37 83 21

近几年,独立游戏中采用了 Roguelike 形式的作品越来越多,这也从侧面证明了玩家对这类游戏需求的增长。如果您还不了解 Roguelike 是怎么一回事,那么可以查阅我们之前转载的本站作者写过的一篇文章:《Roguelike 到底是啥-讲讲 Roguelike 相关知识》。Roguelike 有很多基本要素,那么对开发者来说,有几项是需要花时间去做的。比如随机地图的生成,就是整个 Roguelike 游戏的基础之一。我们也曾经介绍过其它的一些方法,比如《一种 Roguelike 地牢生成算法》以及《Troy: 我是如何设计地牢的》等。那么今天我们就来介绍一种另外一种随机地图生成方法,它可以说非常的简单。这就是今天的主题:随机生成洞穴类的地图。

今天用到的核心方法是:细胞自动机(Cellular Automata),关于它的更多信息可以查阅这里,这个链接有一切关于细胞自动机的内容。

这个教程的内容生成的地图是基于 Tile 的。

实现方法

首先,我们要生成一幅随机地图,我们通过控制随机数的范围分布来实现它。一般情况下,我们采用小于 50% 的几率来生成墙,其余的就是地面。地面是可行走的,而墙则是可碰撞的。

生成后的地图应该是一个二维数组 mapArray,接下来我们开始遍历整个数组的每个元素,采用的规则(我们叫它 4-5 规则)如下:

  1. 如果当前元素是墙,那么周围超过 4 个墙就还保持为墙
  2. 如果当前元素是地板,那么周围超过 5 个墙就变为墙
  3. 循环直至完成
  4. 重复循环 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
}

为了便于大家理解,我特地做了演示例子。(按“下一步”可以继续下一步的生成,“重新生成”则重新生成一幅地图的原始数据。不妨多多尝试看看生成的结果。)

怎么样?看了还是挺有趣的吧?生成的确实像是一个洞穴呢~已经可以用这样的地图来做一些游戏了(至于做什么就看您的想象了)。

问题所在

经过多次生成之后,会发现,生成的地图也不是那么尽如人意,比如像这样的还好:

Good

可是还会有很多完全分离的情况:

Bad

而且,除了不联通之外,还有很重要的一点:如果空洞太大的话,会很无聊呢。除非找到很适合的游戏玩法,不然,一般情况下我们还是希望洞穴的内容比较复杂,路线比较曲折才好,不是么?

那么,可以想办法改进一下

改进方法

其实归纳一下规则,其实就是:

规则 1:

  1. 按照一定几率初始化数据;Winit(p) = rand(0, 100) < 45%
  2. R(p) 是周围一圈墙的数目;
  3. 如果一个元素周围墙数大于等于 5,那么就是墙。W'(p) = R(p) >= 5

而如果我们想要让大片空地也放入一些墙,那么,我们可能要做多一些判断。现在采用的一个方法是:再向外扩大一圈,如果这个范围内的墙少于特定的数目(比如 2),那么同样也放一个墙到这个元素位置上。可以写为 R2(p) <= 2

那么规则就会变成:

规则 2:

  1. 按照一定几率初始化数据;Winit(p) = rand(0, 100) < 45%
  2. Rn(p) 是周围 n 圈墙的数目;
  3. W'(p) = R1(p) >= 5 || R2(p) <= 2

当然,上面这个是可行的方法之一,我们可以很灵活的组合判断的方法。目前我采用的一个规则是:

采用规则:

  1. 按照 40% 几率初始化数据;Winit(p) = rand(0, 100) < 40%
  2. 重复四次:W'(p) = R1(p) >= 5 || R2(p) <= 2
  3. 重复三次:W'(p) = R1(p) >= 5

下面这个例子就是我采用的规则得到的结果:(按“下一步”可以继续下一步的生成,“重新生成”则重新生成一幅地图的原始数据。不妨多多尝试看看生成的结果。)

怎么样?是不是看起来有意思多了?当然偶尔也还会出现不联通的现象,那这时候就需要我们用代码来打通它们了,也不是难办的事情,留给大家自己尝试去吧。

对比一下

我将两种方式采用同样的初始数据放在一起做了一下比较,可以看到这两种方式还是有很大不同的:(按“下一步”可以继续下一步的生成,“重新生成”则重新生成一幅地图的原始数据。不妨多多尝试看看生成的结果。)

最后

好了,我们完成了一个简单的 PCG 游戏的基础功能,下面可以在这个基础上做我们自己的 Roguelike 游戏啦。

我还是做了一个演示,如图所示:

demo

查看本教程的演示
手机建议用横屏观看

这篇教程的基础来自于这篇文章:Cellular Automata Method for Generating Random Cave-Like Levels,如果希望从原文中找到线索,不妨去看看。好文章但愿它不会消失……

我们通过对细胞自动机的简单了解可以发现,其实我们可以自行制定很多规则,来让数据变得更加奇妙或者出乎预料,如果您自行实现了很奇妙的算法,不妨让我们也了解一下哦。

近期点赞的会员

 分享这篇文章

eastecho 

从前的边城浪子,现在的路人乙 

您可能还会对这些文章感兴趣

参与此文章的讨论

  1. lykyyy 2015-12-03

    感觉很有用啊 先学习学习

    • eastecho 2015-12-11

      类似的教程以后还会有的!

  2. eastecho 2016-01-13

    随机地图生成算法 看来还是蛮受欢迎的

  3. zhangtianfeng 2016-03-11

    最近正琢磨如何做出比较有特色的随机地图,文章非常有帮助

  4. UnVoxel 2016-03-24

    值得借鉴 马上也要写洞穴生成

  5. llq 2016-05-26

    非常棒,这个栗子举的真好啊,尤其是那个演示,awsome~~~

  6. yihleego 2016-08-17

    学习了

    最近由 りご 修改于:2016-09-03 01:51:23
  7. GaiGai 2016-09-11

    真厉害,学习了。还有那个演示也特别好,谢谢楼主。

  8. dearjy 2016-10-16

    非常感谢,这个教程非常有用。
    不过有一个疑问,楼主其中
    按照 40% 几率初始化数据;Winit(p) = rand(0, 100) < 40%
    重复四次:W'(p) = R1(p) >= 5 || R2(p)

  9. yukiandi 2017-02-11

    很赞

  10. all right 2017-07-22 微信会员

    真的太棒了,学到了很多

  11. DemiGod 2017-07-27

    太棒了,刚好需要这个算法

  12. BugCreater 2017-10-29

    赞!能否给点思路如何判断死胡同

  13. Aprilbuble 2017-11-12

    我想问个问题,如果当前位置是墙,周围超过4个墙就保持墙,如果周围没有超过4个墙呢?没有做一个反面的动作;意味着不管周围是不是超过4个墙,反正当前位置就是墙,那这句代码的意义在哪里?

  14. alimaGame 2017-12-07

    请问有什么办法办到没有大直边呢?就是在边界的时候不出现很长的直路。
    然后还有如果有多种地形的话,有没有什么好办法融到一起。

  15. Feonya 2017-12-28

    这篇文章太有用了,照着例子用GMS2实现了 :D, 开心~~~ 感谢楼主

  16. shadow0yi 2018-03-21

    你好啊,想请教一个问题
    按这个算法生成的地图,有没办法能在地图内部划分出区块?
    例如我们生成一张较大的地图,可能中间会有好几块大的空地,也会有一些半岛,想请教一下有没什么思路可以辨认出地图中的这些大的空地和半岛,并标记出来呢?

  17. snakesssyyyy 2018-07-24

    请问大佬怎么打开不连通区域呢,脑子笨想不出法子

  18. 崔希斯跳大刷新大 五杀 2018-08-12 微信会员

    大佬 如何打通连通区域呢?给个思路呗

您需要登录或者注册后才能发表评论

登录/注册