A星寻路 - lua实现
A星的大致流程:
每走下一步前,评估所有可走下一步的移动量,然后从中选择移动量最小步骤;不断重复这个过程,直到到达目的地。
图解
下图中,0为可行走,1为障碍,灰色为起点,橘色为目标点

1、(1, 6)可达的格子为(2, 6)和(1, 5)
(2, 6)到起点的移动量g=1, 到终点的移动量h=4, 放入OpenList
(1, 5)到起点的移动量g=1, 到终点的移动量h=4, 放入OpenList
OpenList: (2, 6), (1, 5)
CloseList: 空

2、从OpenList取出(g+h)最小的格子(2, 6),放入CloseList
(2, 6)可达的格子为(3, 6)
(3, 6)到起点的移动量g=2, 到终点的移动量h=3, 放入OpenList
OpenList: (1, 5), (3, 6)
CloseList: (2, 6)

3、从OpenList取出(g+h)最小的格子(1, 5),放入CloseList
(1, 5)可达的格子为(1, 4)
(1, 4)到起点的移动量g=2, 到终点的移动量h=5, 放入OpenList
OpenList: (3, 6), (1, 4)
CloseList: (2, 6), (1, 5)

4、从OpenList取出(g+h)最小的格子(3, 6),放入CloseList
(3, 6)可达的格子为(4, 6), (3, 5)
(4, 6)到起点的移动量g=3, 到终点的移动量h=2, 放入OpenList
(3, 5)到起点的移动量g=3, 到终点的移动量h=2, 放入OpenList
OpenList: (1, 4), (4, 6), (3, 5)
CloseList: (2, 6), (1, 5), (3, 6)

4、从OpenList取出(g+h)最小的格子(4, 6),放入CloseList
(4, 6)可达的格子为(5, 6), (4, 5)
(5, 6)到起点的移动量g=4, 到终点的移动量h=1, 放入OpenList
(4, 5)到起点的移动量g=4, 到终点的移动量h=1, 放入OpenList
OpenList: (1, 4), (3, 5), (5, 6), (4, 5)
CloseList: (2, 6), (1, 5), (3, 6), (4, 6)

5、从OpenList取出(g+h)最小的格子(3, 5),放入CloseList
(3, 5)可达的格子为(4, 5), (3, 4)
(4, 5)格子已走过, 且(3, 5)的g=3, (4, 5)的g=4, 没有更短路径, 忽略
(3, 4)到起点的移动量g=4, 到终点的移动量h=3, 放入OpenList
OpenList: (1, 4), (5, 6), (4, 5), (3, 4)
CloseList: (2, 6), (1, 5), (3, 6), (4, 6), (3, 5)

5、从OpenList取出(g+h)最小的格子(5, 6),放入CloseList
(5, 6)可达的格子为(6, 6), (5, 5)
(6, 6)到起点的移动量g=5, 到终点的移动量h=2, 放入OpenList
(5, 5)为目标点,放入CloseList
OpenList: (1, 4), (4, 5), (3, 4), (6, 6)
CloseList: (2, 6), (1, 5), (3, 6), (4, 6), (3, 5), (5, 6), (5, 5)

