提到寻路算法,大家都会想到A*算法。

在度娘找了不少代码,看了不少教程之后,尤其是这个文章中提到的总结:http://www.cppblog.com/christanxw/archive/2006/04/07/5126.html

A*算法总结(Summary of the A* Method)

Ok ,现在你已经看完了整个的介绍,现在我们把所有步骤放在一起:

1.         把起点加入 open list 。

2.         重复如下过程:

a.         遍历 open list ,查找 F 值最小的节点,把它作为当前要处理的节点。

b.         把这个节点移到 close list 。

c.         对当前方格的 8 个相邻方格的每一个方格?

◆     如果它是不可抵达的或者它在 close list 中,忽略它。否则,做如下操作。

◆     如果它不在 open list 中,把它加入 open list ,并且把当前方格设置为它的父亲,记录该方格的 F , G 和 H 值。

◆     如果它已经在 open list 中,检查这条路径 ( 即经由当前方格到达它那里 ) 是否更好,用 G 值作参考。更小的 G 值表示这是更好的路径。如果是这样,把它的父亲设置为当前方格,并重新计算它的 G 和 F 值。如果你的 open list 是按 F 值排序的话,改变后你可能需要重新排序。

d.         停止,当你

◆     把终点加入到了 open list 中,此时路径已经找到了,或者

◆     查找终点失败,并且 open list 是空的,此时没有路径。

3.         保存路径。从终点开始,每个方格沿着父节点移动直至起点,这就是你的路径。

我按照这个思路中的总结,写了一个算法出来,开启列表和关闭列表是基于stl来实现的。

大概10000*10000的地图,寻路寻下来,要用30秒的时间,汗颜,如果比较复杂的地形,要用1分多钟。。。

最后我自己对我自己的代码做了一个总结,发现主要慢的地方是这几个步骤:

a.         遍历 open list ,查找 F 值最小的节点,把它作为当前要处理的节点。

实际上这个步骤,是为了直接对路径进行排序,如果不这么做,最终的路径,很可能会出现很多重复来回走的路,但是,这个BUG是可以在最终筛选节点的时候在来处理的,最终筛选的时候来处理的效率要比在寻路算法中直接搜索效率得多,如果你是游戏开发程序员,那么你的算法不得不这么做,使用二叉堆来搞这个步骤会比较快,或者你先实现短距离最佳路径,在使用远距离寻路时用短距离的走路函数配合最后筛选的方式也可以实现寻路,如果你是游戏外挂作者,你就可以不排序,而最后来筛选。

◆     如果它是不可抵达的或者它在 close list 中,忽略它。否则,做如下操作。

◆     如果它不在 open list 中,把它加入 open list ,并且把当前方格设置为它的父亲,记录该方格的 F , G 和 H 值。

◆     如果它已经在 open list 中,检查这条路径 ( 即经由当前方格到达它那里 ) 是否更好,用 G 值作参考。更小的 G 值表示这是更好的路径。如果是这样,把它的父亲设置为当前方格,并重新计算它的 G 和 F 值。如果你的 open list 是按 F 值排序的话,改变后你可能需要重新排序。

在估价8方向的逻辑中,遍历两个列表,是非常不明智的,尤其是关闭列表,这个列表只会一直增长,不会有减少的情况。

遍历open list,是很要命的事,10000*10000的地图,很轻松的就到几万个节点了。每次寻找下一步,都遍历一下,最终搞下来,那是相当之慢啊。

但是更要命的是查close list,每寻找下一个节点,就要查8次。如果真的去搜容器,搜链表,那最终寻路寻下来会奇慢无比。

找到问题所在之后,那就对症下药,抛弃这些耗时的代码,重新设计寻路流程:

1、自行分配内存,创建一个二值化的地图,这样就避免了查找关闭列表,而是以数组的形式直接访问坐标的状态。

2、实现设置/获取某个坐标状态接口(开启或关闭),既然需要自己创建二值化地图,那么就应该要实现设置,获取二值化地图的接口给寻路算法使用。

3、把起始坐标作为路径列表头

重复如下过程:

a、从起点开始估价,每次估价设置当前节点为关闭,然后对比7方向(上一个节点已经在上一次置为关闭,当前节点也在估价开始时关闭,所以是7个方向)

b、选择距离目标坐标最近的一个方向来作为下一次估价的节点,并设置当前节点为它的父节点。

c、如果7个方向都不能走,说明是一条死路,回溯到他的父节点重新寻找其他方向。

结束:直到你的当前坐标为目标坐标时,或者路径列表为空时

结束后,如果路径列表不为空,则根据路径列表筛选出最终路径。

