广度优先搜索算法在Unity网格地图中实现最短路径
内容学习取自于B站BeaverJoeUP主
最终效果展示图 鼠标点击方块 然后球向那个方块移动
方块的设置 以及位置点 都在上述视频中有展示
大体是分为三个脚本
- WayPoint 这是挂载在每个方块上的
- PathFinding 这是负责寻找最佳路径的类
- EnemyMovement 球挂载的脚本 负责根据寻找到的最佳路径移动至目标点
WayPoint
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
public class WayPoint : MonoBehaviour
{
EnemyMovement enm;
public WayPoint WayPointParent;//储存找到这个点的父级
public bool IsSerach=false;
public TMP_Text tex;
// Start is called before the first frame update
void Start()
{
tex = transform.GetChild(0).GetChild(0).GetComponent<TMP_Text>();
enm = FindObjectOfType<EnemyMovement>();
// Debug.Log(gameObject.name + ":" + GetPosition());
tex.text = GetPosition().ToString();
tex.overflowMode = TextOverflowModes.Overflow;
tex.enableWordWrapping = false;
tex.fontSize = 24;
}
public Vector2Int GetPosition()//获取这个点的位置 将自身左边转换成位置坐标
{
return new Vector2Int(Mathf.RoundToInt(transform.localPosition.z/1.5f), Mathf.RoundToInt(transform.localPosition.x / 1.5f));
}
private void OnMouseDown()
{
PathFinding pf = FindObjectOfType<PathFinding>();
pf.EndPoint = gameObject;
pf.isRunning = true;
enm.OnStop();
enm.Onstart();
}
}
PathFinding
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PathFinding : MonoBehaviour
{
[SerializeField] private GameObject startPoint, endPoint;//记录起始点和目标点
Queue<WayPoint> queue = new Queue<WayPoint>();//队列 用于存放每次检索到的点
public bool isRunning = true;//用于找到最佳路径后取消寻找
WayPoint tempPoint;
public List<WayPoint> waylist;//获得所有路径点
public GameObject EndPoint
{
get {
return endPoint;
}
set { endPoint = value; }
}
public GameObject StartPoint
{
get
{
return startPoint;
}
set { startPoint = value; }
}
private void Start()
{
LoadAllWayPoints();//获取所有路径点
startPoint.GetComponent<MeshRenderer>().material.color = new Color(0, 1, 0, 1);
endPoint.GetComponent<MeshRenderer>().material.color = new Color(1, 0, 0, 1);
// ExploreAround();
}
public Dictionary<Vector2Int, WayPoint> waypoint = new Dictionary<Vector2Int, WayPoint>();//存放场景瓦片,通过瓦片位置标记每一个瓦片
private Vector2Int[] directions =
{
Vector2Int.up,
Vector2Int.right,
Vector2Int.down,
Vector2Int.left
};
private void ExploreAround()
{ if (!isRunning)
return;
foreach (var item in directions)
{
// Debug.Log("Exploring" +(tempPoint.GetPosition() + item));
if (!waypoint.ContainsKey(tempPoint.GetPosition() + item))
continue;
if (waypoint[tempPoint.GetPosition() + item].IsSerach == true)
continue;
// waypoint[tempPoint.GetPosition() + item].GetComponent<MeshRenderer>().material.color = new Color(0, 0, 1, 1);
var tempWayPoint = waypoint[tempPoint.GetPosition() + item];
tempWayPoint.IsSerach = true;
tempWayPoint.WayPointParent = tempPoint;
queue.Enqueue(tempWayPoint);
}
}
//将场景中的瓦片,存放在字典wayPointDict中,在游戏一开始时
private void LoadAllWayPoints()
{
var wayPoints = FindObjectsOfType<WayPoint>();
foreach (var item in wayPoints)
{
var tempWayPoint = item.GetPosition();
if (waypoint.ContainsKey(tempWayPoint))
{
Debug.Log("skip overlap block" + item);
}
else
{
waypoint.Add(item.GetPosition(), item);
}
}
}
// 判断当前点是否为最终点
public void BFS()
{
for (int i = 0; i < waylist.Count; i++)
{
waylist[i].GetComponent<MeshRenderer>().material.color = new Color(1, 1, 1, 1);
}
startPoint.GetComponent<MeshRenderer>().material.color = new Color(0, 1, 0, 1);
endPoint.GetComponent<MeshRenderer>().material.color = new Color(1, 0, 0, 1);
queue.Clear();
queue.Enqueue(startPoint.GetComponent<WayPoint>());
while (queue.Count > 0&&isRunning)
{
tempPoint = queue.Dequeue();
// Debug.Log("Search From:" + tempPoint.GetPosition());
ExploreAround();
StopIfSerchEnd();
}
}
private void StopIfSerchEnd()//用于判断是否找到最佳路径
{
if (tempPoint == endPoint.GetComponent<WayPoint>())
{
//那么意味着我们已经找到终点,停止继续搜索路径
isRunning = false;
Debug.Log("serach is finish");
waylist=new List<WayPoint>();
waylist.Add(tempPoint);
while (tempPoint.WayPointParent!=startPoint.GetComponent<WayPoint>())
{
waylist.Add(tempPoint.WayPointParent);
tempPoint = tempPoint.WayPointParent;
tempPoint.GetComponent<MeshRenderer>().material.color = new Color(0, 1, 0, 1);//将最佳路径的每个点赋值为绿色
}
waylist.Add(startPoint.GetComponent<WayPoint>());
startPoint.GetComponent<MeshRenderer>().material.color = new Color(0, 0, 1, 1);
waylist.Reverse();
startPoint = endPoint;
}
else
{
Debug.Log("没有找到路径");
}
}
public List<WayPoint> ReturnPath()//刷新路径
{
BFS();
return waylist;
}
}
EnemyMovement
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyMovement : MonoBehaviour
{
[SerializeField] private List<GameObject> pathWayPoints = new List<GameObject>();
PathFinding pf;
WayPoint _point;
private void Start()
{
pf = FindObjectOfType<PathFinding>();
}
IEnumerator FindWayPoint(List<WayPoint> _WayPoint)
{
foreach (var item in _WayPoint)
{
//transform.position = item.transform.position+ new Vector3(0, 1f, 0);
_point = item;
yield return StartCoroutine(ToPoint(_point));
}
}
IEnumerator ToPoint(WayPoint _Point)
{
while (transform.position != (_Point.transform.position + new Vector3(0, 1f, 0)))
{
transform.position = Vector3.MoveTowards(transform.position, _Point.transform.position + new Vector3(0, 1f, 0), 3 * Time.deltaTime);
yield return null;
}
}
public void Onstart()//开启寻找路线
{
WayPoint[] way = FindObjectsOfType<WayPoint>();
for (int i = 0; i < way.Length; i++)
{
way[i].IsSerach = false;
}
StartCoroutine(FindWayPoint(pf.ReturnPath()));
}
public void OnStop()//停止寻找路线
{
StopAllCoroutines();
if (_point != null)
pf.StartPoint = _point.gameObject;
}
}