从(5, 5)往前回溯步骤得到移动路径:(5, 5) <- (5, 6) <- (4, 6) <- (3, 6) <- (2, 6) <- (1, 6)。注意:不是CloseList
核心代码
---2个点之间是否有最佳路径 ---@return AStar.Point[]? path 没有最佳路径则返回nil function AStar:FindPath(startCellX, startCellY, targetCellX, targetCellY) if startCellX == targetCellX and startCellY == targetCellY then return nil end if self:_CheckOutOfRange(startCellX, startCellY) then return nil end if self:_CheckOutOfRange(targetCellX, targetCellY) then return nil end self:_ResetStepInfos() self.m_TargetCell.x = targetCellX self.m_TargetCell.y = targetCellY self.m_OpenList = {} local tempStepInfo = self:GetStepInfo(startCellX, startCellY) tempStepInfo.used = true if self:_AddWalkables(tempStepInfo) then --到目标点了 return self:_GetWalkPath(startCellX, startCellY) end repeat local best = self:_FindBest() if nil == best then break end if self:_AddWalkables(best) then --到目标点了 return self:_GetWalkPath(startCellX, startCellY) end until #self.m_OpenList <= 0 return nil end ---检查上下左右格子, 如果可以走, 则放入open列表 ---@param fromStepInfo AStar.StepInfo ---@return boolean isReachTarget 是否到达目标点 function AStar:_AddWalkables(fromStepInfo) local cellPos = fromStepInfo.cellPos --print(string.format("AddWalkables: %s, %s", cellPos.x, cellPos.y)) local result = self:_AddWalkable(fromStepInfo, cellPos.x+1, cellPos.y) or self:_AddWalkable(fromStepInfo, cellPos.x, cellPos.y-1) or self:_AddWalkable(fromStepInfo, cellPos.x-1, cellPos.y) or self:_AddWalkable(fromStepInfo, cellPos.x, cellPos.y+1) return result end ---检查相邻的格子, 如果可以走则放入open列表 ---@param fromStepInfo AStar.StepInfo ---@return boolean isReachTarget 是否已到达目标点 function AStar:_AddWalkable(fromStepInfo, toCellX, toCellY) local targetCell = self.m_TargetCell if toCellX == targetCell.x and toCellY == targetCell.y then --到达终点 local toStepInfo = self:GetStepInfo(toCellX, toCellY) toStepInfo.prevStep = fromStepInfo return true end if nil ~= self.m_Cells[toCellY] then local cell = self.m_Cells[toCellY][toCellX] if (self.m_CellGetter and self.m_CellGetter(cell)) or 0 == cell then local toStepInfo = self:GetStepInfo(toCellX, toCellY) if not toStepInfo.used then toStepInfo.g = fromStepInfo.g + 1 toStepInfo.h = math.abs(toCellX - targetCell.x) + math.abs(toCellY - targetCell.y) toStepInfo.f = toStepInfo.g + toStepInfo.h toStepInfo.used = true toStepInfo.prevStep = fromStepInfo table.insert(self.m_OpenList, toStepInfo) --print(string.format("cell:(%s, %s), g:%s, h:%s", toCellX, toCellY, toStepInfo.g, toStepInfo.h)) else local gTemp = fromStepInfo.g + 1 if gTemp < toStepInfo.g then --发现更短路径 toStepInfo.g = gTemp toStepInfo.h = math.abs(toCellX - targetCell.x) + math.abs(toCellY - targetCell.y) toStepInfo.f = toStepInfo.g + toStepInfo.h toStepInfo.prevStep = fromStepInfo end end else --超出范围 end else --超出范围 end return false end ---从open列表中找一个最佳的格子 function AStar:_FindBest() local openList = self.m_OpenList local cnt = #openList if cnt == 0 then return end local best = openList[1] local index = 1 for i=2,cnt do local stepInfo = openList[i] if stepInfo.f < best.f then best = stepInfo index = i end end table.remove(openList, index) return best end
其余代码
---@class AStar ---@field new fun() : AStar local AStar = {} AStar.__index = AStar ---@class AStar.Point ---@field x integer ---@field y integer ---@class AStar.StepInfo ---@field cellPos AStar.Point ---@field g integer 起点到该点的移动量 ---@field h integer 该点到终点的预估移动量 ---@field f integer g+h ---@field used boolean ---@field prevStep AStar.StepInfo function AStar.new() local inst = {} setmetatable(inst, AStar) inst:ctor() return inst end function AStar:ctor() --- 格子信息, 使用[y][x]获取, 存放格子是否可以走 self.m_Cells = nil ---@as table<integer, table<integer, integer>> self.m_CellGetter = nil ---用于记录走到某个格子时的一些信息 self.m_CachedStepInfos = nil ---@as table<integer, table<integer, AStar.StepInfo>> self.m_OpenList = nil ---@as AStar.StepInfo[] 下一步可以走的格子都会加入该列表 self.m_TargetCell = { x = 0, y = 0 } ---目标位置 end ---@param getter fun(cellX, cellY) : integer 0_可走, 1_不可走, nil_超出范围 function AStar:SetTiles(tiles, getter) self.m_Cells = tiles self.m_CellGetter = getter self.m_CachedStepInfos = nil end function AStar:_CheckOutOfRange(cellX, cellY) local row = self.m_Cells[cellY] if nil == row then return true end if nil == row[cellX] then return true end return false end function AStar:_ResetStepInfos() if nil == self.m_CachedStepInfos then return end for k1_RowIndex, v1_RowStepInfos in pairs(self.m_CachedStepInfos) do for k2_ColIndex, v2_OneStepInfo in pairs(v1_RowStepInfos) do v2_OneStepInfo.g = 0 v2_OneStepInfo.h = 0 v2_OneStepInfo.f = 0 v2_OneStepInfo.used = false v2_OneStepInfo.prevStep = nil end end end ---@return AStar.StepInfo function AStar:GetStepInfo(cellX, cellY) if nil == self.m_CachedStepInfos then self.m_CachedStepInfos = {} end local rowStepInfos = self.m_CachedStepInfos[cellY] if nil == rowStepInfos then rowStepInfos = {} self.m_CachedStepInfos[cellY] = rowStepInfos end local oneStepInfo = rowStepInfos[cellX] if nil == oneStepInfo then oneStepInfo = { cellPos = { x=cellX, y=cellY }, g = 0, h = 0, f = 0, } rowStepInfos[cellX] = oneStepInfo end return oneStepInfo end ---close列表转换为移动路径 function AStar:_GetWalkPath(startCellX, startCellY) local result = {} local tempStepInfo = self:GetStepInfo(self.m_TargetCell.x, self.m_TargetCell.y) while nil ~= tempStepInfo do local cellPos = tempStepInfo.cellPos if cellPos.x == startCellX and cellPos.y == startCellY then break end table.insert(result, 1, cellPos) tempStepInfo = tempStepInfo.prevStep end self.m_OpenList = nil return result 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)

浙公网安备 33010602011771号