社区分享 | 程序化地牢生成算法

文摘   科技   2024-10-25 20:00   上海  
这篇文章转载自 Unity 社区大佬 ForgemasterGua。介绍了程序化生成地牢所需的基础算法伪随机数生成器、配对函数、寻路算法,和常用的迷宫生成算法细胞自动机等。ForgemasterGua 在 Unity 中国开发者社区持续更新技术内容中,点击阅读原文,前往 ForgemasterGua 的社区主页,阅读更多干货文章。
在游戏开发中,程序化地牢生成是一种强大的技术,能够自动创建复杂且多样化的地牢布局。通过算法和随机数生成器,开发者可以设计出独特的地下城,每次游戏体验都与众不同。若想生成可玩性高且自然有趣的地牢,我们需要许多算法结合进行使用。

伪随机数生成器

伪随机数生成器 (Pseudorandom Number Generator, PRNG) 是一种算法,用于生成一系列数值,这些数值的性质近似于随机数的性质。尽管这些数值看起来是随机的,但它们实际上是由一个初始值 (称为种子) 决定的,因此在相同种子的情况下其结果是确定的。这一特性也经常用于游戏中的无限地图生成中,通过此特性及其他函数来保证地图边缘的衔接平滑。

线性同余生成器


线性同余生成器 (Linear Congruential Generator, LCG) 是一种常见的伪随机数生成算法。它通过一个线性递推关系来生成伪随机数序列。
LCG 的公式如下:
out = (in * a + c) mod m
代码案例如下:
public static int LCGRandom(int v){    return (1140671485 * v + 12820163) % 16777216;}
其中 out 为输出,in 是输入,a 是乘数,c 是增量,m 是模数。我们的游戏开发中通常只需要将 a 和 c 设置为一个大数字,m 则通常为 2^32(int32),由上述公式可见该算法十分简易且效率很高。对于常用的 a 和 c 可以参考下表:

来源:https://en.wikipedia.org/wiki/Linear_congruential_generator

配对函数

在地牢生成代码中我们也会经常遇到需要将两个数字映射到一个数字的问题,例如:通过 x 坐标,y 坐标两个数字确定一个随机数,改随机数来决定此处地面网格是否存在某种植物或道具。此时我们就需要用到配对函数:在数学中,配对函数是将两个自然数唯一编码为一个自然数的过程。

康托配对


康托配对函数 (Cantor Pairing Function) 是由德国数学家格奥尔格·康托尔 (Georg Cantor) 提出的一种方法,用于将两个自然数唯一地映射到一个自然数。康托配对函数的定义如下:对于任意两个自然数 (a) 和 (b),其配对结果 (z)。

ContorPairing 的计算公式如下:

z = (a + b)(a + b + 1) / 2 + b

代码案例如下:
public static int CantorPair(int a, int b){    return (a + b) * (a + b + 1) / 2 + b;}
可见此算法也十分简单高效。

寻路算法

在游戏开发算法中寻路算法也是老生常谈的。在本篇章中,该类算法通常用于基于房间生成后的道路生成。讲述这部分的文章随处可见,本篇章则仅简述作为参考。

广度优先搜索 (BFS)

这种算法会从起点开始,逐层向外扩展,直到找到目标点。它适用于寻找最短路径,但在复杂地图中效率较低。

深度优先搜索 (DFS)

这种算法会沿着一条路径一直走到尽头,然后回溯并尝试另一条路径。虽然它不一定能找到最短路径,但实现简单。

Dijkstra 算法

这是一个经典的最短路径算法,通过逐步扩展已知最短路径来找到从起点到终点的最短路径。它考虑了路径的权重,因此能找到最优路径。

A* 算法

这是目前最常用的寻路算法之一。它结合了广度优先搜索和 Dijkstra 算法的优点,通过启发式函数估算路径成本,从而更高效地找到最优路径。

跳点寻路算法 (Jump Point Search, JPS)

是一种优化的路径搜索算法,由澳大利亚的两位教授于 2011 年提出。它基于 A* 算法,但通过减少需要检查的节点数量来提高效率,特别适用于网格地图的路径搜索。

