游戏寻路A*算法
A*算法是一种启发式的BFS,目的就是找到到达目标位置的最短路径。启发式函数如下:
f(x) = g(x) + h(x)
g(x)是对出发点到达当前点距离的估约,h(x)是当前点到终点距离的估约。算法是一个广度优先搜索的过程,但是搜索时的可选集合是一个优先级队列,f(x)越小优先级越高。
算法过程描述
1。用起点初始优先级队列opened;
2。在opened中取最小f(x)的坐标节点,如果该点就是终点,则成功找到返回,否则,向该点的相邻方向寻找可行的下一个移动节点,计算每一个可行节点的f(x)保存,放入opened队列中,继续步骤2;
关于最短路径的求解还应该维护一个映射表,保存移动过程的上一个节点,之后再从终点往前遍历到起点便是整条路径了。
求单源最短路径的迪杰斯特拉算法其实就是当h(x)=0的A*算法。以下是算法的伪码(摘自维基百科)
- function A*(start,goal)
- closedset := the empty set // The set of nodes already evaluated.
- openset := {start} // The set of tentative nodes to be evaluated, initially containing the start node
- came_from := the empty map // The map of navigated nodes.
- g_score[start] := 0 // Cost from start along best known path.
- // Estimated total cost from start to goal through y.
- f_score[start] := g_score[start] + heuristic_cost_estimate(start, goal)
- while openset is not empty
- current := the node in openset having the lowest f_score[] value
- if current = goal
- return reconstruct_path(came_from, goal)
- remove current from openset
- add current to closedset
- for each neighbor in neighbor_nodes(current)
- tentative_g_score := g_score[current] + dist_between(current,neighbor)
- tentative_f_score := tentative_g_score + heuristic_cost_estimate(neighbor, goal)
- if neighbor in closedset and tentative_f_score >= f_score[neighbor]
- continue
- if neighbor not in openset or tentative_f_score < f_score[neighbor]
- came_from[neighbor] := current
- g_score[neighbor] := tentative_g_score
- f_score[neighbor] := tentative_f_score
- if neighbor not in openset
- add neighbor to openset
- return failure
详细设计实现
游戏中自动寻路的场景会被划分成网格,每一个网格可以是阻挡或者通路,在当前网格中可以向周围8个相邻方向移动(左,右,上,下,左上,左下,右上,右下)。运用A*算法从start寻路到end,启发式函数可以这样设计:
1。g(x)设计:初始化起点的g(start) = 0。假设当前位置是cur,下一个位置next若是cur从对角移动过来的(即左上,左下,右上,右下)则g(next) = g(cur) + 14,若是从水平或者垂直方向移动过来的,则g(next) = g(cur) + 10。常数14和10是这么得来的:勾股定理中,假设两直角边都是1,对角线则是1.414,各自放大10倍取整就是10和14;
2。h(x)设计:假设当前节点是(xcur,ycur),终点是(xend,yend),h(x) = abs(xcur – xend) + abs(ycur- yend);
优先级队列可以用最小堆来实现,但是搜索过程中可能出现这样的情况:新添加一个可用节点时,该节点已经在opened队列中了,且新节点有更小的f(x)值,这时候要更新队列中这个节点并重新调整堆。查找新入的节点是否已经在队列中了,单纯用最小堆实现时间复杂度是O(n),可以再加一个hash表,维护坐标点到最小堆中节点的映射,复杂度降为O(1)。代码实现如下:
- #define TI(x, y) ((x)*H+(y))
- struct OpenedNode{
- int _f, _g;
- int _x, _y;
- int _parent;
- OpenedNode(int f = -1, int g = -1, int x = -1, int y = -1, int parent = -1):_f(f),_g(g),_x(x),_y(y),_parent(parent) {}
- };
- template <size_t W, size_t H>
- class OpenedHeap {
- OpenedNode _heap[W*H];
- int _set[W*H];
- int _sz;
- void move_top(int icur) {
- OpenedNode ON = _heap[icur];
- while (icur > 0) {
- int iparent = (icur-1) / 2;
- if (_heap[iparent]._f <= ON._f) break;
- _heap[icur] = _heap[iparent];
- // update
- _set[TI(_heap[icur]._x,_heap[icur]._y)] = icur;
- icur = iparent;
- }
- _heap[icur] = ON;
- _set[TI(_heap[icur]._x, _heap[icur]._y)] = icur;
- }
- void move_bottom(int icur) {
- OpenedNode ON = _heap[icur];
- while (true) {
- int ileft = icur * 2 + 1;
- if (ileft >= _sz) break;
- int iright = icur * 2 + 2;
- int ismaller = iright < _sz && _heap[iright]._f < _heap[ileft]._f? iright:ileft;
- if (_heap[ismaller]._f < ON._f) {
- _heap[icur] = _heap[ismaller];
- _set[TI(_heap[icur]._x, _heap[icur]._y)] = icur;
- icur = ismaller;
- }else break;
- }
- _heap[icur] = ON;
- _set[TI(_heap[icur]._x, _heap[icur]._y)] = icur;
- }
- public:
- OpenedHeap():_sz(0) {
- memset(_set, -1, sizeof(_set));
- }
- void push(const OpenedNode &ON) {
- _heap[_sz] = ON;
- int icur = _sz++;
- move_top(icur);
- // ensure();
- }
- void pop(OpenedNode &ret) {
- ret = _heap[0];
- _set[TI(_heap[0]._x,_heap[0]._y)] = -1;
- assert(_sz>0);
- _heap[0] = _heap[--_sz];
- if (_sz > 0)
- move_bottom(0);
- // ensure();
- }
- bool Get(int x, int y, OpenedNode &ret) {
- if (_set[TI(x,y)] >= 0) {
- ret = _heap[_set[TI(x,y)]];
- return true;
- }
- return false;
- }
- bool make_smaller(int x, int y, int f, int g, int parent) {
- int icur = _set[TI(x,y)];
- assert(_heap[icur]._x == x && _heap[icur]._y == y);
- if (icur < 0) return false;
- if (f == _heap[icur]._f) return true;
- bool top = false;
- if (f < _heap[icur]._f) top = true;
- _heap[icur]._f = f;
- _heap[icur]._g = g;
- _heap[icur]._parent = parent;
- if (top) {
- move_top(icur);
- }else {
- move_bottom(icur);
- }
- //ensure();
- return true;
- }
注意事项:
这也是我实现中犯过的错:
1。移动过程中不能从子节点回到直接父节点,这个是理所当然,所以实现时候在往8个方向移动时要屏蔽父节点。
2。已经在closed中的节点是有可能重新加入opened中的,所以在移动时得到一个新的节点时还要判断是不是比closed中这个节点更cheap(若closed中已经存在这个节点了)。
全部代码在此。可以用在游戏地图动态生成中实现的地图来测试A*寻路,下面是在一个35*100的地图中的自动寻路的效果(字符'-'代表路径)
游戏寻路A*算法的更多相关文章
- 地图四叉树一般用在GIS中,在游戏寻路中2D游戏中一般用2维数组就够了
地图四叉树一般用在GIS中,在游戏寻路中2D游戏中一般用2维数组就够了 四叉树对于区域查询,效率比较高. 原理图
- Unity3D 2D游戏中寻径算法的一些解决思路
需求 unity3d的3d开发环境中,原生自带了Navigation的组件,可以很便捷快速的实现寻路功能.但是在原生的2d中并没有相同的功能. 现在国内很多手机游戏都有自动寻路的功能,或者游戏中存在一 ...
- 原因好消息: PSP游戏自己主动算法设计(两)
这是我们讲的传说中的一项措施A×算法.事实上,类上传之前似小件,下面我们分析一下它去 毕竟,在游戏程序,我们从移动一个点到另一个点.和得到的轨迹的最短距离,类别似这样的算法以及几个.运营效率几乎是相同 ...
- H5版俄罗斯方块(3)---游戏的AI算法
前言: 算是"long long ago"的事了, 某著名互联网公司在我校举行了一次"lengend code"的比赛, 其中有一题就是"智能俄罗斯方 ...
- 游戏碰撞OBB算法(java代码)
业务需求 游戏2D型号有圆形和矩形,推断说白了就是碰撞检测 : 1.圆形跟圆形是否有相交 2.圆形跟矩形是否相交 3.矩形和矩形是否相交 ...
- cocos2d 消除类游戏简单的算法 (一)
1. 游戏视频演示 2.三消游戏我的理解 上面视频中的游戏.我做了2个星期时间,仅仅能算个简单Demo,还有bug.特效也差点儿没有.感觉三消游戏主要靠磨.越磨越精品. 市场上三消游戏已经超级多了.主 ...
- LeetCode | 289. 生命游戏(原地算法/位运算)
记录dalao的位运算骚操作 根据百度百科 ,生命游戏,简称为生命,是英国数学家约翰·何顿·康威在 1970 年发明的细胞自动机. 给定一个包含 m × n 个格子的面板,每一个格子都可以看成是一个细 ...
- 51nod 1459 迷宫游戏 (最短路径—Dijkstra算法)
题目链接 中文题,迪杰斯特拉最短路径算法模板题. #include<stdio.h> #include<string.h> #define INF 0x3f3f3f3f ],v ...
- html5游戏-包围盒检测算法
矩形包围盒算法:检测2个矩形是否重叠,在这样情况下要判断2个矩形是否碰撞只需要比较两个矩形顶点的坐标即可.假设矩形A用(x1,y1)表示左上角,(x2,y2)表示右下角,矩形B用(x3,y3)表示左上 ...
随机推荐
- Java学习_注解
使用注解 注解是放在Java源码的类.方法.字段.参数前的一种特殊"注释". 1 // this is a component: 2 @Resource("hello&q ...
- Sharding jdbc 强制路由策略(HintShardingStrategy)使用记录
背景 随着项目运行时间逐渐增加,数据库中的数据也越来越多,虽然加索引,优化查询,但是数据量太大,还是会影响查询效率,也给数据库增加了负载. 再加上冷数据基本不使用的场景,决定采用分表来处理数据,从而来 ...
- Java连接MySQL数据库——含详细步骤和代码
工具:eclipse.MySQL.MySQL连接驱动:mysql-connector-java-5.1.45.jar 首先要下载Connector/J地址:http://www.mysql.com/d ...
- 有了它(powermock)再也不担心单元测试不达标了
为什么要写单元测试 优点:单元测试可以减少bug率,提升代码的质量.还可以通过单元测试来熟悉业务. 公司硬性要求:有些公司可能还会强制要求,每次新增代码.或者变更代码单测覆盖率要达到多少比例才能申请代 ...
- 庐山真面目之十微服务架构 Net Core 基于 Docker 容器部署 Nginx 集群
庐山真面目之十微服务架构 Net Core 基于 Docker 容器部署 Nginx 集群 一.简介 前面的两篇文章,我们已经介绍了Net Core项目基于Docker容器部署在Linux服 ...
- zigzag压缩算法
前文 Base 128 Varints 编码(压缩算法) 介绍了Base 128 Varints这种对数字传输的编码,了解到了这种编码方式是为了最大程度压缩数字的.但是,在前文里,我们只谈论到了正数的 ...
- Java基础经典案例
案例列表 01减肥计划switch版本 02减肥计划if版本 03逢七跳过 04不死神兔 05百钱白鸡 06数组元素求和 07判断两个数组是否相同 08查找元素在数组中的索引 09数组元素反转 10评 ...
- node获取请求参数的方法get与post请求
1.get请求 get的请求参数是携带在url中的,因此需要引入url模块对请求进行解析,再使用url.parse()方法,get请求多用于页面跳转.表单等请求中,例如page页码.表单账号密码等 先 ...
- Beta冲刺——第十天(补发)
这个作业属于哪个课程 https://edu.cnblogs.com/campus/fzzcxy/2018SE1 这个作业要求在哪里 https://edu.cnblogs.com/campus/fz ...
- 园子的品牌专区上新:NoSQL 数据库佼佼者 Aerospike
品牌专区是园子去年推出的新楼盘,为优秀的科技企业在园子里提供一个地方,展示自己的品牌,分享自己的技术内容. 最近我们和国外领先的 NoSQL 数据库厂商 Aerospike 达成了合作,入驻了园子的品 ...