我在我的游戏中增加了迷宫场景,这个迷宫随机生成而且中央有一片开阔地带作为终点位置。作为挑战的一部分我希望迷宫在有一定规模的前提下不至于过难,解决方案是玩家身在迷宫中时给出某种提示(UI 或者立体声音效)将玩家引向正确的方向。
随机生成迷宫的方式不唯一,生成方式影响迷宫风格。就没有高度差距的二维迷宫而言回溯法生成的迷宫形状自然,容易得到一个完美迷宫,即联系迷宫中任意两点的路径唯一。
这个方案的效果长这样,有道路,还有两个空腔,其中一个空腔在左上角:
约定,原理,数据结构
首先我们做出一些约定:
- 迷宫被视为由一系列单元组成,无关具体形状。
- 单元记录从自身出发能到达的其他邻接点,在这里迷宫单元是方格,邻接点固定在上下左右四个方向,那么保存在某方向上是否有开口即可。
- 迷宫使用矩阵表示,单元位置坐标 (a, b) 表示在矩阵的第 a 行第 b 列,而 a、b 分别为迷宫在 x、z 轴上的单位长度位移增量(形如上面那张图,这样约定省略了换算,二维坐标的 xy 轴/行列可以方便地作用于 Unity 中的坐标)。
回溯法原理简单粗暴。
- 初始状态的迷宫所有单元都是全封闭的,算法将指定的起点压栈
- 观察栈顶节点,如果节点没有未访问的邻接点就出栈,出栈导致栈空则结束算法,得到有可用邻接点的单元作为当前点
- 随机选取一个当前点未访问过的邻接点,将该点压栈、打上已访问标记、连接该点和当前点,重复步骤2
使用矩阵表示迷宫,可以用数组存放所有节点。因为每个点只考虑上下左右方向是否连通,可以使用一个二进制位表示连通与否。更极端一些甚至连同访问标记和迷宫中的空洞也可以使用二进制位表示。
迷宫中的空洞要在生成迷宫前预先设置,不能与起点重叠,这里规定设置一个空洞需要空洞的左上角点坐标、横方向增量和纵方向增量(前面约定的作用体现了)。迷宫生成时算法走到空洞所在点时将此空洞的所有点打上访问标记,并且将空洞所在的点相互连接形成大的空穴。
走在迷宫里稍微有些震撼,虽然有些模型只是占位符比如那个奇怪的楼梯……
代码
复制类的内容,或者生成 dll 文件后导入可以在 Unity 中创建迷宫类实例。
using System;
using System.Collections.Generic;
namespace RandMaze
{
/// <summary>
/// 生成四方向随机迷宫
/// </summary>
public class DMaze
{
/// <summary>
/// 存放迷宫单元,其大小在构造函数中决定
/// </summary>
public int[] Maze { get; private set; }
/// <summary>
/// 迷宫中的空洞,连接的空洞被视为一个单元
/// </summary>
public bool[] Hole { get; private set; }
private bool[] visited;
/// <summary>
/// 迷宫的行数,迷宫单元在x轴上的个数
/// </summary>
public int XCount { get; private set; }
/// <summary>
/// 迷宫的列数,迷宫单元在y轴上的个数
/// </summary>
public int YCount { get; private set; }
public int Capacity { get; private set; }
public Random rand = new Random();
public int enter_x, enter_y;
public int exit_x, exit_y;
public bool enterOpen = false, exitOpen = false;
public const int up = 1;
public const int right = 1 << 1;
public const int down = 1 << 2;
public const int left = 1 << 3;
/// <summary>
/// 生成 x 行 y 列的迷宫
/// </summary>
/// <param name="x">迷宫行数</param>
/// <param name="y">迷宫列数</param>
pub