一直想把塔防游戏的小兵改成不固定路线,这就涉及到寻路,用lua简单实现了一遍。
一、 构建地图
local map = {
{11,12,13,14,15,16,17,18,19,},
{21,22,23,24,25,26,27,28,29,},
{31,32,33,34,35,36,37,38,39,},
{41,42,43,44,45,46,47,48,49,},
{51,52,53,54,55,56,57,58,59,},
{61,62,63,64,65,66,67,68,69,},
{71,72,73,74,75,76,77,78,79,},
{81,82,83,84,85,86,87,88,89,},
{91,92,93,94,95,96,97,98,99,},
}
就这一个简单粗暴的表吧,假设是一个 9 * 9 的地图。上面的值表示地图中的一个点。
二、 构建节点对象
local function createPoint(x, y, nextPoint, lastPoint)
return {
x = x, --该节点在地图中的x坐标
y = y, --该节点在地图中的y坐标
value = map[x][y], --该节点在地图中的值
np = nextPoint, --该节点的下一个节点(子节点)
lp = lastPoint, --该节点的上一个节点(父节点)
}
end
因为在计算中我们要纪录从哪个节点到哪个节点,所以最好以对象为计算单位,找到目标节点之后顺着父节点往上查找就可以得到完整路线。
三、 定义变量
寻路的要素: 地图、障碍、开始节点、目标节点
local endPoint = {4,4}
local startPoint = {1,2}
local zhangAis = {}
local hadGoPoints = {}
endPoint 和 startPoint 就不说了,这里解释一下zhangAis 和 hadGoPoints (命名比较随意,轻喷)。
zhangAis : 障碍集合, 保存地图中无法行走的点。比如 假设地图上33这个点是障碍,则
zhangAis[33] = true
hadGoPoints : 已经走过的点的集合 如已经走过了 33、34节点则
hadGoPoints[33] = true
hadGoPoints[34] = true
为什么要hadGoPoints?参考A*算法,走过的路就不要走第二次了。
四、 寻找下一步可以走的节点
local function checkCanGoNext(x, y)
local canGos = {}
if x - 1 > 0 and not zhangAis[map[x-1][y]] then
table.insert(canGos, {x - 1, y})
end
if x + 1 < 10 and not zhangAis[map[x + 1][y]] then
table.insert(canGos, {x + 1, y})
end
if y - 1 > 0 and not zhangAis[map[x][y - 1]] then
table.insert(canGos, {x , y - 1})
end
if y + 1 < 10 and not zhangAis[map[x][y + 1]] then
table.insert(canGos, {x, y + 1})
end
local function compareDistance(point_1, point_2)
local distance_1_x = math.abs(point_1[1] - endPoint[1])
local distance_1_y = math.abs(point_1[2] - endPoint[2])
local distance_1 = math.sqrt(distance_1_x ^ 2 + distance_1_y ^ 2)
local distance_2_x = math.abs(point_2[1] - endPoint[1])
local distance_2_y = math.abs(point_2[2] - endPoint[2])
local distance_2 = math.sqrt(distance_2_x ^ 2 + distance_2_y ^ 2)
return distance_1 < distance_2
end
table.sort(canGos, compareDistance)
return canGos
end
原理简单,就是根据当前节点的x y 坐标计算出上下左右的四个点。这里加了一个排序,根据计算出的节点和目标节点距离进行排序,距离越近越排在前面。更近说明走这一步更容易找到目标节点,简单粗暴的预测。
五、 判断是否找到目标节点
local function checkEnd(point)
if point.x == endPoint[1] and point.y == endPoint[2] then
return true
end
end
很简单,比较 x 和 y。 endPoint是全局变量。
六、 找到目标节点后的处理
local function handleEnd(point)
local nowPoint = point
local rd = {}
while nowPoint.lp do
print(nowPoint.value)
table.insert(rd, 1, nowPoint.value)
nowPoint = nowPoint.lp
end
return rd
end
找到目标节点后 根据父节点输出完整路线并返回。此时的point就是目标节点。
七、寻路
local function getNextPoint(point)
hadGoPoints[point.value] = true
local nexts = checkCanGoNext(point.x, point.y)
for _, v in ipairs(nexts) do
local np = createPoint(v[1], v[2], _, point)
if not hadGoPoints[np.value] then
point.np = np
if true == checkEnd(point.np) then
return handleEnd(point.np)
else
local rd = getNextPoint(point.np)
if rd then
return rd
end
end
end
end
end
重头戏,短短十几行代码,整个寻路的灵魂。先不解释吧,看一下结果。
七、测试
随便定义一些障碍和调用寻路
zhangAis[32] = true
zhangAis[23] = true
zhangAis[24] = true
zhangAis[25] = true
zhangAis[26] = true
zhangAis[27] = true
zhangAis[28] = true
zhangAis[29] = true
zhangAis[45] = true
zhangAis[34] = true
zhangAis[42] = true
zhangAis[52] = true
zhangAis[62] = true
zhangAis[72] = true
zhangAis[82] = true
zhangAis[54] = true
zhangAis[73] = true
zhangAis[75] = true
zhangAis[84] = true
zhangAis[66] = true
zhangAis[58] = true
--开始寻路
local start = createPoint(startPoint[1],startPoint[2])
local rd = getNextPoint(start)
printTable(rd)
为了方便观察 我们把上面的障碍 用**表示:
{11,12,13,14,15,16,17,18,19,},
{21,22,**,**,**,**,**,**,**,},
{31,**,33,**,35,36,37,38,39,},
{41,**,43,44,**,46,47,48,49,},
{51,**,53,**,55,56,57,**,59,},
{61,**,63,64,65,**,67,68,69,},
{71,**,**,74,**,76,77,78,79,},
{81,**,83,**,85,86,87,88,89,},
{91,92,93,94,95,96,97,98,99,},
好了目标就是从节点12绕过障碍找到一条路线到节点44。
printTable得到路线是
{22 21 31 41 51 61 71 81 91 92 93 94 95 85 86 76 77 67 57 56 55 65 64 63 53 43 44}
开始是点节12, 第一步节点22,最后节点44,看起来没有错!
看下在游戏中的效果如何:
xc2
依托障碍和建塔构建路线,最后一步堵住去路,小兵寻找到新路线!
八、优点
这个算法的优点是:不用判断是否遍历完,如果程序完成时rd没有值,那就是无路可走。
感兴趣的同学可以自己修改障碍测试一下。
九、缺点
有时候找到了一条路径,但未必是最优路径。因为一旦找到目标节点就返回成功了。可以进行优化直到找到最优路径。另外当地图很大时就比较困难了。
十、解析
关于getNextPoint函数,其实是不断地递归查找。但是并不是乱找,按距离的优先级进行递归,所以一般能够很快找到目标节点。大家复制代码自行测试吧!
感谢浏览!