首页 > 代码库 > A*寻路算法的lua实现

A*寻路算法的lua实现

一、问题概述

游戏中有敌我双方,有四十个方格,当轮到我方武将行动的时候,要先显示出我方武将可以行动的方位,这个就涉及到我方武将的行动力的大小来决定,预先做出路径的预算。这里还要考虑敌方以及地标(例如:炸弹、势头)的阻挡,以及特殊方格对武将行动力的消耗以及敌方的间隔阻挡规则。

当碰到这个问题的时候,问老大选择用什么寻路算法,他推荐的是Dijstra算法,但我看了之后感觉还不是很适合我的需求,第一:我觉得Dijstra算法是有向图的最佳路径选择,节点之间路径长度必须先知晓,但我这里四十个方格如果要两两制定感觉稍微复杂,而且随着武将的移动,地图还是时时变化的,感觉更复杂。第二:Distra算法没有阻挡考虑,当然也可以记录若干路径然后考虑阻挡选择最优路径,我感觉也稍复杂。

然而我的第一反应该需求A*算法挺接近的,然后咨询老大,老大说A*方格之间距离必须要均等不然比较复杂。然后再咨询公司其他稍有经验同事,当然各有个的说法,什么深度、广度遍历,感觉没一个确定的说法。让我选择就纠结了一天,不敢轻易选择,如果稍有考虑不慎,说不定就要做几天白用工,但最后我还是坚持自己的想法用A*来实现。

二、关于A*


A*通用的计算公式F=G+H

G:表示从起点A移动到网格上指定方格的移动耗费(我这里没考虑斜方向移动),也可理解为节点的权重

H:表示从制定方格移动到终点B的预计耗费,这里一般就是计算距离*系数

三、寻路思想

1.从起点A开始,把它添加到"开启列表"

2.寻找A点可到到的周围四个点,这里可到达是指没有阻挡并且已经不再关闭列表中的节点,并把他们添加进开启列表,并设置他们的父节点为A

3.从开启列表中删除A点并添加进关闭列表中,关闭列表就是存放的不需要检测的方格节点

4.检查它所有相邻并且合一到达的方格,如果这些方格还不再开启列表里的话就把他们加入开启列表,计算这些方格的GHF值并设置它的父节点

5.如果某个相邻方格 D 已经在 "开启列表" 里了, 检查如果用新的路径 (就是经过C 的路径) 到达它的话, G值是否会更低一些, 如果新的G值更低, 那就把它的 "父方格" 改为目前选中的方格 C, 然后重新计算它的 F 值和 G 值 (H 值不需要重新计算, 因为对于每个方块, H 值是不变的). 如果新的 G 值比较高, 就说明经过 C 再到达 D 不是一个明智的选择, 因为它需要更远的路, 这时我们什么也不做

6.当发现开启列表中有终点目标方格的时候则说明路径已经找到。

关于详细的图解可以参考其他网友的详解,我这里就不详细写了。

四、找回路径


我们找到最后一个点的时候然后层层往之前找到他的父节点迭代到最后不为空结束

五、数据结构

Point类

[plain] view plaincopyprint?

  1. module(‘Point‘, package.seeall)  

  2. -- require("script/battle/BattleCommon")  

  3. --计算F值  

  4. function CalcF( point )  

  5.     point.F = point.G + point.H  

  6. end  

  7.   

  8. function create( posId )  

  9.     local point = {}  

  10.     point.parentPoint = {}  

  11.     point.step = 1 --用于计算h值  

  12.     local x,y = BattleCommon.convertPosIdToCoord(posId)  

  13.     point.F = 0  

  14.     point.G = 0  

  15.     point.H = 0  

  16.     point.X = y            --point.X范围[1,5]  

  17.     point.Y = x            --point.Y范围[1,8]  

  18.     point.posId = posId  

  19.     point.CalcF = CalcF  

  20.       

  21.     return point  

  22. end  

  23.   

  24. return Point  

地形(Maze)结构

