一直想把塔防游戏的小兵改成不固定路线,这就涉及到寻路,用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函数,其实是不断地递归查找。但是并不是乱找,按距离的优先级进行递归,所以一般能够很快找到目标节点。大家复制代码自行测试吧!

感谢浏览!