关于A*算法,推荐一篇博客:
莫水千流-A星寻路算法介绍
博客中介绍了A*算法的原理,通过这个探索小游戏实现自动寻路,结合代码加深对A*算法的理解。
首先定义了一个Point类:
local Point = class('Point',{})
-- r 行 c 列
function Point:ctor(r,c)
self.r = r
self.c = c
end
function Point:getR()
return self.r
end
function Point:getC()
return self.c
end
-- f 权值
function Point:setF(f)
self.f = f
end
function Point:getF()
return self.f
end
-- p 前继
function Point:setP(p)
self.p = p
end
function Point:getP()
return self.p
end
return Point
这个类保存了r行,c列的点的权值F,最重要的是保存它的父节点(在算法里找到该点的上一个点)。
首先,通过点击地图上的某一瓦片来确定目的地:
function MapEditor:openTouch()
local visibleRect = cc.rect(self:getPositionX(),self:getPositionY(),self:getMapWidth(),self:getMapHeight())
local contain = false
local beginPos
local function onTouchBegan( touch,event )
beginPos = touch:getLocation()
if cc.rectContainsPoint(visibleRect,beginPos) then
contain = true
end
return true
end
local function onTouchMoved( touch,event )
end
local function onTouchEnded( touch,event )
local endPos = touch:getLocation()
if math.abs(beginPos.x-endPos.x) < 10 and math.abs(beginPos.y-endPos.y) < 10 then
if contain and cc.rectContainsPoint(visibleRect,endPos) then
local posOnMap = self:convertTouchToNodeSpace(touch)
local R = math.floor(posOnMap.y / self:getRange())
local C = math.floor(posOnMap.x / self:getRange()) + 1
R = self.R - R
self:getParent():autoRoute(R,C)
end
end
end
self.listener = cc.EventListenerTouchOneByOne:create()
self.listener:registerScriptHandler(onTouchBegan,cc.Handler.EVENT_TOUCH_BEGAN)
self.listener:registerScriptHandler(onTouchMoved,cc.Handler.EVENT_TOUCH_MOVED)
self.listener:registerScriptHandler(onTouchEnded,cc.Handler.EVENT_TOUCH_ENDED)
self:getEventDispatcher():addEventListenerWithSceneGraphPriority(self.listener,self)
local function onNodeEvent(event)
if event == 'exit' then
self:getEventDispatcher():removeEventListener(self.listener)
end
end
self:registerScriptHandler(onNodeEvent)
end
function MapEditor:closeTouch()
self:getEventDispatcher():removeEventListener(self.listener)
end
如果点中了地图上的某一瓦片,将通过点击的坐标算出这个瓦片的行列。
-- 自动寻路
function MainScene:autoRoute(R,C)
if self.auto then
print("--正在自动寻路,无法选择新的目标点。--")
return
end
-- 原地
local heroR = self.currentHero:getR()
local heroC = self.currentHero:getC()
if heroR == R and heroC == C then
print("--当前可行动的英雄所在点--")
return
end
-- 障碍 有英雄的 不能作为目标点
local tile = self.mapLayer:getTileByPos(R,C)
if tonumber(tile:getId()) >= 3 and tonumber(tile:getId()) <= 5 then -- 障碍
print("--障碍不能作为目标点--")
return
else
for i=1,#self.heros do
local hero = self.heros[i]
local _r = hero:getR()
local _c = hero:getC()
if _r==R and _c==C then
print("--有英雄的不能作为目标点--")
return
end
end
end
--
self.startPoint = require('app/views/Point').new(heroR,heroC)
self.endPoint = require('app.views.Point').new(R,C)
self.openList = {}
self.closeList = {}
self:beginRoute(self.startPoint)
end
设定障碍和有英雄所在的瓦片不能为目标点,如果正在自动寻路将不能再选择自动寻路的目标点。self.startPoint和self.endPoint是Point类的对象。self.openList保存所有可以考虑的点,self.closeList保存考虑过的点。
function MainScene:beginRoute(point)
self.auto = true
if not listContainPoint(self.closeList,point) then
table.insert(self.closeList,point)
end
local fourPoints = self:getFourDirPoints(point)
local num = #self.openList
for i = 1 , #fourPoints do
local p = fourPoints[i]
if isPointEqual(self.endPoint,p) then
table.insert(self.closeList,p)
self:findPath()
return true
end
local F = self:getF(p:getR(),p:getC())
p:setF(F)
table.insert(self.openList,p)
end
if num - #self.openList == 0 then
if #self.openList > 0 then
local newOne = self.openList[#self.openList]
table.remove(self.openList,#self.openList)
return self:beginRoute(newOne)
else
print("--无法自动寻找路径--")
self.auto = false
return nil
end
end
local ft = {}
for i = 1 , #self.openList do
local p = self.openList[i]
local F = p:getF()
table.insert(ft,F)
end
local min = ft[1]
local minIndex = 1
for i = 2 , #ft do
if ft[i] < min then
min = ft[i]
minIndex = i
end
end
local tmp = self.openList[minIndex]
table.remove(self.openList,minIndex)
return self:beginRoute(tmp)
end
先从self.startPoint开始,它被加到了self.closeList中,然后通过getFourDirPoints找到这个点上左下右四个方向的点:
function MainScene:getFourDirPoints(point)
local r = point:getR()
local c = point:getC()
local up = cc.p(r-1,c)
local left = cc.p(r,c-1)
local down = cc.p(r+1,c)
local right = cc.p(r,c+1)
local t = {up,left,down,right}
local relt = {}
for i = 1 , 4 do
local ccp = t[i]
if isPointEqualXY(self.endPoint,ccp) then
self.endPoint:setP(point)
table.insert(relt,1,self.endPoint)
return relt
end
if self:PointCanOpen(ccp) then
-- 在openList中的已经有父节点
if not listContainObj(self.openList,ccp) then
local p = require('app/views/Point').new(ccp.x,ccp.y)
p:setP(point)
table.insert(relt,p)
end
end
end
return relt
end
上左下右四个点是cc.p()对象,并不是Point对象,如果这个点就是选择的终点,那么指定终点的父节点,并返回:
if isPointEqualXY(self.endPoint,ccp) then
self.endPoint:setP(point)
table.insert(relt,1,self.endPoint)
return relt
end
在终点还没出现在查找范围内时,如果符合考虑的条件,那么用这个点的行列创建一个Point对象,并指定父节点:
if self:PointCanOpen(ccp) then
-- 在openList中的已经有父节点
if not listContainObj(self.openList,ccp) then
local p = require('app/views/Point').new(ccp.x,ccp.y)
p:setP(point)
table.insert(relt,p)
end
end
如果这个点在这次查询中已经在self.openList中(上几次查询被加入),那么不再考虑,因为这个点已经被指定了父节点。
考虑条件:
function MainScene:PointCanOpen(ccp)
local tile = self.mapLayer:getTileByPos(ccp.x,ccp.y)
if not tile then
return