整个算法流程就是这样,修改后,10000*10000的简单地图寻路,只需0-30毫秒,比较复杂的地图不超过100毫秒,反正跟之前对比是比较神了。

反正我自己也不知道这个算法算不算A*算法了,反正效率是比那种随时查表的方式高多了。

关键代码如下:

#define MHD_X 2
#ifndef MINSIFT
#define MINSIFT 60.0
#endif
#ifndef MAXSIFT
#define MAXSIFT 120.0
#endif
#ifndef MHD_X
#define MHD_X 1
#endif
#ifndef MHD_Y
#define MHD_Y 1
#endiftypedef struct _APOINT{int x;int y;_APOINT *parent;
}APOINT, *LPAPOINT;class CAStarFinder
{
public:CAStarFinder(){m_nTX = -1; m_nTY = -1; m_pMap = NULL; m_pSafePoint = NULL;}CAStarFinder(int nTX, int nTY) : m_nTX(nTX), m_nTY(nTY), m_pMap(NULL) , m_pSafePoint(NULL){}//构造函数参数 终点X, 终点Yvoid Search(int X, int Y, std::vector<POINT> &vResult);//搜索函数,参数为:起点X, 起点Y, 传入一个容器返回最终筛选好的节点void SetMapAttributes(bool *pMap, DWORD nWidth = 0xFFFFFFFF, DWORD nHeight = 0xFFFFFFFF);//设置地图属性,参数为:地图指针, 宽度, 高度void SetTargetPos(int X, int Y){m_nTX = X; m_nTY = Y;}//由于这个类是2个构造函数,一个带参数,一个不带参数,所以公开一个设置目标地址参数
private:LPAPOINT Manhattan(LPAPOINT lpPoint);//估价函数
private:int m_nTX, m_nTY;bool *m_pMap;DWORD m_nMapWidth, m_nMapHeight;LPAPOINT m_pSafePoint;
};
//算法中使用的获取距离函数
double _p2g(int x1, int y1, int x2, int y2)
{return sqrt(pow(double(abs(x1 - x2)), 2) + pow(double(abs(y1 - y2)), 2));
}//调试输出函数
void _outf(const char *format, ...)
{va_list al;char buf[BLX_MAXSIZE];va_start(al, format);_vsnprintf(buf, BLX_MAXSIZE, format, al);va_end(al);OutputDebugStringA(buf);
}
struct _find_astar_note_gap{_find_astar_note_gap(int X, int Y, double Gap) : _X(X), _Y(Y), _Gap(Gap) {}bool operator()(POINT &point){return _p2g(point.x, point.y, _X, _Y) < _Gap;}int _X, _Y;double _Gap;
};void CAStarFinder::SetMapAttributes(bool *pMap, DWORD nWidth, DWORD nHeight)
{m_pMap = pMap;m_nMapWidth = nWidth;m_nMapHeight = nHeight;
}LPAPOINT CAStarFinder::Manhattan(LPAPOINT lpPoint)
{int nX = lpPoint->x, nY = lpPoint->y;int aX[3] = {nX - MHD_X, nX, nX + MHD_X};int aY[3] = {nY - MHD_Y, nY, nY + MHD_Y};_SetMapValue(m_pMap, nX, nY, false);//设置坐标开启关闭状态,这个函数应该由你自己编写bool bState = 0;double dbMinGap = 10000000.0, dbTemp;nX = -1;nY = -1;for(int i = 0; i < 3; i++){for(int j = 0; j < 3; j++){//使用交叉方式遍历7个位置(当前坐标在一开始就已经置为关闭了,他的父节点也在上次置为关闭了)if(aX[j] > m_nMapWidth || aY[i] > m_nMapHeight)continue;//坐标越界则直接下一圈bState = _GetMapValue(m_pMap, aX[j], aY[i]);//获取坐标开启关闭状态,这个函数应该由你自己编写//由于_GetMapValue会用得非常频繁,所以不应该以任何面向对象的形式或者各种复杂的结构指针的形式来调用,直接写一个函数来让编译器硬编码定位过去,是最效率的方法if(!bState){//为关闭则直接下一圈循环continue;}else{dbTemp = _p2g(aX[j], aY[i], m_nTX, m_nTY);//计算绝对距离if(dbTemp < dbMinGap){//更小的绝对距离表示更好的方向dbMinGap = dbTemp;nX = aX[j];nY = aY[i];}}}}if(nX > 0 && nY > 0){//7方向任意一个有效时,新建一个节点,把当前节点设置为他的父节点,并返回这个新建的节点LPAPOINT pResult = new APOINT;pResult->x = nX;pResult->y = nY;pResult->parent = lpPoint;return pResult;}//否则就是死路,返回NULLreturn NULL;
}void CAStarFinder::Search(int X, int Y, std::vector<POINT> &vResult)
{outf("%d, %d, %d, %d, %d, %d", X, Y, m_nTX, m_nTY, m_nMapWidth, m_nMapHeight);if((int)m_nTX < 0 || (int)m_nTY < 0 || X < 0 || Y < 0 || (DWORD)X > m_nMapWidth || (DWORD)Y > m_nMapHeight || !m_pMap)return;APOINT *sp = new APOINT;sp->x = X;sp->y = Y;sp->parent = NULL;LPAPOINT point = sp;LPAPOINT p2 = NULL;while(m_pSafePoint != NULL){//这个循环是防止同一个对象操作时因线程强制结束而导致的内存泄漏问题p2 = m_pSafePoint;m_pSafePoint = m_pSafePoint->parent;delete p2;}DWORD dwTime = clock();do{p2 = point;point = Manhattan(p2);//估价并返回下一个最小距离的节点if(!point){//估价后,如果返回NULL,则说明这条路是死路,此时应该回溯到上一个节点(父节点)重新搜索point = p2->parent;//置当前节点为父节点delete p2;if(!point)break;//如果当前节点置为他的父节点后仍为NULL,则说明这个路径列表已经为空了,应该检查://1、坐标是否正确(起点和终点)//2、地图数据是否正确//3、获取地图状态值的函数是否正常}}while(_p2g(point->x, point->y, m_nTX, m_nTY) > 20.0);//由于我对最终目标坐标不是绝对精确,所以我就这么干了,如果要求绝对精确,应该直接==判断。int nCount = 0;int nResultCount = 0;int nX, nY;POINT ptTemp;m_pSafePoint = point;if(point){//筛选路径,我是根据已经选定的所有节点与下一节点的距离必须大于MINSIFT 最后选定的节点与下一节点的距离必须小于 MAXSIFT的距离来筛选,该距离可以自行根据需要设定,这两个常量宏在头文件中,可以在包含前自定义nX = point->x;nY = point->y;ptTemp.x = nX;ptTemp.y = nY;vResult.clear();do{if( _p2g(ptTemp.x, ptTemp.y, point->x, point->y) < 120.0 && std::find_if(vResult.begin(), vResult.end(), _find_astar_note_gap(point->x, point->y, 60.0)) == vResult.end() ){//为什么要遍历整个容器来筛选距离大于MINSIFT的节点而不是只对比上个节点?//原因是列表不是排序好的,如果只遍历一个节点,非常有可能选到往回走的路//将符合条件的节点加入到容器ptTemp.x = point->x;ptTemp.y = point->y;vResult.push_back(ptTemp);nResultCount++;}//清理内存p2 = point;point = point->parent;delete p2;nCount++;m_pSafePoint = point;}while(point != NULL);}dwTime = clock() - dwTime;_outf("最终寻路到:%X, %X", nX, nY);_outf("路径节点数:%d", nCount);_outf("筛选节点数:%d", nResultCount);_outf("算法耗时:%d 毫秒", dwTime);
}

必要的stl的头文件:

#include <vector>
#include <algorithm>

必要的C函数库头文件:

#include <math.h>

其他的我也忘了还需要什么了,好像调试输出的函数需要一个可变参数的头文件。

返回最终路径,建议使用queue或者list,push_front向前添加节点,如果像我一样使用vector,则返回的最终路径是反向的。

该算法寻路的准确性不高,这是弱点,如果要对这个算法改良,需要在选到一个节点之后,进行判断所有已有节点中是否有和他相邻的节点,如果有,把已有的父节点之后的节点删除,重设他的父节点,这样做,对效率是有一些提升,因为整个列表中,那些无用的节点已经被排除在外了,但是准确性依旧不高。但对于游戏外挂寻路来说,这个算法实际上是很好的。

最后要注意的问题:

1、不要试图把这种寻路算法写成基类然后继承来调用,不信你可以往类中加入一个虚函数看看那效率,会慢很多,因为通过访问虚函数表,在调用最终的函数,这个过程会很有很大开销。在处理量少的情况下可能影响不大,但是处理量大的时候,目测是会慢3-10倍的样子,根据地图复杂度而定。

2、最好不要以面向过程的编程方式来写这种算法,因为需要的参数比较多,如果全靠压栈传参,效率会比面向对象的方式慢不少。

最后装B的说明下理由:

C++的thiscall 是用寄存器来传递this指针的   外mov ecx, esi   内mov esi, ecx  大概是这种方式

一个参数压栈指令push 会比上面那两条指令慢n倍,push会访问内存,内存跟的速度跟寄存器不是一个级别的。

而在函数的内部,面向过程的函数始终还是要通过ebp或者esp寄存器来访问栈中的参数,thiscall内部可以通过this指针来访问类成员,这和访问参数是一个道理,反正都要寻址,这个是避免不了了,所以能避免的就是尽可能的少的传递参数,如果你实在纠结这点开销,你可以把类成员函数都设计成无参数,全靠成员变量来操作,当然这个代码得你自己去改了。

游戏寻路算法的简单实现相关推荐

  1. 游戏思考24:游戏寻路算法思考和Unity场景相关代码开发原则及导入地图数据(10/09)

    文章目录 一.游戏寻路算法 1)总体划分为 2)取几种算法详细介绍 (1)JPS介绍及其优化 二.场景相关开发原则 1)Unity开发商业项目场景开发基本原则 2)商业项目的大体流程和组织思路 一.游 ...

  2. 【Android】基于A星寻路算法的简单迷宫应用

    简介 基于[漫画算法-小灰的算法之旅]上的A星寻路算法,开发的一个Demo.目前实现后退.重新载入.路径提示.地图刷新等功能.没有做太多的性能优化,算是深化对A星寻路算法的理解. 界面预览: 初始化: ...

  3. 盘点即时战略游戏中高实用性寻路算法

    编者按:在即时战略(RTS)游戏中,寻路系统可谓其核心要素之一,但它的算法也是较难攻克的内容之一.在这篇文章中,来自iSamurai工作室的伍一峰为广大游戏开发者带来了他对即时战略游戏寻路算法的深入思 ...

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

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

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

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

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

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

  7. 如何快速找到最优路线?深入理解游戏中寻路算法

    如果你玩过MMOARPG游戏,比如魔兽,你会发现人物行走会很有趣,为了模仿人物行走的真实体验,他们会选择最近路线达到目的地,期间会避开高山或者湖水,绕过箱子或者树林,直到走到你所选定的目的地. 这种看 ...

  8. java a星寻路算法_用简单直白的方式讲解A星寻路算法原理

    很多游戏特别是rts,rpg类游戏,都需要用到寻路.寻路算法有深度优先搜索(DFS),广度优先搜索(BFS),A星算法等,而A星算法是一种具备启发性策略的算法,效率是几种算法中最高的,因此也成为游戏中 ...

  9. 关于小游戏魔塔的优化算法(2)---用A*改造寻路算法

    魔塔添加新的功能! 上一节我们做到了寻找可行走路径,但是不够 因为魔塔中充斥着各色各样的怪物,门.我们可行走的路径实在太少了! 现在我们作为一名玩家,想通关游戏,我们大概每层要做两件事情 用损血最少的 ...

