游戏中AI自由寻路,追随玩家,绕开障碍物(基于unity实现)

  • 前言
  • 主要实现功能
  • 主要代码分析
  • 游戏运行效果
  • 动手操作
  • 结语

前言

本博客中采用的寻路算法主要为A星算法,A星算法主要实现代码借鉴他人博客(忘记原文地址了,如果原作者刚好看到可以留言,博主将注明原文出处,若涉及侵权请联系博主核实后将立马删除)A星算法具体原理不在本文章描述范围, 若想深入学习算法原理可自行搜索关键字:A星算法。

主要实现功能

  1. AI 在将以自身为原点,生成自定义大小的正方形为检测范围;
  2. AI 在自由寻路状态时,随机选取正方形边缘上的一点为目标点,朝目标点移动,到达目标点时将再次选取随机点作为目标点进行移动;
  3. AI生成的地图中每个地图元素都会在成时发射一条向上2米的射线检测,用于检测障碍物(AI定义为1米身高,超过2米的障碍物并不会影响AI前进);
  4. 地图元素采用对象池模式,每次敌人切换目标时地图元素并不用重新生成,而是初始化后继续使用;
  5. AI在追随模式时,地图所有元素将与目标点做距离判定,将距离玩家最近的元素作为目标点,当AI到达目标点时若还为玩家位置将再次作出判定,直到接近玩家为止;
  6. 鼠标左键为自由寻路模式,右键为追随模式,游戏时可自行控制查看效果。

主要代码分析

  1. AI控制脚本
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;}
}
  1. 地图元素管理类
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 + rightneighbours = AssignNeighbours(i + 1, j, neighbours);//right//neighbours = AssignNeighbours(i + 1, j - 1, neighbours);//right + downneighbours = AssignNeighbours(i, j - 1, neighbours);//down//neighbours = AssignNeighbours(i - 1, j - 1, neighbours);//down + leftneighbours = 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;}}
  1. 地图元素类
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;elsereturn 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";}}}
  1. A星算法主要实现类
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);//初始化成本为0start.nodeTotalCost = 0;//估计成本等于自身到目标的位置start.estimateCost = HeuristicEstimateCost(start, goal);closeQueue = new PriorityQueue();//起始节点Node node = null;//打开队列长度大于0while (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

结语

如果你觉得文章还不错就点点关注吧!
如果你有更好的提议欢迎评论区留言或者私信!
如果你想在第一时间里了解更多关于我的作品,或者想了解编程界其他技术,请扫码关注个人微信公众号,公众号留言将持续带来更新!

游戏中AI自由寻路,追随玩家,绕开障碍物(基于unity实现)相关推荐

  1. 游戏中常用的寻路算法(6):地图表示

    在本系列文档大部分内容中,我都假设A*用于某种网格上,其中的"节点"是一个个网格的位置,"边"是从某个网格位置出发的各个方向.然而,A*可用于任意图形,不仅仅是 ...

  2. 即时战略游戏中实用的寻路算法分享

    http://www.gameres.com/340777.html GameRes游资网授权发布,文 / 伍一峰 RTS中的寻路系统一般需要满足有以下几个条件: 1. 效率高,因为rts普遍地图大, ...

  3. 游戏中常用的寻路算法的分享(3):A*算法的实现

    概述 剥除代码,A* 算法非常简单.算法维护两个集合:OPEN 集和 CLOSED 集.OPEN 集包含待检测节点.初始状态,OPEN集仅包含一个元素:开始位置.CLOSED集包含已检测节点.初始状态 ...

  4. 百万局对战教AI做人,技术解读FPS游戏中AI如何拟人化

    作者:johnxuan,腾讯 TEG 应用研究员 FPS 游戏 AI 是腾讯 AI Lab 的一大重要研究方向,其拟人化 AI 也在 FPS 手游<穿越火线-枪战王者(CFM)>春节期间上 ...

  5. 游戏中常用的寻路算法(5)预先计算好的路径的所用空间

    有时候,影响计算寻路路径的不是时间,而是计算路径所需的上百个单元格所占的空间.寻路是需要内存来运行寻路算法,还需要额外内存来存储寻到的路径.运行寻路算法(A*,开集或闭集)所需的临时空间经常会比存储这 ...

  6. 游戏中的道具与RMB玩家

    我们可将在游戏中的道具按功能和表现特点划分为功能型道具和表现型道具两类.功能型道具也就是我们常规意义上说的道具,能对角色的数值产生影响或是具有特殊功效的道具,例如游戏中经常使用的红蓝药水.任务道具等等 ...

  7. 游戏中常用的寻路算法的分享(2):Heuristics 函数

    转自:https://www.gameres.com/485150.html 启发式函数h(n)告诉A*从任何结点n到目标结点的最小代价评估值.因此选择一个好的启发式函数很重要. 启发式函数在A* 中 ...

  8. 《MFC游戏开发》笔记十 游戏中的碰撞检测进阶:地图类型障碍物判定

    本系列文章由七十一雾央编写,转载请注明出处. http://blog.csdn.net/u011371356/article/details/9394465 作者:七十一雾央 新浪微博:http:// ...

  9. 游戏中常用的寻路算法的分享(4)处理移动中的障碍物

    一个寻路算法会计算出一条绕过静止障碍物的路径,但如果障碍物会移动呢?当一个单位移动到达某特定点时,原来的障碍物可能不在那点了,或者在那点上出现了新的障碍物.如果路线可以绕过典型的障碍物,那么只要使用单 ...

最新文章

  1. 放个手机在单位自动打卡_钉钉自动打卡(家校打卡,寒假特辑)
  2. HDU-3038-How Many Answers Are Wrong
  3. 关于问题vxworks与linux区别
  4. 前端学习(1932)vue之电商管理系统电商系统之tree树形控件
  5. VB.NET Visual Basic
  6. 零式机器人_最帅机器人作品“EVA”“天元突破”谁才是男人真正的浪漫
  7. 小爱音箱mini系统故障怎么办_梦龙评机小米小爱音箱HD使用体验
  8. 框架简述 带你认识 Mybatis
  9. LeetCode(1029)——两地调度(JavaScript)
  10. android表格布局占满整行,Android布局之表格布局TableLayout详解
  11. Removing a detached instance--删除失败
  12. python超清壁纸_Python爬取5K分辨率超清唯美壁纸
  13. Terminal Emulator for Android(安卓终端模拟器)的使用
  14. 计算机编辑学,计算机常识及电文档编辑学习.doc
  15. Day739.GEO经纬度数据结构自定义数据结构 -Redis 核心技术与实战
  16. 日期转换 例如 二零零六年十二月二十一日 转换成 2006年12月21日
  17. 如何 使用 apache 访问 本地目录及本地文件
  18. BGP协议详解及工作原理
  19. 卢卡斯定理及python实现
  20. C语言随机刷新,C语言 刷新缓冲区

热门文章

  1. Csgo 控制台——代码
  2. 应用在华为P9手机上安装失败原因分析 (错误码:-110)
  3. 查看外键名称查看数据库外键名字
  4. 数字IC手撕代码--小米科技(除法器设计)
  5. PTA L-1-30~L-1-56L-2-1~L-2-10
  6. Pygame之接小弹珠
  7. 一键安装系统的各种好处
  8. 再获殊荣!引力互联荣获“2020年度数据标注产业创新奖”
  9. 汇编——数组字的传输
  10. 46.以太坊源码分析(46)p2p-peer.go源码分析