赞
踩
本博客中采用的寻路算法主要为A星算法,A星算法主要实现代码借鉴他人博客(忘记原文地址了,如果原作者刚好看到可以留言,博主将注明原文出处,若涉及侵权请联系博主核实后将立马删除)A星算法具体原理不在本文章描述范围, 若想深入学习算法原理可自行搜索关键字:A星算法。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Enemy : MonoBehaviour { //内部单例 private static Enemy Instance = null; //路径 private List<Node> path = new List<Node>(); //起点 private GameObject start; //目标 private GameObject end; //开始寻路 public bool startPathfinding = true; //克隆体 public GameObject cube; //行 public int row; //计时器 public float timer = 0; //地图 private Dictionary<Vector3, Node> map = new Dictionary<Vector3, Node>(); //圆点 private GameObject dot; //辅助线 private LineRenderer line = null; //移动点数量 public int pathCount = 0; /// <summary> /// 单利访问 /// </summary> public static Enemy instance { get { return Instance; } } private void Awake() { Instance = this; //辅助线 line = new GameObject("Line").AddComponent<LineRenderer>(); line.startWidth = 0.1f; line.endWidth = 0.1f; dot = new GameObject("Dot"); } private void Update() { //测试 if (Input.GetMouseButtonDown(0)) AutoFindWay(); if (Input.GetMouseButtonDown(1)) PursueTarget(); if (startPathfinding) { if (path.Count > 0) { transform.position = Vector3.MoveTowards(transform.position, path[pathCount].transform.position, Time.deltaTime); transform.LookAt(path[pathCount].transform.position); if (Vector3.Distance(transform.position, path[pathCount].transform.position) <= 0) { pathCount--; } } } else { Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); if (Physics.Raycast(ray, out RaycastHit hit) && hit.collider != null && hit.collider.GetComponent<Node>() != null) { path.Clear(); FindTargetPath(hit.collider.gameObject); startPathfinding = true; } } } /// <summary> /// 自动寻路 /// </summary> public void AutoFindWay() { path.Clear(); NewPath(); startPathfinding = true; } /// <summary> /// 追击目标 /// </summary> public void PursueTarget() { path.Clear(); FindTargetPath(FindObjectOfType<Player>().gameObject); startPathfinding = true; } /// <summary> /// 新路线 /// </summary> private void NewPath() { print("寻找新路径 >>>>>>>>>>"); //原点到两边距离相等 if (row % 2 == 0) row++; //圆点位置等于自身位置 dot.transform.position = transform.position; //以自身为圆点形成坐标坐标系 MovingRangeFormationPoint(map,row); //设置地图 NodeManager.Instance.getMap(dot.GetComponentsInChildren<Node>()); //起点 start = map[FindRecently(map, transform.transform)].gameObject; //目标点 end = map[EdgeGetTarget(start.transform)].gameObject; //延迟0.5s寻路 Invoke("FindWay", 0.5f); } /// <summary> /// 找到目标 /// </summary> public void FindTargetPath(GameObject target) { print("以距离目标最近的点作为路径 >>>>>>"); //原点到两边距离相等 if (row % 2 == 0) row++; //圆点位置等于自身位置 dot.transform.position = transform.position; //以自身为圆点形成坐标坐标系 MovingRangeFormationPoint(map,row); //设置地图 NodeManager.Instance.getMap(dot.GetComponentsInChildren<Node>()); //起点 start = map[FindRecently(map, transform.transform)].gameObject; //目标点 end = map[FindRecently(map,target.transform)].transform.gameObject; //延迟1s寻路 Invoke("FindWay", 0.5f); } /// <summary> /// 以自身为圆点形成坐标坐标系 /// </summary> private void MovingRangeFormationPoint(Dictionary<Vector3, Node> map, int row) { print("以自身为圆点形成坐标坐标系"); //以自身为原点 Vector3 centerPos = dot.transform.position - new Vector3(row * 0.5f - 0.5f, 0, row * 0.5f - 0.5f); //复用之前的对象 if (map.Count > 0) { //清空障碍物 NodeManager.Instance.CloseObstacle(); //下标 int index = 0; //地图上的点(重复利用) List<Node> points = new List<Node>(); //找出之前的点 foreach (Vector3 i in map.Keys) { //坐标初始化 map[i].Init(); //加入对象池列表 points.Add(map[i]); } //清空地图 map.Clear(); for (int i = 0; i < row; i++) { for (int j = 0; j < row; j++) { //正方形坐标 Vector3 pos = new Vector3(centerPos.x + i, 0, centerPos.z + j); //点的位置重新赋值 points[index].transform.position = pos; //加入地图 map.Add(pos, points[index]); //下一个点 index++; } } } else { for (int i = 0; i < row; i++) { for (int j = 0; j < row; j++) { Vector3 pos = new Vector3(centerPos.x + i, 0, centerPos.z + j); //正方形坐标 GameObject game = Instantiate(cube, pos, Quaternion.identity); //将地图设置为圆点的之物体 game.transform.SetParent(dot.transform); //点坐标位置 game.name = pos.ToString(); //添加至地图 map.Add(pos, game.GetComponent<Node>()); //坐标初始化 map[pos].Init(); } } } } /// <summary> /// 寻找路线 /// </summary> public void FindWay() { if (end.GetComponent<Node>().nObstacle) { NewPath(); Debug.LogError("目标为障碍物,重新寻路"); return; } //路径 path = AStar.FindPath(start.GetComponent<Node>(), end.GetComponent<Node>()); //画辅助线 line.positionCount = path.Count; for (int i = 0; i < path.Count; i++) { line.SetPosition(i, path[i].gameObject.transform.position); } //移动路径大小 pathCount = path.Count - 1; } /// <summary> /// 边缘获取目标位置 /// </summary> private Vector3 EdgeGetTarget(Transform dot) { List<Vector3> squarePoints = new List<Vector3>(); int width = (row - 1) / 2; for (int j = row - 1; j >= 0; j--) { squarePoints.Add(dot.position + new Vector3(width - j, 0, width)); } for (int j = 1; j < row; j++) { squarePoints.Add(dot.position + new Vector3(width, 0, width - j)); } for (int j = 1; j < row; j++) { squarePoints.Add(dot.position + new Vector3(width - j, 0, -width)); } for (int j = row - 2; j > 0; j--) { squarePoints.Add(dot.position + new Vector3(-width, 0, width - j)); } int randomTarget = Random.Range(0, squarePoints.Count - 1); return squarePoints[randomTarget]; } /// <summary> /// 寻找地图最近的点 /// </summary> /// <param name="target"></param> /// <returns></returns> private Vector3 FindRecently(Dictionary<Vector3, Node> map, Transform target) { List<Vector3> dos = new List<Vector3>(); //找出地图上的点 foreach (Vector3 i in map.Keys) { dos.Add(i); } //寻找距离玩家最近的点 Vector3 miniDis = dos[0]; for (int i = 1; i < dos.Count; i++) { //找出距离玩家最近位置点 if (Vector3.Distance(miniDis, target.position) > Vector3.Distance(dos[i], target.position)) { miniDis = dos[i]; } } return map[miniDis].transform.position; } }
using System.Collections.Generic; using UnityEngine; public class NodeManager : MonoBehaviour { private static NodeManager instance = null; public static NodeManager Instance { get { if (instance == null) instance = FindObjectOfType(typeof(NodeManager)) as NodeManager; if (instance == null) Debug.LogError("Could not find a NodeManager. Please add one NodeManager in the scense"); return instance; } } //地图坐标 public Node[,] nodes; /// <summary> /// 障碍物列表 /// </summary> public List<GameObject> obstacleList = new List<GameObject>(); //设置为障碍物 public void SetObstacle(GameObject obs) { if (obstacleList.Contains(obs)) return; //障碍物列表 obstacleList.Add(obs); //设置障碍物 obs.GetComponent<Node>().MarkAsObstacle(); } /// <summary> /// 清理障碍物 /// </summary> public void CloseObstacle() { foreach (GameObject i in obstacleList) { i.GetComponent<Renderer>().material.color = Color.white; } obstacleList.Clear(); print("清理障碍物"); } //得到地图 public void getMap(Node[] games) { int index = -1; nodes = new Node[Enemy.instance.row, Enemy.instance.row]; for (int i = 0; i < Enemy.instance.row; i++) { for (int j = 0; j < Enemy.instance.row; j++) { index++; nodes[i, j] = games[index]; } } } /// <summary> /// 得到附近节点 /// </summary> /// <param name="node"></param> /// <returns></returns> public List<Node> GetNeighbours(Node node) { //附近节点 List<Node> neighbours = new List<Node>(); for (int i = 0; i < Enemy.instance.row; i++) { for (int j = 0; j < Enemy.instance.row; j++) { if(nodes[i,j] == node) { neighbours = AssignNeighbours(i, j + 1, neighbours);//up //neighbours = AssignNeighbours(i + 1, j + 1, neighbours);//up + right neighbours = AssignNeighbours(i + 1, j, neighbours);//right //neighbours = AssignNeighbours(i + 1, j - 1, neighbours);//right + down neighbours = AssignNeighbours(i, j - 1, neighbours);//down //neighbours = AssignNeighbours(i - 1, j - 1, neighbours);//down + left neighbours = AssignNeighbours(i - 1, j, neighbours);//left //neighbours = AssignNeighbours(i - 1, j + 1, neighbours);//left + up } } } return neighbours; } /// <summary> /// 分配的临近点 /// </summary> /// <param name="row"></param> /// <param name="col"></param> /// <param name="neighbours"></param> /// <returns></returns> private List<Node> AssignNeighbours(int row, int col, List<Node> neighbours) { // if (row >= 0 && col >= 0 && row < Enemy.instance.row && col < Enemy.instance.row) { //不属于障碍物 if(!obstacleList.Contains(nodes[row, col].gameObject)) { neighbours.Add(nodes[row, col]); } } return neighbours; } }
using System; using UnityEngine; using UnityEngine.UI; public class Node : MonoBehaviour, IComparable { /// <summary> /// 节点到总成本 G /// </summary> public float nodeTotalCost = 1.0f; /// <summary> /// 估计成本 F /// </summary> public float estimateCost = 0.0f; /// <summary> /// 路径的父节点 /// </summary> public Node nParent; /// <summary> /// 是否障碍物 /// </summary> public bool nObstacle = false; /// <summary> /// 初始化 /// </summary> public void Init() { nodeTotalCost = 1.0f; estimateCost = 0.0f; nObstacle = false; nParent = null; //向上2米检测是否有障碍物 if (Physics.Raycast(transform.position, transform.up, out RaycastHit hit, 2)) { //加入障碍物列表 NodeManager.Instance.SetObstacle(gameObject); //画出辅助线 Debug.DrawRay(transform.position, transform.up * 2, Color.red, 10); } } /// <summary> /// 设置为障碍物 /// </summary> public void MarkAsObstacle() { nObstacle = true; } /// <summary> /// 对比路径成本 /// </summary> /// <param name="obj"></param> /// <returns></returns> public int CompareTo(object obj) { Node node = (Node)obj; if(estimateCost < node.estimateCost) return -1; else if(estimateCost > node.estimateCost) return 1; else return 0; } public override string ToString() { return transform.position.x + "," + transform.position.y; } /// <summary> /// 显示大小 /// </summary> public void ShowCost() { var text = GetComponentInChildren<Text>(); if (nObstacle) { text.text = "障碍物"; text.color = Color.red; } else { text.text = "F: " + estimateCost.ToString("F2") + "\n" + "G: " + nodeTotalCost.ToString("F2") + "\n"; } } }
using System.Collections.Generic; using UnityEngine; public class AStar : MonoBehaviour { //开启队列 public static PriorityQueue openQueue; //关闭队列 public static PriorityQueue closeQueue; //触发式评估大小 private static float HeuristicEstimateCost(Node currentNode, Node goalNode) { //返回两个节点的距离 return Vector3.Distance(currentNode.transform.position, goalNode.transform.position); } /// <summary> /// 寻找路径 /// </summary> /// <param name="start">开始位置</param> /// <param name="goal">目标位置</param> /// <returns></returns> public static List<Node> FindPath(Node start, Node goal) { openQueue = new PriorityQueue(); //天际节点 openQueue.Add(start); //初始化成本为0 start.nodeTotalCost = 0; //估计成本等于自身到目标的位置 start.estimateCost = HeuristicEstimateCost(start, goal); closeQueue = new PriorityQueue(); //起始节点 Node node = null; //打开队列长度大于0 while (openQueue.Count != 0) { //得到起点 node = openQueue.First(); //判断起点是否等于目标点 if(node == goal) { //node.GetComponentInChildren<Text>().text = "目标点"; print("寻路完毕"); break; } //得到周围的位置 List<Node> neighbours = NodeManager.Instance.GetNeighbours(node); foreach(Node neighbour in neighbours) { //不在关闭队列 if (!closeQueue.Contains(neighbour)) { //节点总成本=节点初始节点 + 附近点到起点距离 neighbour.nodeTotalCost = node.nodeTotalCost + HeuristicEstimateCost(neighbour,node); //估计成本 = 附近点到目标点距离 + 节点总成本 neighbour.estimateCost = HeuristicEstimateCost(neighbour, goal) + neighbour.nodeTotalCost; //附近节点的父物体等于起点 neighbour.nParent = node; //不在开启队列 if (!openQueue.Contains(neighbour)) { //加入开启队列 openQueue.Add(neighbour); } } } //起点加入关闭队列 closeQueue.Add(node); //起点从打开队列移除 openQueue.Remove(node); } if (node != goal) { Debug.LogError("找不到路径"); } return CalculatePath(node); } /// <summary> /// 计算路径 /// </summary> /// <param name="node"></param> /// <returns>计算后得到的路径</returns> private static List<Node> CalculatePath(Node node) { //路径列表 List<Node> path = new List<Node>(); while (node != null) { //节点加入路径列表 path.Add(node); //设置父物体 node = node.nParent; } return path; } }
场景配置图
游戏运行效果
打开unity,新建一个3D项目,将 AI自动寻路+绕路+追随.unitypackage文件导入项目,打开Game场景,点击开始运行就可以看到效果了!
AI自动寻路+绕路+追随.unitypackage
资源提取码: 7t1p
如果你觉得文章还不错就点点关注吧!
如果你有更好的提议欢迎评论区留言或者私信!
如果你想在第一时间里了解更多关于我的作品,或者想了解编程界其他技术,请扫码关注个人微信公众号,公众号留言将持续带来更新!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。