[plain] view plaincopyprint?

  1. --根据一个table创建一个地形  

  2. function create( tb )  

  3.     local maze = {}  

  4.     maze.step = 1 --格子与格子的基本距离,用于计算H值  

  5.     maze.mazeArray = tb  

  6.     maze.openList = TabledArray.create() --开启列表  

  7.     maze.closeList = TabledArray.create() --关闭列表  

  8.     maze.findPath = findPath  

  9.     return maze  

  10. end  


六、主要代码

[plain] view plaincopyprint?

  1. module(‘Maze‘, package.seeall)  

  2. require("script/battle/TabledArray")  

  3. require("script/battle/BattleCommon")  

  4. require ("script/battle/Point")  

  5.   

  6. -- --获取列表中F值最小的点  

  7. function getMinPoint( pointsList )  

  8.     local minPoint = pointsList.tbl[1]  

  9.     for i = 1,pointsList:getSize() do  

  10.         if minPoint.F > pointsList.tbl[i].F then  

  11.             minPoint = pointsList.tbl[i]  

  12.         end  

  13.     end  

  14.     return minPoint  

  15. end  

  16.   

  17. -- --检测是否有阻挡,没有阻挡为true  

  18. function checkBlock( maze,x,y,roleFlag)              --x范围[1,5]  y范围[1,8]  

  19.     if roleFlag == BattleCommon.BATTLE_GROUP_ALLIES then --我方阵营  

  20.         if maze.mazeArray[x][y][1] == 0 or maze.mazeArray[x][y][1] == 1 then  

  21.             return true  --没有阻挡  

  22.         else  

  23.             return false     

  24.         end  

  25.     elseif roleFlag == BattleCommon.BATTLE_GROUP_ENEMY then  

  26.         if maze.mazeArray[x][y][1] == 0 or maze.mazeArray[x][y][1] == 2 then  

  27.             return true  --没有阻挡  

  28.         else  

  29.             return false     

  30.         end  

  31.     end  

  32. end  

  33.   

  34. -- --列表中是否包含x,y的点  

  35. function existsXY( list,x,y )  

  36.     if list:getSize()>0 then  

  37.         for i,point in pairs(list.tbl) do  

  38.             if point.X == x and point.Y == y then  

  39.                 return true  

  40.             end  

  41.         end  

  42.     end  

  43.     return false  

  44. end  

  45.   

  46. --列表中是否包含某个点  

  47. function existsPoint( list,point )  

  48.     for i, p in pairs(list.tbl) do  

  49.         if (p.X == point.X) and (p.Y == point.Y) then  

  50.             return true  

  51.         end  

  52.     end  

  53.     return false  

  54. end  

  55.   

  56.   

  57. -- --检测能达到的点  

  58. function canReach( maze,startPoint,x,y,roleFlag)  

  59.     if (not checkBlock(maze,x,y,roleFlag)) or  existsXY(maze.closeList,x,y) then --关闭列表中包含这个点或者有阻挡  

  60.         return false  

  61.     else  

  62.         if (math.abs(x-startPoint.X)+math.abs(y-startPoint.Y) == 1 ) then  

  63.             return true  

  64.         end  

  65.     end  

  66. end  

  67.   

  68. -- --获取相邻的点  

  69. function getSurroundPoints( maze,point,roleFlag )  

  70.     local surroundPoints = TabledArray.create()  

  71.     for i = point.X - 1 ,point.X + 1 do  

  72.         for j=point.Y - 1,point.Y + 1 do  

  73.             if i>0 and i<6 and j > 0 and j < 9 then  --排除超过表姐  

  74.                 if BattleCommon.distanceFromTo(point.posId,BattleCommon.convertToPositionId(j-1,i-1)) < 2 then  

  75.                     if canReach(maze,point,i,j,roleFlag) then  

  76.                         surroundPoints:append(maze.mazeArray[i][j][2])  

  77.                     end  

  78.                 end  

  79.             end  

  80.         end  

  81.     end  

  82.     return surroundPoints   --返回point点的集合  

  83. end  

  84.   

  85. -- --计算G值  

  86. function CalcG( point )  

  87.     local G = point.G  

  88.     local parentG = 0  

  89.     if point.parentPoint then  

  90.         parentG = point.parentPoint.G  

  91.     end  

  92.     return G + parentG  

  93. end  

  94.   

  95. function foundPoint( tempStart,point )  

  96.     local G = CalcG(point)  

  97.     if G < point.G then  

  98.         point.parentPoint = tempStart  

  99.         point.G = G  

  100.         point:CalcF()  

  101.     end  

  102. end  

  103.   

  104.   

  105. function notFoundPoint( maze,tempStart,point )  

  106.     point.parentPoint = tempStart  

  107.     point.G = CalcG(point)  

  108.     point:CalcF()  

  109.     maze.openList:append(point)  

  110. end  

  111.   

  112. function getPoint( list,data )  

  113.     for i,point in pairs(list.tbl) do  

  114.         if point.posId == data.posId then  

  115.             return point  

  116.         end  

  117.     end  

  118.     return nil  

  119. end  

  120.   

  121. -- --寻找路径(起始路径)  

  122. local function findPath( maze,startPoint,endPoint,roleFlag)  

  123.     maze.openList:append(startPoint)  

  124.     while maze.openList:getSize() ~= 0 do  

  125.         --找出F的最小值  

  126.         local tempStart = getMinPoint(maze.openList)  

  127.         maze.openList:removeById(1)  

  128.         maze.closeList:append(tempStart)  

  129.         --找出它相邻的点  

  130.         local surroundPoints = getSurroundPoints(maze,tempStart,roleFlag)  

  131.         for i,point in pairs(surroundPoints.tbl) do  

  132.             if existsPoint(maze.openList,point) then  

  133.                 --计算G值,如果比原来大,就什么都不做,否则设置他的父节点为当前节点,并更新G和F  

  134.                 foundPoint(tempStart,point)  

  135.             else  

  136.                 --如果他们不再开始列表里面,就加入,并设置父节点,并计算GHF  

  137.                 notFoundPoint(maze,tempStart,point)  

  138.             end  

  139.         end  

  140.         --如果最后一个存在则返回  

  141.         if getPoint(maze.openList,endPoint) then  

  142.             return getPoint(maze.openList,endPoint)  

  143.         end  

  144.     end  

  145.     return getPoint(maze.openList,endPoint)  

  146. end  

  147.   

  148.   

  149. --根据一个table创建一个地形  

  150. function create( tb )  

  151.     local maze = {}  

  152.     maze.step = 1 --格子与格子的基本距离,用于计算H值  

  153.     maze.mazeArray = tb  

  154.     maze.openList = TabledArray.create() --开启列表  

  155.     maze.closeList = TabledArray.create() --关闭列表  

  156.     maze.findPath = findPath  

  157.     return maze  

  158. end  

  159.   

  160.   

  161. return Maze  


