A*寻路算法
2016-03-05 17:33
441 查看
实现原理
算法名称:A*算法应用场景:游戏里的自动寻路
原理:
1,从起点开始找它周围可以走的格子,算出可走格子中F值最小的格子,再就以这个格子作为新的中心点,又同样找其周围可以走的格子,以此类推,直至找到目的点。
2,确定最优格子是取F值最小为依据,那么F值怎么算?F = G + H,G:表示从起点移动到指定格子的耗费(一般横竖着的格子耗费为10(or 1),斜着的格子耗费为14(or 1.4),等于其父节点G + 自己的G的和)。H:表示从指定格子移动到终点的预计耗费(一般就是指定点到终点相差的X,Y绝对值的和)。
3,每次找的周围格子都会放入到“开放列表”中,如果下次周围点某几个“开放列表”中已经存在,是否修改这些存在的点属性,是看新计算的G值,如果G值 > 原来的G值,则什么也不做,否则,就以新的G值替换,同时改变其父节点。
4,每个格子都以其中心点作为父节点,这样链接起来,就像一个链表结构,这样直到找到目的点后,就可以通过目的点的父节点这样一级级还原出路径了。
5,每个格子的属性:x坐标,y坐标,G值,H值,F值(F=G + H),父格子。(这种结构类似链表)
寻路步骤
1,从起点A开始, 把它作为待处理的方格存入一个”开启列表”, 开启列表就是一个等待检查方格的列表.2,寻找起点A周围可以到达的方格, 将它们放入”开启列表”, 并设置它们的”父方格”为A.
3,从”开启列表”中删除起点 A, 并将起点 A 加入”关闭列表”, “关闭列表”中存放的都是不需要再次检查的方格.
4, 从 “开启列表” 中选择 F 值最低的方格C.
5,把它从 “开启列表” 中删除, 并放到 “关闭列表” 中.
6,检查它所有相邻并且可以到达 (障碍物和 “关闭列表” 的方格都不考虑) 的方格. 如果这些方格还不在 “开启列表” 里的话, 将它们加入 “开启列表”, 计算这些方格的 G, H 和 F 值各是多少, 并设置它们的 “父方格”为C .
7, 如果某个相邻方格 D 已经在 “开启列表” 里了, 检查如果用新的路径 (就是经过C 的路径) 到达它的话, G值是否会更低一些, 如果新的G值更低, 那就把它的 “父方格” 改为目前选中的方格 C, 然后重新计算它的 F 值和 G 值 (H 值不需要重新计算, 因为对于每个方块, H 值是不变的). 如果新的 G 值比较高, 就说明经过 C 再到达 D 不是一个明智的选择, 因为它需要更远的路, 这时我们什么也不做.
8,然后继续找F值最小的,如此循环下去…
下面是一张寻路的示例图:绿块是起点,蓝块是障碍,红块是终点,天蓝框是中心点,绿框是检测过的点,框左上角F值,框左下角是G值,框右下角是H值,红线是寻路过程。
最终通过父节点还原路径,如下图:(红点表示最终路径)
示例代码(Lua)
这个是项目中的源码,寻路核心就是用的上面讲的A*算法。1,格子点结构定义(Node)
local Point = class("Point") function Point:ctor(pos) self.parentPoint = nil self.F = 0 self.G = 0 self.H = 0 self.x = pos.x self.y = pos.y end --计算F值 function Point:CalcF( point ) self.F = self.G + self.H end return Point
2,A*寻路类
local FindPath = class("FindPath") function FindPath:ctor() --路径列表 self.pathArry = {} end --获取列表中F值最小的点 function FindPath:getMinPoint(pointList) local minPoint = pointList[1] for i = 1, table.getn(pointList) do if minPoint.F > pointList[i].F then minPoint = pointList[i] end end return minPoint end --从开启列表移除点 function FindPath:removePoint(point, pointList) if table.getn(pointList) <= 0 then return end for i=table.getn(pointList), 1, -1 do if pointList[i].x == point.x and pointList[i].y == point.y then table.remove(pointList, i) end end end --列表中是否包含点 function FindPath:existPoint(pointList, point) for i, p in pairs(pointList) do if (p.x == point.x) and (p.y == point.y) then return i end end return false end --获取相邻的点 function FindPath:getSurroundPoints(point) local surroundPoints = {} for i=point.x - 1, point.x + 1 do for j=point.y - 1, point.y + 1 do if i == point.x and j == point.y then --点自己 elseif not self:existPoint(self.closeList, cc.p(i, j)) then if mapLayer:isObstacleByTile(cc.p(i, j)) then --障碍 else local tPos = require("app.control.Point").new(cc.p(i, j)) local dPos = self.endPos if i == point.x or j == point.y then tPos.G = 1 else tPos.G = 1.4 end tPos.H = math.abs(dPos.x-tPos.x) + math.abs(dPos.y-tPos.y) tPos.parentPoint = point table.insert(surroundPoints, tPos) end end end end return surroundPoints --返回point点的集合 end --计算G值 function FindPath:CalcG(point) local G = point.G local parentG = 0 if point.parentPoint then parentG = point.parentPoint.G end return G + parentG end function FindPath:foundPoint(tempStart, point, idx) local lastP = self.openList[idx] local G = self:CalcG(point) if G < lastP.G then point.parentPoint = tempStart point.G = G point:CalcF() self.openList[idx] = point end end function FindPath:notFoundPoint(tempStart, point) point.parentPoint = tempStart point.G = self:CalcG(point) point:CalcF() table.insert(self.openList, point) end --获得角色真实位置(移动一格完成后的位置) function FindPath:getRoleRealPos() local curPos = nil if self.roleTgtPos then curPos = self.roleTgtPos else --角色位置 local x, y = myRole:getPosition() curPos = cc.p(x, y) end return mapLayer:tileCoordForPos(curPos) end --开始寻找路径 function FindPath:startFindPath(endPos) local startPos = self:getRoleRealPos() if endPos.x == startPos.x and endPos.y == startPos.y then printInfo("click same point") return false end --print("startPos:", startPos.x, startPos.y) --print("endPos:", endPos.x, endPos.y) --初始化值 self.openList = {} self.closeList = {} self.pathArry = {} local findPos = nil local isFind = true startPos = require("app.control.Point").new(startPos) self.endPos = require("app.control.Point").new(endPos) table.insert(self.openList, startPos) --寻路逻辑 while table.getn(self.openList) > 0 do --找出F的最小值 local tempStart = self:getMinPoint(self.openList) self:removePoint(tempStart, self.openList) table.insert(self.closeList, tempStart) --找出它相邻的点 local surroundPoints = self:getSurroundPoints(tempStart) for i,point in pairs(surroundPoints) do local idx = self:existPoint(self.openList, point) if idx then --计算G值,如果比原来大,就什么都不做,否则设置他的父节点为当前节点,并更新G和F self:foundPoint(tempStart, point, idx) else --如果他们不再开始列表里面,就加入,并设置父节点,并计算GHF self:notFoundPoint(tempStart, point) end end --如果最后一个存在则返回 local idx = self:existPoint(self.openList, self.endPos) if idx then findPos = self.openList[idx] break end end --添加路径 --printInfo("find path") while findPos do --转换为地图坐标 --printInfo("%d, %d", findPos.x, findPos.y) local tempPos = mapLayer:posForTileGood(findPos) --printInfo("%d, %d", tempPos.x, tempPos.y) table.insert(self.pathArry, tempPos) findPos = findPos.parentPoint end if table.getn(self.pathArry) < 2 then isFind = false end --重置 self.openList = {} self.closeList = {} self.endPos = nil findPos = nil return isFind end --获取移动的偏移 function FindPath:getMoveOffset() local num = table.getn(self.pathArry) if num < 2 then self:removePath() return false end local curPos = self.pathArry[num] local targetPos = self.pathArry[num - 1] table.remove(self.pathArry, num) local sPos = cc.pSub(curPos, targetPos) local angle = math.atan2(sPos.y, sPos.x) * 180 / math.pi + 180 local direct = camera:getDirectionByAngle(angle) myRole:changeAction(actionList.run, direct) return cc.pMul(sPos, -1) end --移除路径 function FindPath:removePath() self.pathArry = {} end --设置角色目标位置 function FindPath:setRolePos(pos) self.roleTgtPos = pos end --获取当前路径 function FindPath:getCurPath() return self.pathArry end return FindPath
项目运行示例图:
相关文章推荐
- github 分支
- SQL Server 2008 : Invoke or BeginInvoke cannot be called on a control until the window handle has ..
- Linux http post 请求 实例
- Android 指定Dex分包的Gradle插件
- ICMP
- gcc/g++ 动态 静态 链接库 so
- [LeetCode]145 二叉树后序遍历
- 获取XIB和Storyboard生成的控件
- 【学习笔记】ES6标准入门
- 根据txt文件中指定的文件名进行文件分类
- mongodb集群搭建
- 「C语言」「汇编」C输出hello world!系统发生了什么?
- apache的三种工作模式
- 打开固定文件的pr_debug
- 现代软件工程第一周作业
- 浅谈OC中排序的方法
- 随机生成30道四则运算-NEW
- HashMap和HashTable的区别
- 怎么获取当前页面的URL
- 附加数据库失败,无法升级数据库,因为它是只读的