在之前博客提及过A*寻路算法,同时想实现生成迷宫算法,所以有了这次主题。
参考链接:有关迷宫的生成算法和解密算法_迷宫求解摸墙算法-CSDN博客
我们采用prim算法来生成迷宫:
让迷宫全是墙.
选一个单元格作为迷宫的通路,然后把它的邻墙放入列表
当列表里还有墙时
从列表里随机选一个墙,如果这面墙分隔的两个单元格只有一个单元格被访问过
那就从列表里移除这面墙,即把墙打通,让未访问的单元格成为迷宫的通路
把这个格子的墙加入列表
如果墙两面的单元格都已经被访问过,那就从列表里移除这面墙
所以第一步是要让所有格子周围都生成墙,保留第一个格子的左边和最后一个格子的右边不生成墙(即当作是迷宫的通路)。
通过以下流程求出路径:
把起点加入 open list 。
重复如下过程:
遍历 open list ,查找 F 值最小的节点,把它作为当前要处理的节点。
把这个节点移到 close list 。
对当前方格的 8 个相邻方格的每一个方格?
如果它是不可抵达的或者它在 close list 中,忽略它。
如果它不在 open list 中,把它加入 open list ,并且把当前方格设置为它的父亲,记录该方格的 F , G 和 H 值。
如果它已经在 open list 中,检查这条路径 ( 即经由当前方格到达它那里 ) 是否更好,用 G 值作参考。更小的 G 值表示这是更好的路径。如果是这样,把它的父亲设置为当前方格,并重新计算它的 G 和 F 值。如果你的 open list 是按 F 值排序的话,改变后你可能需要重新排序。
停止,当你
把终点加入到了 open list 中,此时路径已经找到了,或者
查找终点失败,并且 open list 是空的,此时没有路径。
保存路径。从终点开始,每个方格沿着父节点移动直至起点,这就是你的路径。
首先生成一系列格子,让它们作为一个背景模板,并管理这些格子。
public class GridController : MonoBehaviour
{
private GameObject[,] m_arrGrid = null; //管理每个格子gameObject的数组
private const int m_iRowCount = 15; //格子行数
private const int m_iColCount = 20; //格子列数
private void Awake()
{
if (m_arrGrid == null)
{
//创建一个10行20列的迷宫型
m_arrGrid = new GameObject[m_iRowCount, m_iColCount];
}
//遍历创建格子
for(int i = 0; i < m_iRowCount; i++)
{
for(int j = 0; j < m_iColCount; j++)
{
GameObject grid = Instantiate(Resources.Load("Prefab/Grid"),transform);
m_arrGrid[i, j] = grid;
}
}
}
void Start()
{
//每个格子显示出场特效
for (int i = 0; i < m_iRowCount; i++)
{
for (int j = 0; j < m_iColCount; j++)
{
m_arrGrid[i, j].transform.localScale = Vector3.zero;
m_arrGrid[i, j].transform.DOScale(Vector3.one, 1.0f);
}
}
Invoke("CreateAllLines", 2.0f);
}
}
maze_1
为了后期能更好管理每个grid的属性,包括后期这个grid周围有几面墙、grid是否被访问过等等,于是就另外写了一个类,绑定到每一个gameobject上。
//生成所有墙,留一个左上角起点和右下角终点
public void CreateAllLines()
{
for (int i = 0; i < m_iRowCount; i++)
{
for (int j = 0; j < m_iColCount; j++)
{
if (i != 0 || j != 0)
{
m_arrGridMono[i, j].CreateLine(Grid.LEFT);
}
if (i != m_iRowCount - 1 || j != m_iColCount - 1)
{
m_arrGridMono[i, j].CreateLine(Grid.RIGHT);
}
m_arrGridMono[i, j].CreateLine(Grid.UP);
m_arrGridMono[i, j].CreateLine(Grid.DOWN);
}
}
Invoke("ClearSomeLines", 2.0f);
}
public class Grid : MonoBehaviour
{
//四个方向所代表的数值
public const int LEFT = 0;
public const int UP = 1;
public const int RIGHT = 2;
public const int DOWN = 3;
//格子一半长度
public const int HALF_SIZE = 16;
//墙要放置的地方
public static Vector2[] m_linePosition = null;
//四个方向是否被堵死
private bool[] m_arrIsWayPass = {false,false,false,false};
//四个方向的墙的gameObject
private GameObject[] m_arrLine = { null, null, null, null };
private void Awake()
{
if(m_linePosition == null)
{
m_linePosition = new Vector2[4];
m_linePosition[LEFT] = Vector2.left * HALF_SIZE;
m_linePosition[RIGHT] = Vector2.right * HALF_SIZE;
m_linePosition[UP] = Vector2.up * HALF_SIZE;
m_linePosition[DOWN] = Vector2.down * HALF_SIZE;
}
}
//在一个方向上生成一面墙
public void CreateLine(int iDirection)
{
GameObject goLine = Instantiate(Resources.Load("Prefab/Line"), transform);
m_arrLine[iDirection] = goLine;
m_arrIsWayPass[iDirection] = true;
goLine.transform.localScale = Vector3.zero;
goLine.transform.localPosition = m_linePosition[iDirection];
//如果是左边或者右边的线,要记住把它翻转
if (iDirection == LEFT || iDirection == RIGHT)
{
goLine.transform.localRotation = Quaternion.Euler(Vector3.forward * 90);
}
goLine.transform.DOScale(Vector3.one, 1.0f);
}
}
maze_2
我们利用prim生成迷宫算法来将一些墙去除。
//清除一些墙,来生成迷宫
//我们可以维护一个迷宫单元格的列表,而不是边的列表。
//在这个迷宫单元格列表里面存放了未访问的单元格,我们在单元格列表中随机挑选一个单元格,
//如果这个单元格有多面墙联系着已存在的迷宫通路,我们就随机选择一面墙打通。
public void ClearSomeLines()
{
List listGrid = new List();
//以左上角为起点,开始访问
m_arrGridMono[0, 0].SetVisited(true);
//先把最左上边的右边第一个,下边第一个加入到list中
if (m_iColCount > 1)
{
listGrid.Add(m_arrGridMono[0, 1]);
}
if (m_iRowCount > 1)
{
listGrid.Add(m_arrGridMono[1, 0]);
}
//循环取list中的随机一个未打通的单元格,与周围打通了的随机一个单元格进行连通,
//并且将其他没有打通的且未在list中的放入list中
while (listGrid.Count > 0)
{
int iVisitIndex = Random.Range(0, listGrid.Count);
Grid gridVisit = listGrid[iVisitIndex];
int iPosRow = gridVisit.GetPosRow();
int iPosCol = gridVisit.GetPosCol();
listGrid.RemoveAt(iVisitIndex);
//存储已经打通了的grid信息,每一个元素代表某个方向的grid脚本
List> listVisitedGrid = new List>();
//上判断
if (iPosRow > 0)
{
if (m_arrGridMono[iPosRow - 1, iPosCol].IsVisited())
{
listVisitedGrid.Add(new KeyValuePair(Grid.UP, m_arrGridMono[iPosRow - 1, iPosCol]));
}
else if (!listGrid.Contains(m_arrGridMono[iPosRow - 1, iPosCol]))
{
listGrid.Add(m_arrGridMono[iPosRow - 1, iPosCol]);
}
}
//下判断
if (iPosRow < m_iRowCount - 1)
{
if (m_arrGridMono[iPosRow + 1, iPosCol].IsVisited())
{
listVisitedGrid.Add(new KeyValuePair(Grid.DOWN, m_arrGridMono[iPosRow + 1, iPosCol]));
}
else if (!listGrid.Contains(m_arrGridMono[iPosRow + 1, iPosCol]))
{
listGrid.Add(m_arrGridMono[iPosRow + 1, iPosCol]);
}
}
//左判断
if (iPosCol > 0)
{
if (m_arrGridMono[iPosRow, iPosCol - 1].IsVisited())
{
listVisitedGrid.Add(new KeyValuePair(Grid.LEFT, m_arrGridMono[iPosRow, iPosCol - 1]));
}
else if (!listGrid.Contains(m_arrGridMono[iPosRow, iPosCol - 1]))
{
listGrid.Add(m_arrGridMono[iPosRow, iPosCol - 1]);
}
}
//右判断
if (iPosCol < m_iColCount - 1)
{
if (m_arrGridMono[iPosRow, iPosCol + 1].IsVisited())
{
listVisitedGrid.Add(new KeyValuePair(Grid.RIGHT, m_arrGridMono[iPosRow, iPosCol + 1]));
}
else if (!listGrid.Contains(m_arrGridMono[iPosRow, iPosCol + 1]))
{
listGrid.Add(m_arrGridMono[iPosRow, iPosCol + 1]);
}
}
//随机在listVisitedGrid中挑选一个,进行双向打通
int iVisitedIndex = Random.Range(0, listVisitedGrid.Count);
int iDirection = listVisitedGrid[iVisitedIndex].Key;
Grid gridVisited = listVisitedGrid[iVisitedIndex].Value;
gridVisit.DeleteLine(iDirection);
gridVisited.DeleteLine(Grid.ContraryDirection(iDirection));
gridVisit.SetVisited(true);
}
Invoke("FindWay", 2.0f);
}
maze_3
去除之后,通过A*寻路算法破解迷宫。
//找到迷宫的通路
public void FindWay()
{
List listOpenGrid = new List();//待检查的格子
List listCloseGrid = new List();//已经检查过的格子
//把起始点放入openGrid中
listOpenGrid.Add(m_arrGridMono[0, 0]);
Grid nowGrid = null; //现在在处理中的格子
//更新起点的FGH评估值
m_arrGridMono[0, 0].UpdateValue(m_iRowCount - 1, m_iColCount - 1, null);
//当终点已加入到了open list或者可以遍历的格子已经为空了,那么就终止查找父节点的循环
while (listOpenGrid.Count > 0 && !listOpenGrid.Contains(m_arrGridMono[m_iRowCount - 1, m_iColCount - 1]))
{
nowGrid = listOpenGrid[0];
int iNumF = nowGrid.GetNumF();
//遍历listOpenGrid,查找F值最小的格子,并且把它移到listCloseGrid中
foreach (Grid openGrid in listOpenGrid)
{
if (iNumF >= openGrid.GetNumF())
{
nowGrid = openGrid;
iNumF = nowGrid.GetNumF();
}
}
listOpenGrid.Remove(nowGrid);
listCloseGrid.Add(nowGrid);
Grid neighborGrid = null;
//对当前方格的上下左右四个相邻方格作判断
//如果它是不可抵达的或者它在close list中,忽略它
if (!nowGrid.HasLine(Grid.UP))
{
neighborGrid = m_arrGridMono[nowGrid.GetPosRow() - 1, nowGrid.GetPosCol()];
if (!listCloseGrid.Contains(neighborGrid))
{
//如果它不在open list中,把它加入open list
if (!listOpenGrid.Contains(neighborGrid))
{
listOpenGrid.Add(neighborGrid);
}
//更新FGH评估值
neighborGrid.UpdateValue(m_iRowCount - 1, m_iColCount - 1, nowGrid);
}
}
if (!nowGrid.HasLine(Grid.DOWN))
{
neighborGrid = m_arrGridMono[nowGrid.GetPosRow() + 1, nowGrid.GetPosCol()];
if (!listCloseGrid.Contains(neighborGrid))
{
if (!listOpenGrid.Contains(neighborGrid))
{
listOpenGrid.Add(neighborGrid);
}
neighborGrid.UpdateValue(m_iRowCount - 1, m_iColCount - 1, nowGrid);
}
}
if ((!nowGrid.HasLine(Grid.LEFT)) && nowGrid.GetPosCol() > 0)
{
neighborGrid = m_arrGridMono[nowGrid.GetPosRow(), nowGrid.GetPosCol() - 1];
if (!listCloseGrid.Contains(neighborGrid))
{
if (!listOpenGrid.Contains(neighborGrid))
{
listOpenGrid.Add(neighborGrid);
}
neighborGrid.UpdateValue(m_iRowCount - 1, m_iColCount - 1, nowGrid);
}
}
if ((!nowGrid.HasLine(Grid.RIGHT)) && nowGrid.GetPosCol() < m_iColCount - 1)
{
neighborGrid = m_arrGridMono[nowGrid.GetPosRow(), nowGrid.GetPosCol() + 1];
if (!listCloseGrid.Contains(neighborGrid))
{
if (!listOpenGrid.Contains(neighborGrid))
{
listOpenGrid.Add(neighborGrid);
}
neighborGrid.UpdateValue(m_iRowCount - 1, m_iColCount - 1, nowGrid);
}
}
}
Grid wayGrid = m_arrGridMono[m_iRowCount - 1, m_iColCount - 1];
while (wayGrid)
{
wayGrid.SetGone();
wayGrid = wayGrid.GetParentGrid();
}
}
public class Grid : MonoBehaviour
{
//四个方向所代表的数值
public const int LEFT = 0;
public const int UP = 1;
public const int RIGHT = 2;
public const int DOWN = 3;
//格子一半长度
public const int HALF_SIZE = 16;
//墙要放置的地方
public static Vector2[] m_linePosition = null;
private bool m_bIsVisited = false; //是否被访问过,也就是是否已经成为通路了
//四个方向是否被堵死
private bool[] m_arrIsWayPass = { false, false, false, false };
//四个方向的墙的gameObject
private GameObject[] m_arrLine = { null, null, null, null };
//格子所在位置
private int m_iPosRow = 0;
private int m_iPosCol = 0;
//自身的图案
private Image m_imgSelf = null;
//自身的父节点
private Grid m_gridParent = null;
//计算搜索评估值的三个数值
private int m_F = 0; //总评估值
private int m_G = 0; //起始点到这个点的代价
private int m_H = 0; //这个点到终点的预估代价(采用曼哈顿距离直接计算)
private void Awake()
{
if (m_linePosition == null)
{
m_linePosition = new Vector2[4];
m_linePosition[LEFT] = Vector2.left * HALF_SIZE;
m_linePosition[RIGHT] = Vector2.right * HALF_SIZE;
m_linePosition[UP] = Vector2.up * HALF_SIZE;
m_linePosition[DOWN] = Vector2.down * HALF_SIZE;
}
m_imgSelf = transform.GetComponent();
}
//计算一个方向的反方向
public static int ContraryDirection(int iDirection)
{
return (iDirection + 2) % 4;
}
//在一个方向上生成一面墙
public void CreateLine(int iDirection)
{
GameObject goLine = Instantiate(Resources.Load("Maze/Line"), transform);
m_arrLine[iDirection] = goLine;
m_arrIsWayPass[iDirection] = true;
goLine.transform.localScale = Vector3.zero;
goLine.transform.localPosition = m_linePosition[iDirection];
//如果是左边或者右边的线,要记住把它翻转
if (iDirection == LEFT || iDirection == RIGHT)
{
goLine.transform.localRotation = Quaternion.Euler(Vector3.forward * 90);
}
goLine.transform.DOScale(Vector3.one, 1.0f);
}
//某个方向上是否有墙
public bool HasLine(int iDirection)
{
return m_arrIsWayPass[iDirection];
}
//是否被访问过
public bool IsVisited()
{
return m_bIsVisited;
}
//修改访问标记
public void SetVisited(bool bIsVisited)
{
m_bIsVisited = bIsVisited;
}
//打通墙壁
public void DeleteLine(int iDirection)
{
if (m_arrIsWayPass[iDirection])
{
m_arrIsWayPass[iDirection] = false;
m_arrLine[iDirection].transform.DOScale(Vector3.zero, 1.0f);
}
}
//设置这个格子的位置
public void SetPos(int iPosRow, int iPosCol)
{
m_iPosCol = iPosCol;
m_iPosRow = iPosRow;
}
//获取这个格子所在位置
public int GetPosRow()
{
return m_iPosRow;
}
public int GetPosCol()
{
return m_iPosCol;
}
//设置这个格子被走过
public void SetGone()
{
m_imgSelf.DOColor(Color.green, 1.0f);
}
//设置父节点
public void SetParentGrid(Grid parentGrid)
{
m_gridParent = parentGrid;
}
//返回父节点
public Grid GetParentGrid()
{
return m_gridParent;
}
//返回这个格子到起点的距离
public int GetNumG()
{
return m_G;
}
//返回这个格子的评估值
public int GetNumF()
{
return m_F;
}
//更新评估值
public void UpdateValue(int iEndGridPosRow, int iEndGridPosCol, Grid lastGrid)
{
m_H = (iEndGridPosRow - m_iPosRow) + (iEndGridPosCol - m_iPosCol);
if (lastGrid)
{
int iLastG = lastGrid.GetNumG();
if (m_G == 0 || m_G >= iLastG + 1)
{
m_G = iLastG + 1;
m_gridParent = lastGrid;
}
}
m_F = m_H + m_G;
}
}
maze_4
这篇文章绑定的即是源码资源,为unity资源package,导入package后直接运行即可看到总体效果。https://download.csdn.net/download/weixin_45218342/88741148