探索小游戏(三):A*算法实现自动寻路

本文介绍了如何在小游戏开发中应用A*算法实现自动寻路功能。通过创建Point类,设置起点和终点,利用A*算法进行路径规划。在寻路过程中,不断更新openList和closeList,直到找到最短路径。最终通过添加箭头指示,实现了游戏内角色的自动行走。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

关于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 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值