接下来介绍一些常用的迷宫生成算法。

细胞自动机 (Cellular Automata,CA)

细胞自动机 (Cellular Automata, CA) 是一种基于规则的数学模型,用来模拟复杂系统的动态行为。

细胞自动机简介


细胞自动机由一个规则定义的网格组成,其中每个网格称为一个“细胞”。每个细胞有一个状态,可以是“活着”或“死亡”等,状态的变化由特定的规则决定,通常根据相邻细胞的状态来计算。
最著名的细胞自动机之一是康威的。在这个二维网格的细胞自动机中,每个细胞的状态在每一步都可能发生变化。变化规则如下:

一个活细胞有两个或三个邻居时继续存活;

一个死细胞有三个邻居时变为活细胞;
其他情况下,细胞保持当前状态不变。

细胞自动机迷宫算法


规定迷宫范围如 64*64,创建 map 以及用于存储每次迭代结果的 newMap;

var length = 64;      var width = 64;      var map = new int[length, width];      var newMap = new int[length, width];

通过随机数算法填满整个区域范围,这里我们采用 LCG Random 并且将墙壁以占比 45 的量级填充:

for (int x = 0; x < length; x++)        {            for (int y = 0; y < width; y++)            {                newMap[x, y] = 1;                var r = GameMath.LCGRandom(GameMath.CantorPair(x, y)) % 50 + 50;                if (r < 55)                {                    map[x, y] = 0;                }                else                {                    map[x, y] = 1;                }            }        }

遍历整片地图,如果以当前遍历点为中心周围 9 个方格内执行如下判断:

如果中心点为墙,判断周围 8 个点是否存在 4 个及以上的墙壁;

如果满足则中心为墙壁,否则为空洞;

如果中心点为空洞,判断周围 8 个点是否存在 5 个及以上的墙壁;

如果存在则中心为墙壁;否则为空洞;

for (int x = 1; x < length - 1; x++){    for (int y = 1; y < width - 1; y++)    {        var wallCount = 0;        for (int i = x - 1; i <= x + 1; i++)        {            for (int j = y - 1; j <= y + 1; j++)            {                if (map[i, j] == 1)                {                    wallCount++;                }                else                {                    // continue;                }            }        }        if (map[x, y] == 1)        {            if (wallCount > 4)            {                newMap[x, y] = 1;            }            else            {                newMap[x, y] = 0;            }        }        else        {            if (wallCount > 4)            {                newMap[x, y] = 1;            }            else            {                newMap[x, y] = 0;            }        }    }}for (int x = 0; x < length; x++){    for (int y = 0; y < width; y++)    {        map[x, y] = newMap[x, y];    }}
规定迭代次数,重复执行上一步。

小结

分析算法每一步的作用可以看出对于迷宫的形成,通过噪声函数确定墙壁的占比决定了最后迷宫围墙的稀疏程度。通过该算法生成的墙体会随着迭代次数的增加噪点会变得越来越少,迷宫看起来也会越来越平滑。
但该算法会根据初始噪声的不同而产生如下图所示的孤立洞穴,因此为了保证游戏地图的可玩性,不建议单独使用该算法进行设计。

Unity 中文社区持续征集内容投稿,欢迎与 Unity 官方分享你的技术笔记、项目 demo、行业经验、有趣案例,加入社区建设,繁荣内容生态,带领百万 Unity 中文开发者一同学习。

投稿方式:
方式一:在 Unity 中文社区首页(https://developer.unity.cn/)创建个人账号,点击【写文章】,发表文章;

方式二:联系邮箱 learn-cn@unity.cn投稿技术内容


长按关注
Unity 官方开发者服务平台
第一时间了解 Unity 社区动向,学习开发技巧

 点击“阅读原文”,访问博主更多技术分享 


Unity官方开发者服务平台
Unity引擎官方开发者服务平台,分享技术干货、学习课程、产品信息、前沿案例、活动资讯、直播信息等内容。
 最新文章