A星lua实现
A星的大致流程:
每走下一步前,评估所有可走下一步的移动量,然后从中选择最佳步骤;不断重复这个过程,直到到达目的地。
local Astar = {} Astar.__index = Astar function Astar.new() local inst = {} setmetatable(inst, Astar) inst:ctor() return inst end function Astar:ctor() self.m_Tiles = nil ---格子信息, 使用[y][x]获取, 存放格子是否可以走 self.m_TileCanWalkFunc = nil self.m_CachedStepInfos = nil ---用于记录走到某个格子时的一些信息 self.m_OpenList = nil ---下一步可以走的格子都会加入该列表 self.m_ClosedList = nil ---已走过的格子 self.m_ToLoc = { x = 0, y = 0 } ---目标位置 end ---@param func "用于返回格子是否可走, 默认0可走, 1不可走" function Astar:SetTiles(tiles, func) self.m_Tiles = tiles self.m_TileCanWalkFunc = func self.m_CachedStepInfos = nil end function Astar:GetTiles() return self.m_Tiles end function Astar:_CheckOutOfRange(x, y) local row = self.m_Tiles[y] if nil == row then return true end if nil == row[x] then return true end return false end function Astar:_ResetStepInfos() if nil == self.m_CachedStepInfos then return end for k_rowIndex, v_rowStepInfos in pairs(self.m_CachedStepInfos) do for k_colIndex, v_stepInfo in pairs(v_rowStepInfos) do v_stepInfo.g = 0 ---起点到该点的移动量 v_stepInfo.h = 0 ---该点到终点的预估移动量 v_stepInfo.f = 0 ---g+h v_stepInfo.used = false v_stepInfo.prevStep = nil end end end ---@return "{ loc={ x=xxx, y=xxx }, g=xxx, h=xxx, f=xxx, used=xxx }" function Astar:GetStepInfo(x, y) if nil == self.m_CachedStepInfos then self.m_CachedStepInfos = {} end local row = self.m_CachedStepInfos[y] if nil == row then row = {} self.m_CachedStepInfos[y] = row end local stepInfo = row[x] if nil == stepInfo then stepInfo = { loc = { x=x, y=y }, g = 0, h = 0, f = 0, } row[x] = stepInfo else stepInfo.x = x stepInfo.y = y end return stepInfo end ---2个点之间是否有最佳路径 ---@return "没有最佳路径则返回nil" function Astar:FindPath(startX, startY, toX, toY) if startX == toX and startY == toY then return nil end if self:_CheckOutOfRange(startX, startY) then return nil end if self:_CheckOutOfRange(toX, toY) then return nil end self:_ResetStepInfos() self.m_ToLoc.x = toX self.m_ToLoc.y = toY self.m_OpenList = {} self.m_ClosedList = {} local curStepInfo = self:GetStepInfo(startX, startY) curStepInfo.used = true if self:_AddWalkables(curStepInfo) then --到目标点了 return self:_GetWalkPath(startX, startY) end repeat local best = self:_FindBest() if nil == best then break end table.insert(self.m_ClosedList, best) if self:_AddWalkables(best) then --到目标点了 return self:_GetWalkPath(startX, startY) end until #self.m_OpenList <= 0 return nil end ---close列表转换为移动路径 function Astar:_GetWalkPath(startX, startY) local result = {} local tempStepInfo = self.m_ClosedList[#self.m_ClosedList] while nil ~= tempStepInfo do local loc = tempStepInfo.loc if loc.x == startX and loc.y == startY then break end table.insert(result, 1, loc) tempStepInfo = tempStepInfo.prevStep end self.m_OpenList = nil self.m_ClosedList = nil return result end ---检查上下左右格子, 如果可以走, 则放入open列表 ---@return "是否到达目标点" function Astar:_AddWalkables(curStepInfo) local loc = curStepInfo.loc local result = self:_AddWalkable(curStepInfo, loc.x+1, loc.y) or self:_AddWalkable(curStepInfo, loc.x, loc.y-1) or self:_AddWalkable(curStepInfo, loc.x-1, loc.y) or self:_AddWalkable(curStepInfo, loc.x, loc.y+1) return result end ---检查相邻的格子, 如果可以走则放入open列表 ---@return "是否已到达目标点" function Astar:_AddWalkable(prevStepInfo, x, y) local toLoc = self.m_ToLoc if x == toLoc.x and y == toLoc.y then --到达终点 local stepInfo = self:GetStepInfo(x, y) stepInfo.prevStep = prevStepInfo table.insert(self.m_ClosedList, stepInfo) return true end if nil ~= self.m_Tiles[y] then local tile = self.m_Tiles[y][x] if (self.m_TileCanWalkFunc and self.m_TileCanWalkFunc(tile)) or 0 == tile then local stepInfo = self:GetStepInfo(x, y) if not stepInfo.used then stepInfo.g = #self.m_ClosedList + 1 stepInfo.h = math.abs(x - toLoc.x) + math.abs(y - toLoc.y) stepInfo.f = stepInfo.g + stepInfo.h stepInfo.used = true stepInfo.prevStep = prevStepInfo table.insert(self.m_OpenList, stepInfo) else if prevStepInfo.prevStep and stepInfo.g < prevStepInfo.prevStep.g then prevStepInfo.prevStep = stepInfo end end end end return false end ---从open列表中找一个最佳的格子 function Astar:_FindBest() local best = nil local index = 0 for i=1,#self.m_OpenList do local stepInfo = self.m_OpenList[i] if nil == best then best = stepInfo index = i elseif stepInfo.f <= best.f then best = stepInfo index = i end end if index > 0 then table.remove(self.m_OpenList, index) end return best end return Astar
测试代码
local a = Astar.new() local tiles = {} tiles[1] = { 0, 0, 0, 0, 0, 0, 0 } tiles[2] = { 0, 0, 0, 1, 0, 0, 0 } tiles[3] = { 0, 0, 0, 1, 0, 0, 0 } tiles[4] = { 0, 0, 0, 1, 0, 0, 0 } tiles[5] = { 0, 1, 0, 0, 0, 0, 0 } tiles[6] = { 0, 0, 0, 0, 0, 0, 0 } a:SetTiles(tiles) local path = a:FindPath(1, 6, 5, 5) if path and #path > 0 then for i=1,#path do local loc = path[i] print(string.format("row=%s, col=%s", loc.y, loc.x)) end end
【A星原理参考】
A星寻路算法介绍 - 莫水千流 - 博客园 (cnblogs.com)
A星寻路算法 - szmtjs10 - 博客园 (cnblogs.com)