最新文章

  1. pandas 里面对nan的判断
  2. zBrow压力测试图
  3. 文件上传服务器jvm调优,JVM性能调优解决方案(12页)-原创力文档
  4. linux图形界面基本知识(X、X11、Xfree86、Xorg、GNOME、KDE之间的关系)
  5. python【数据结构与算法】深入浅出Linear(线性表)
  6. [人生]不经历风雨怎么见彩虹
  7. ASP.net在线购物商城系统完全解析
  8. 实现真正的「人机」对战:引入三方 AI 引擎
  9. `node2vec` `TSNE` 待解决问题
  10. 幅频特性、相频特性的概念解释
  11. ubuntu20.04上编译android 7.1
  12. java后台通过http请求下载文件
  13. NoSQLBooster for MongoDB延长使用时间
  14. Python显示WiFi密码
  15. 企业微信定时发送图片/文字信息
  16. 三星Galaxy S10可能把加密货币推向数百万名精通高兴技术的手机用户
  17. 【Unity】【Android】问题记录
  18. 数字经济新基建巨头:VMware公司研究
  19. 嵌入式软件工程师面试题目整理(一)
  20. 应用卸载后依然存在的文件目录

热门文章

  1. java.lang.unsatisfiedlinkerror:_我的java程序中的java.lang.UnsatisfiedLinkError :(
  2. 02【IDEA、数据类型转换、运算符、方法】
  3. 使用Cpolar+Cloudreve搭建强大的PHP云盘系统
  4. EasyDL OCR文字识别
  5. jquery——5筛选和查找
  6. php 如何防止盗链,如何在PHP程序中防止盗链
  7. Steam编程区解谜游戏A=B全解(一)
  8. Nvidia显卡硬件编解码能力表 官方链接
  9. 关于java全局变量和局部变量默认值的说明
  10. shell 脚本中获取执行系统命令的输出结果