理论的知识,这篇文章就不作过多的扩展了,网上随便一搜就有很多好的文章。
最近做算法练习,看完理论先自己动手试试。
不废话,下面开始吧:
蓝色是开始点,红色是终点,绿色表示遍历过的点。
主要的逻辑类:
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
/*
*
* Writer:June
*
* Date: 2021.1.13
*
* Function:游戏控制
*
* Remarks:
*
*/
public class GameController : MonoBehaviour
{
#region 单例
/// <summary>
/// 单例
/// </summary>
public static GameController Instance { get; private set; }
private void Awake()
{
if (Instance == null)
Instance = this;
else
{
if (Instance != this)
{
Destroy(gameObject);
}
}
DontDestroyOnLoad(gameObject);
}
#endregion
/// <summary>
/// x轴物体个数
/// </summary>
[SerializeField]private int xLength;
/// <summary>
/// y轴物体个数
/// </summary>
[SerializeField]private int yLength;
/// <summary>
/// 方块预设
/// </summary>
public GameObject cube;
/// <summary>
/// 玩家
/// </summary>
public GameObject player;
/// <summary>
/// 地图父节点
/// </summary>
public Transform mapParent;
/// <summary>
/// 开始位置颜色
/// </summary>
[SerializeField]private Color startColor;
/// <summary>
/// 结束位置颜色
/// </summary>
[SerializeField]private Color endColor;
/// <summary>
/// 路径点链表
/// </summary>
public List<Transform> pathList = new List<Transform>();
/// <summary>
/// Vector2Int方向,用于判断当前位置点的相邻四个方向
/// </summary>
private Vector2Int[] vector2Ints = { Vector2Int.up, Vector2Int.right, Vector2Int.down, Vector2Int.left };
/// <summary>
/// 存储地图的字典
/// </summary>
private Dictionary<Vector2Int, PointData> dicV2Int = new Dictionary<Vector2Int, PointData>();
/// <summary>
/// 队列,存储要判断的点
/// </summary>
private Queue<PointData> pointQueue = new Queue<PointData>();
/// <summary>
/// 开始位置点
/// </summary>
[SerializeField] private GameObject startPoint;
private void Start()
{
CreatMap();
}
/// <summary>
/// 构建地图
/// </summary>
public void CreatMap()
{
for (int x = 0; x < xLength; x++)
{
for (int y = 0; y < yLength; y++)
{
GameObject go = Instantiate(cube);
go.transform.SetParent(mapParent);
go.transform.localPosition = new Vector3(x, 0, y);
go.name = "X=" + x + " Y=" + y;
go.GetComponentInChildren<TextMeshPro>().text = "(" + x + "," + y + ")";
PointData pointData = go.GetComponent<PointData>();
//设置坐标信息
pointData.x = x;
pointData.y = y;
//开始点
if (x == 0 && y == 0)
{
go.GetComponent<MeshRenderer>().material.color = startColor;
startPoint = go;
}
Vector2Int v2Int = new Vector2Int(x, y);
if (!dicV2Int.ContainsKey(v2Int))
dicV2Int.Add(v2Int, pointData);
}
}
//随机一个点,设置为结束点
Vector2Int randomV2 = new Vector2Int(Random.Range(1, xLength), Random.Range(1, yLength));
//结束点处理
dicV2Int[randomV2].GetComponent<PointData>().IsEndPoint = true;
BFS();
CreatPlayer();
//结束点设置一下颜色,加以区分
dicV2Int[randomV2].GetComponent<MeshRenderer>().material.color = endColor;
}
#region 主角相关
/// <summary>
/// 生成主角
/// </summary>
public void CreatPlayer()
{
GameObject playerGo = Instantiate(player);
playerGo.transform.position = new Vector3(0, 0.4f, 0);
//开始移动
StartCoroutine(PlayerMove(pathList, playerGo));
}
/// <summary>
/// 玩家移动
/// </summary>
IEnumerator PlayerMove(List<Transform> _path, GameObject _player)
{
for (int i = 0; i < _path.Count; i++)
{
yield return new WaitForSeconds(1f);
_player.transform.position = new Vector3(_path[i].position.x, 0.4f, _path[i].position.z);
}
}
#endregion
#region 宽度优先搜索
/// <summary>
/// 扩展周围,即找到当前点周围相邻的点(上右下左),并将其添加到队列中
/// </summary>
private void ExploreAround(Vector2Int _currentPoint)
{
foreach (Vector2Int dir in vector2Ints)
{
Vector2Int v2Int = dir + _currentPoint;
if (dicV2Int.ContainsKey(v2Int))
{
var pointDataComp = dicV2Int[v2Int];
//判断是否已经搜索过的
if (pointDataComp.IsCheck || pointQueue.Contains(pointDataComp)) continue;
//设置当前路径点的父级
pointDataComp.parentPoint = dicV2Int[_currentPoint];
pointQueue.Enqueue(pointDataComp);
//设置检测的颜色
dicV2Int[v2Int].GetComponent<MeshRenderer>().material.color = Color.green;
}
}
}
/// <summary>
/// 宽度优先搜索核心逻辑
/// </summary>
private void BFS()
{
//开始时,把开始点加入到队列中判断
pointQueue.Enqueue(startPoint.GetComponent<PointData>());
while (pointQueue.Count > 0)
{
//先找到当前点相邻的点
var pointDataComp = pointQueue.Dequeue();
if (!pointDataComp.IsCheck)
{
//标记为检测
pointDataComp.IsCheck = true;
//如果找到,立即终止算法
if (pointDataComp.IsEndPoint)
{
GetPath(pointDataComp);
//找到终点后,清空队列,以免继续遍历
pointQueue.Clear();
break;
}
ExploreAround(pointDataComp.GetV2IntPos());
}
}
}
/// <summary>
/// 获取最短路径,从终点获取上一节点,一直获取到开始点,则得到路径
/// </summary>
/// <param name="_endPoint">终点</param>
private void GetPath(PointData _endPoint)
{
pathList.Add(_endPoint.transform);
PointData point = _endPoint.parentPoint;
while (point != startPoint.GetComponent<PointData>())
{
//将当前点的父级(上一级),添加到路径中
pathList.Add(point.transform);
//重复从当前点取父级点的操作,直到父级点是开始点为止
point = point.parentPoint;
}
//反转链表
pathList.Reverse();
}
#endregion
}
生成的每个路径点,都会挂有PointData类:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/*
*
* Writer:June
*
* Date: 2021.1.13
*
* Function:玩家移动
*
* Remarks:
*
*/
public class PointData : MonoBehaviour
{
/// <summary>
/// 当前点是否已经检测了
/// </summary>
public bool IsCheck { get; set; }
/// <summary>
/// X位置
/// </summary>
public int x;
/// <summary>
/// Y位置
/// </summary>
public int y;
/// <summary>
/// 当前点是否是终点
/// </summary>
public bool IsEndPoint { get; set; }
/// <summary>
/// (父节点)上一个节点,即当前的点,是由哪个父节点搜索而来的
/// </summary>
public PointData parentPoint;
/// <summary>
/// 获取Vector2Int坐标
/// </summary>
/// <returns></returns>
public Vector2Int GetV2IntPos()
{
Vector2Int vector2Int = new Vector2Int(x, y);
return vector2Int;
}
}
工程文件:
链接:https://pan.baidu.com/s/16wYCX9MdG9Q6Odm7lAyhfQ
提取码:9agf
unity版本 2019.4.12f1