项目地址:https://github.com/kotomineshiki/AIFindPath
视频地址:多重寻路

综合寻路——包括攻击考量的寻路算法

GamePlay

这是一个《文明》+皇室战争的组合。
UI层使用状态机来实现以下操作
1. 点击棋子再点击格子,棋子移动向格子(AStar算法)
2. 点击棋子会弹出该棋子的属性介绍面板,再点击一次取消
3. 点击格子会弹出该格子的属性介绍面板,再点击一次取消
4. 点击底部召唤板,再点击格子,会在该格子处召唤一个棋子
5. 点击召唤板未点击格子时,会在鼠标停留的格子上出现一个虚影
6. 点击棋子再点击另一个棋子的时候,该棋子移动向另一个棋子,如果他们处于敌对阵营,则该棋子列入攻击列表
一些bug:
AStar算法不要使用多线程,因为如果节点情况有变,则会产生线程合并的冲突导致抖动。
其实Astar算法和作为AI的势能场算法是有冲突的,下次重构的时候应该特别注意只使用势能场。因为势能场本身就已经包括了寻路算法,所以没必要特意写一个寻路算法增加代码复杂度,这是这次经验不足导致的。
两个核心难点

Astar算法

先是自己实现了一遍,后来找到了效率更高的插件就重构了

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class MyAStar {public ArrayList openList;public ArrayList closeList;public int targetX;public int targetY;public Vector2Int start;public Vector2Int end;public Stack<string> parentList;//结果栈,存的格式是xypublic List<Vector2Int> resultPath;//结果队列//public Transform plane;//public Transform obstacle;private float alpha = 0;private float incrementPer = 0;public void SetStartGrid(int x, int y){start.x = x;start.y = y;GridMap.instance.grids[x, y].SetGridType(GridType.Start);openList.Add(GridMap.instance.grids[x, y]);}public void SetEndGrid(int x, int y){end.x = x;end.y = y;GridMap.instance.grids[x, y].SetGridType(GridType.End);}public void Clear(){GridMap.instance.grids[start.x, start.y].SetGridType(GridType.Normal);GridMap.instance.grids[end.x, end.y].SetGridType(GridType.Normal);//恢复初始状态for(int i = 0; i < GridMap.instance.row; ++i){for(int j = 0; j < GridMap.instance.column; ++j){GridMap.instance.grids[i, j].parent = null;GridMap.instance.grids[i, j].f = 0;GridMap.instance.grids[i, j].g = 0;GridMap.instance.grids[i, j].h = 0;}}parentList.Clear();openList.Clear();closeList.Clear();resultPath.Clear();}public MyAStar()//初始化函数{parentList = new Stack<string>();openList = new ArrayList();closeList = new ArrayList();resultPath = new List<Vector2Int>();Debug.Log("初始化完成");}public void Calculate(){Debug.Log("开始寻找路径");//yield return new WaitForSeconds(0.1f);//openList.Add(grids[startX, startY]);MyGrid currentGrid = openList[0] as MyGrid;while (openList.Count > 0 && currentGrid.gridType != GridType.End){currentGrid = openList[0] as MyGrid;if (currentGrid.gridType == GridType.End){Debug.Log("找到路径");GenerateResult(currentGrid);}for(int i = -1; i <= 1; i++){for(int j = -1; j <= 1; j++){if (i != 0 || j != 0){int x = currentGrid.x + i;int y = currentGrid.y + j;if (x >= 0 && y >= 0 && x < GridMap.instance.row && y < GridMap.instance.column&& GridMap.instance.grids[x, y].landForm != LandForm.Obstacle&& !closeList.Contains(GridMap.instance.grids[x, y])){int g = currentGrid.g + (int)(Mathf.Sqrt((Mathf.Abs(i) + Mathf.Abs(j))) * 10);if (GridMap.instance.grids[x, y].g == 0 || GridMap.instance.grids[x, y].g > g){GridMap.instance.grids[x, y].g = g;GridMap.instance.grids[x, y].parent = currentGrid;}GridMap.instance.grids[x, y].h = Manhattan(x, y);GridMap.instance.grids[x, y].f = GridMap.instance.grids[x, y].g + GridMap.instance.grids[x, y].h;if (!openList.Contains(GridMap.instance.grids[x, y])){openList.Add(GridMap.instance.grids[x, y]);}openList.Sort();}}}}closeList.Add(currentGrid);openList.Remove(currentGrid);if (openList.Count == 0){Debug.Log("未找到路径");}}}void GenerateResult(MyGrid currentGrid){if (currentGrid.parent != null){parentList.Push(currentGrid.x + "|" + currentGrid.y);resultPath.Add(new Vector2Int(currentGrid.x, currentGrid.y));GenerateResult(currentGrid.parent);}}IEnumerator ShowResult(){Debug.Log("显示开始"+parentList.Count);yield return new WaitForSeconds(0.3f);incrementPer = 1 / (float)parentList.Count;while (parentList.Count != 0){Debug.Log("走一步"+ parentList.Count);string str = parentList.Pop();yield return new WaitForSeconds(0.3f);string[] xy = str.Split(new char[]{'|'});int x = int.Parse(xy[0]);int y = int.Parse(xy[1]);alpha += incrementPer;GridMap.instance.objs[x, y].transform.GetChild(0).GetComponent<MeshRenderer>().material.color= new Color(1 - alpha, alpha, 0, 1);}}int Manhattan(int x,int y)//曼哈顿距离{return (int)(Mathf.Abs(targetX - x) + Mathf.Abs(targetY - y)) * 10;}}

AI部分

AI移动倾向需要满足的要求(按照意愿的优先级从高到低):
但友军在和敌人交战的时候,很愿意前去支援
愿意和同阵营的棋子在一起(夹击有加成,更加安全)
愿意前往距离敌军远的、属于敌人的格子(可以占领地盘)
愿意前往比较近的地方,而不愿意前往比较远的地方。
AI攻击需要满足的要求(按照意愿的优先级从高到低):
当有友军在攻击敌军时,会很愿意攻击被友军攻击的敌人
当自己血量较低的时候,不愿意进行攻击,愿意逃跑
当附近有敌人的时候,会进行攻击

实现方法:势能场+贪心算法
实现思路:给每个格子赋予一个优先度属性,每满足一个条件可以增加一些优先度(势能),每次需要做出决断的时候,计算对于该棋子的当前场面上的所有的格子(计算势能场分布),寻找估价最高的格子并选择前往(此处和势能场稍微有些不同,一般的势能场寻路算法是循着导数梯度到达势能的极值点,而因为寻路算法已经选用了AStar算法,就不再需要此处再写了—注意此处隐藏了一个冲突)每一次判断都选用对于当前局面能选择的最优秀的决策。
攻击判定:每到达一个格子会判断是继续行走还是对在该格子攻击范围内的敌人进行攻击。攻击动作也有一个估价函数。
每次完成一次行走或者攻击或者路径被中断时,就会向ChessManager通过观察者模式进行询问,查找下一步该做的事。

势能场介绍:2D的势能场是围绕某一个点(或者设计者期望拥有的属性)进行一个估值,总势能场是所有估值的叠加。AI应该选择当前附近势能变化最快(梯度)的走,也可以选择周围势能最高点作为目的地。对于障碍物,应该把势能设置为(无穷小|无穷大),这样在选择路径的时候,AI会“讨厌”往障碍物处走

using AStar_2D;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class Thinking : MonoBehaviour {//这个类是用来估值并决策的public int[,] Value;//用来计算各个格子的估值的public int X;public int Y;public Camp myself;public List<Index> answer;public List<Chess> canAttack;public ArrayList  AttackValue;// Use this for initializationvoid Start() {X = GameManager.instance.gridMap.gridX;Y = GameManager.instance.gridMap.gridY;answer = new List<Index>();canAttack = new List<Chess>();myself = this.GetComponent<Chess>().camp;Value = new int[X, Y];AttackValue = new ArrayList();Clear();//GetComponent<Thinking>().SeekWhatToDo();//StartCoroutine(trythink());}/*IEnumerator trythink(){yield return new WaitForSeconds(1);SeekWhatToDo();}*/void Clear(){for (int i = 0; i < X; ++i){for (int j = 0; j < Y; ++j){//Debug.Log(i + "  s" + j);Value[i, j] = 0;//全部数据清零}}}// Update is called once per framevoid Update() {}public void SeekWhatToDo()//做出决策{Clear();answer.Clear();canAttack.Clear();AttackValue.Clear();EvaluatingMove();//生成价值矩阵,然后找最大EvaluatingAttackAction();//寻找攻击价值最高的ArrayList temp=new ArrayList();for (int i = 0; i < X; ++i){for (int j = 0; j < Y; ++j){temp.Add(Value[i, j]);}}temp.Sort();temp.Reverse();for(int i = 0; i < temp.Count; ++i){Find((int)temp[i]);}if(canAttack.Count==0)//无法打的时候走路this.GetComponent<MyAgent>().setDestination(answer[0]);//前往最优点else//可以打的时候打人{ArrayList temp2 = new ArrayList(AttackValue);temp2.Sort();temp2.Reverse();this.GetComponent<Chess>().Attack(FindAttack((int)temp2[0]));}}void Find(int input){for (int i = 0; i < X; ++i){for (int j = 0; j < Y; ++j){if (Value[i, j] == input&&answer.Contains(new Index(i,j))==false)//值相等且不再列表里{answer.Add(new Index(i, j));}}}}Chess FindAttack(int input){for(int i = 0; i < canAttack.Count; ++i){if ((int)AttackValue[i] == input){return canAttack[i];}}return null;}void EvaluatingAttackAction()//判断打架这个行为是否合算{if (Camp.PlayerA == myself){}else if(Camp.PlayerB==myself){foreach(var i in GameManager.instance.chessManager.playerA){if (this.GetComponent<Chess>().CanAttack(i.GetComponent<Agent>().GetCurrentIndex())){canAttack.Add(i);}}//这样获得了可以供攻击的列表,下面判断攻击的价值for(int i = 0; i < canAttack.Count; ++i){int amount=0;amount += 110-(int)canAttack[i].hp; //血越少越有打的价值foreach(var temp in GameManager.instance.chessManager.playerA){//如果有友军在打,则应该优先攻打if (temp.isAttacking == canAttack[i]){amount += 40;}}AttackValue.Add(amount);}}}void EvaluatingMove(){//AI移动倾向需要满足的要求(按照意愿的优先级从高到低):EvaluatingRadius(this.GetComponent<Agent>().GetCurrentIndex());if (Camp.PlayerA == myself){foreach (var i in GameManager.instance.chessManager.playerA){EvaluatingFriend(this.GetComponent<Agent>().GetCurrentIndex());//愿意和同阵营的棋子在一起(夹击有加成,更加安全)if (i.isAttacking)//友军在和敌人交战的时候,很愿意前去支援{EvaluatingAttacking(i.ToAttack[0].GetComponent<Agent>().GetCurrentIndex());}}foreach(var i in GameManager.instance.chessManager.playerB){EvaluatingEnemy(i.GetComponent<Agent>().GetCurrentIndex());}for (int i = 0; i < X; ++i){for (int j = 0; j < Y; ++j){if (GameManager.instance.gridMap.tiles[i, j].tileType == Camp.Nobody)//无人占领区有比较大的吸引力{Value[i, j] += 2;}if (GameManager.instance.gridMap.tiles[i, j].tileType == Camp.PlayerB)//鼓励进攻{Value[i, j] += 1;}}}//愿意前往距离敌军远的、属于敌人的格子(可以占领地盘)}else if (Camp.PlayerB == myself){foreach (var i in GameManager.instance.chessManager.playerB){EvaluatingFriend(this.GetComponent<Agent>().GetCurrentIndex());if (i.isAttacking)//友军在和敌人交战的时候,很愿意前去支援{EvaluatingAttacking(i.ToAttack[0].GetComponent<Agent>().GetCurrentIndex());}}foreach (var i in GameManager.instance.chessManager.playerA){EvaluatingEnemy(i.GetComponent<Agent>().GetCurrentIndex());}for (int i = 0; i < X; ++i){for (int j = 0; j < Y; ++j){if (GameManager.instance.gridMap.tiles[i, j].tileType == Camp.Nobody)//无人占领区有比较大的吸引力{Value[i, j] += 2;}if (GameManager.instance.gridMap.tiles[i, j].tileType == Camp.PlayerA)//鼓励进攻{Value[i, j] += 1;}}}//愿意前往距离敌军远的、属于敌人的格子(可以占领地盘)}}void EvaluatingRadius(Index position)//传入自己的地址,以自己为半径来制造一个个等势面{for (int distance = 1; distance < 7; ++distance){Index tempA = position + new Index(-distance, distance);Index tempB = position + new Index(distance, distance);Index tempC = position + new Index(-distance, -distance);Index tempD = position + new Index(distance, -distance);for (int i = tempA.X; i <= tempB.X; ++i){//上面一行if (IsValid(new Index(i, tempA.Y))){// Debug.Log(i+ "     "+ tempA.Y);Value[i, tempA.Y] += 7 - distance;}}for (int i = tempC.X; i <= tempD.X; ++i){//下面一行if (IsValid(new Index(i, tempC.Y))){Value[i, tempC.Y] += 7 - distance;}}for (int i = tempC.Y + 1; i < tempA.Y; ++i){//左边if (IsValid(new Index(tempA.X, i))){Value[tempA.X, i] += 7 - distance;}}for (int i = tempD.Y + 1; i < tempB.Y; ++i){//右边if (IsValid(new Index(tempD.X, i))){Value[tempD.X, i] += 7 - distance;}}}}void EvaluatingAttacking(Index position)//传入敌人的地址,敌人周围的格子魅力增加{int distance = 1;Index tempA = position + new Index(-distance, distance);Index tempB = position + new Index(distance, distance);Index tempC = position + new Index(-distance, -distance);Index tempD = position + new Index(distance, -distance);for (int i = tempA.X; i <= tempB.X; ++i){//上面一行if (IsValid(new Index(i, tempA.Y))){Value[i, tempA.Y] += 10;}}for (int i = tempC.X; i <= tempD.X; ++i){//下面一行if (IsValid(new Index(i, tempC.Y))){Value[i, tempC.Y] += 10 ;}}for (int i = tempC.Y + 1; i < tempA.Y; ++i){//左边if (IsValid(new Index(tempA.X, i))){Value[tempA.X, i] += 10 ;}}for (int i = tempD.Y + 1; i < tempB.Y; ++i){//右边if (IsValid(new Index(tempD.X, i))){Value[tempD.X, i] += 10 ;}}}void EvaluatingFriend(Index position)//输入一个地址,把周围的格子的魅力+2{for(int distance = 1; distance < 5; ++distance){Index tempA = position + new Index(-distance, distance);Index tempB = position + new Index(distance, distance);Index tempC = position + new Index(-distance, -distance);Index tempD = position + new Index(distance, -distance);for (int i = tempA.X; i <= tempB.X; ++i){//上面一行if(IsValid(new Index(i, tempA.Y))){Value[i, tempA.Y] += 5-distance;}}for (int i = tempC.X; i <= tempD.X; ++i){//下面一行if (IsValid(new Index(i, tempC.Y))){Value[i, tempC.Y] += 5-distance;}}for(int i = tempC.Y + 1; i < tempA.Y; ++i){//左边if(IsValid(new Index(tempA.X, i))){Value[tempA.X, i] += 5-distance;}}for (int i = tempD.Y + 1; i < tempB.Y; ++i){//右边if (IsValid(new Index(tempD.X, i))){Value[tempD.X, i] += 5-distance;}}}}void EvaluatingEnemy(Index position)//输入一个地址,把周围的格子的魅力+2{for (int distance = 1; distance < 3; ++distance){Index tempA = position + new Index(-distance, distance);Index tempB = position + new Index(distance, distance);Index tempC = position + new Index(-distance, -distance);Index tempD = position + new Index(distance, -distance);for (int i = tempA.X; i <= tempB.X; ++i){//上面一行if (IsValid(new Index(i, tempA.Y))){Value[i, tempA.Y] += 5 - distance;}}for (int i = tempC.X; i <= tempD.X; ++i){//下面一行if (IsValid(new Index(i, tempC.Y))){Value[i, tempC.Y] += 5 - distance;}}for (int i = tempC.Y + 1; i < tempA.Y; ++i){//左边if (IsValid(new Index(tempA.X, i))){Value[tempA.X, i] += 5 - distance;}}for (int i = tempD.Y + 1; i < tempB.Y; ++i){//右边if (IsValid(new Index(tempD.X, i))){Value[tempD.X, i] += 5 - distance;}}}}bool IsValid(Index position){if (position.X >= X||position.X<0) return false;if (position.Y >= Y || position.Y < 0) return false;return true;}
}

为什么要用AStar+势能场

势能场本身其实可以进行寻路,但是在二维的坐标世界中,势能的梯度下降并不是像3维连续的势能的梯度下降,这就导致了一个bug:可能在等势面上(两个同等取值的格子)来回移动。这个是可以避免的(使用一个列表来管理优先前行的位置,前端估值高,后端估值低),发现重复移动则remove掉队首。但是这次真的没时间再做这个了,留给暑假腾讯NextIdea里解决吧。使用AStar还有除此之外的好处:势能场中的势能极值点往往是终点(水往低处流),这样可以获得比势能场寻路更短的路径(但不是最优的路径,因为势能场寻路可以选择被敌人攻击风险最小的方法)。

结语

在这个作业上实在是倾注了太多心血了,前后重构了三次,虽然最后bug还是蛮多的。我之后又重构了一次,想纯粹使用势能场函数进行AI判断,结果虽然也可以,但是bug很多,很容易死机。

unity学习:寻路算法(AStar算法)与简单AI(势能场估价算法)相关推荐

  1. java 路由算法_几种简单的负载均衡算法及其Java代码实现

    什么是负载均衡 负载均衡,英文 名称为Load Balance,指由多台服务器以对称的方式组成一个服务器集合,每台服务器都具有等价的地位,都可以单独对外提供服务而无须其他服务器的辅助.通过某种 负载分 ...

  2. unity3d学习(三)制作敌人简单AI和发射火球

    制作敌人简单AI和发射火球 写在前面: - 学习课本:Unity5实战 使用c#和unity开发多平台游戏-作者:Joseph Hocking - 内容:第三章 -unity3d学习(一)制作第一人称 ...

  3. MFC学习笔记之三(粒子系统+怪物简单AI+碰撞检测)

    到上海找到住的地方之后,干的第一件事,就是抓紧时间学习,为了找到工作努力ing... 备注:以下请参考http://blog.csdn.net/hust_xy/article/details/9374 ...

  4. AI美颜SDK算法详解

    AI美颜SDK是近几年兴起的新兴美颜方式,区别于传统的美颜工具,AI美颜采用人工智能的深度学习算法实现智能化美颜,下文小编将为大家讲解一下AI美颜SDK相关的知识. 一.与传统美颜的区别之处 从宏观角 ...

  5. 【Unity2D】实现敌人Enemy简单AI的巡回移动

    上一篇文章:[Unity2D]实现敌人随机简单AI来回移动_dangoxiba的博客-CSDN博客先把制作好动画帧表格导出来,我的是32*32像素的,按下面两张图把参数修改一下,并且裁剪出四张图来,把 ...

  6. Unity吃豆人敌人BFS广度(宽度)优先算法实现怪物追踪玩家寻路

    本人正在努力建设自己的公众号,大家可以关注公众号,此文章最近也会上线我的公众号,公众号将免费提供大量教学Unity相关内容,除了从Unity入门到数据结构设计模式外,我还会免费分享我再游戏开发中使用的 ...

  7. 算法学习笔记:简单数据结构及排序算法

    原学习视频跳转地址:https://www.bilibili.com/video/BV13g41157hK?p=2 本文为自学视频整理的简单笔记 目录 排序 冒泡排序 选择排序 插入排序 归并排序(递 ...

  8. 分类算法学习(二)——贝叶斯算法的原理及简单实现

    1.3.贝叶斯分类的基础--贝叶斯定理 每次提到贝叶斯定理,我心中的崇敬之情都油然而生,倒不是因为这个定理多高深,而是因为它特别有用.这个定理解决了现实生活里经常遇到的问题:已知某条件概率,如何得到两 ...

  9. 肤色检测算法 - 基于不同颜色空间简单区域划分的皮肤检测算法

    由于能力有限,算法层面的东西自己去创新的很少,很多都是从现有的论文中学习,然后实践的. 本文涉及的很多算法,在网络上也有不少同类型的文章,但是肯定的一点就是,很多都是不配代码的,或者所附带的代码都是象 ...

最新文章

  1. 在线图表编辑工具 draw.io 10.6.2 版本发布
  2. 《大话数据结构》第9章 排序 9.7 堆排序(上)
  3. java gettime_Java Util.getTime方法代码示例
  4. Link-Cut Tree
  5. 常见问题及解决方案(前端篇)
  6. Kafka JMX 监控 之 jmxtrans + influxdb + grafana
  7. jdbc连接对象的获取 20210409233805735
  8. 12010.linux应用程序之spi
  9. 狼性文化遭质疑,那我们当个佛系程序员可好?
  10. python输入一个整数、输出该整数的所有素数因子_【401】Python 求合数的所有质数因子...
  11. cookie控制窗口打开打开
  12. 计算今天是本学期第几周
  13. 瑜伽修身 偷师动物12招(组图)
  14. 有关H5第八章的页面布局与规划介绍
  15. 《前端》JavaScript总结
  16. 让你的桌面起飞吧(OpenSolaris2008.05 VirtualBox WinXP)
  17. zz-tcp参数配置
  18. Windows下及linux下PVM并行计算平台的搭建
  19. Linux 如何查看帮助信息
  20. stata豪斯曼检验报错

热门文章

  1. Java32位Win7系统Jdk_win7 32位旗舰版配置与调试JDK环境技巧【图文】
  2. [CMS漏洞]EmpireCMS_V7.5的一次审计【转载】
  3. 名帖322 启功 行书《自作诗十二屏》
  4. “当你不再是程序员,很多事会脱离掌控”—— 对话全球最大独立开源公司SUSE CTO...
  5. 一键HTTPS | 特邀您参与用户调研
  6. 嵌入式实时Hypervisor:XtratuM (2)
  7. Mac笔记本浏览器打不开网页解决办法
  8. 《口吃者的自我治疗》(7. 口吃者的12大行为准则之如何完成)
  9. 石以砥利,芝士傻妞——机器人助手
  10. POJ 3190: Stall Reservations