调用方法

[plain] view plaincopyprint?

  1. function printPath( presentPoint )  

  2.     local pathArray = TabledArray.create()  

  3.     while presentPoint do  

  4.         pathArray:preppend(presentPoint.posId)  

  5.         presentPoint = presentPoint.parentPoint  

  6.     end  

  7.     local startPoint = pathArray:get(2)  

  8.     local endPoint = pathArray:getLast()  

  9.       

  10.     cclog(startPoint)  

  11.     cclog(endPoint)  

  12.     cclog("从"..startPoint.."到"..endPoint.."的路径是:")  

  13.     for i,p in pairs(pathArray.tbl) do  

  14.         cclog(p)  

  15.     end  

  16. end  


[plain] view plaincopyprint?

  1. local array = battleBoard:createBoxPoints(cRole:getFlag(),40)  

  2. local maze = Maze.create(array)  

  3. local startPoint = Point.create(cRole:getPositionId())  

  4. local endPoint = Point.create(40)  

  5. local presentPoint = maze:findPath(startPoint,endPoint,cRole:getFlag())  

  6. printPath(presentPoint)  


七、运行效果




手机测试效果还可以,貌似在255*255规模的方格规模上采用A*还是不会有太大的效率影响。如果需要交流欢迎联系。


A*寻路算法的lua实现