算法名称: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值,红线是寻路过程。
这个是项目中的源码,寻路核心就是用的上面讲的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
项目运行示例图: