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

关于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 false
    end 
    if tonumber(tile:getId()) >= 3 and tonumber(tile:getId()) <= 5 then  -- 障碍
        return false
    elseif tonumber(tile:getId()) == 2 then -- 陷阱
        return false
    elseif listContainObj(self.closeList,ccp) then -- 在close表中
        return false
    else  -- 有英雄
        for i=1,#self.heros do
            local hero = self.heros[i]
            local _r = hero:getR()
            local _c = hero:getC()
            if _r==ccp.x and _c==ccp.y then
                return false
            end
        end
    end
    return true
end

可以考虑的点已经创建了Point对象加入到了relt表中并返回,遍历返回的可以考虑的点:

    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

之前查询中如果找到了终点,那么这里将终点插入到self.closeList中,结束查询,因为已经找到了路径。如果没有,则计算它们的权值F,并插入到self.openList中。

function MainScene:getG(r,c)
    -- 从起点到该点需要几步
    return math.abs(self.startPoint:getC() - c) +  math.abs(self.startPoint:getR() - r)
end

function MainScene:getH(r,c)
    -- 该点到终点的估算值
    return math.abs(self.endPoint:getR() - r) + math.abs(self.endPoint:getC() - c)
end

function MainScene:getF(r,c)
    -- F = G + H
    return self:getG(r,c) + self:getH(r,c)
end

变量num记录了这次查询可考虑点之前的self.openList中的元素个数,如果这次查询没有找到符合条件的点(getFourDirPoints方法返回的表中没有元素),num - #self.openList == 0,那么说明按照目前查询的路径,已经没有路可走,那么就废弃这条路线,从self.openList找一个新的点重新查询(最后一个,最近被加入),这个点也将从self.openList中移除。如果self.openList中没有元素,说明没有路径可以到达选择的目标点。

self.openList中有新加入的可考虑点,那么找出其中权值F最小的点,从这个点开始新一轮的查询:

    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)

通过函数的迭代,不断查询,直到终点出现在可考虑范围,getFourDirPoints方法中:

        if isPointEqualXY(self.endPoint,ccp) then
            self.endPoint:setP(point)
            table.insert(relt,1,self.endPoint)
            return relt
        end

对返回的relt表的遍历发现终点,就已经找到了路径:

        if isPointEqual(self.endPoint,p) then
            table.insert(self.closeList,p)
            self:findPath()
            return true
        end

用self.pathList保存路径点:

function MainScene:findPath()
    self.pathList = {}
    table.insert(self.pathList,self.closeList[#self.closeList])
    self:getPointParent(self.endPoint)
end

先将self.closeList的最后一个元素也就是终点插入self.pathList中,然后就是不断查找父节点的过程,终点在之前的查找可考虑点时被指定了父节点,每个考虑的都有唯一的父节点,那么不断反向查询,直到发现一个点的父节点是起点时,就找到了一条完整的路径:

function MainScene:getPointParent(point)
    local parent = point:getP()
    if parent then
        table.insert(self.pathList,1,parent)
        if isPointEqual(self.startPoint,parent) then
            self:getActionDirection()
            return 
        else
            self:getPointParent(parent)
        end
    else
        print('--有一个Point对象没有父节点--')
    end
end

根据之前手动操作的玩法,得到从这条路径行走每一步的前进的方向:

function MainScene:getActionDirection()
    local dir = {}
    for i = 1 , #self.pathList do
        local direction 
        if i ~= #self.pathList then
            local p1 = self.pathList[i]
            local p2 = self.pathList[i+1]
            if p1:getR() == p2:getR() then  -- 左右
                if p1:getC() < p2:getC() then
                    direction = 'right'           
                else
                    direction = 'left'
                end
            else  -- 上下
                if p1:getR() > p2:getR() then
                    direction = 'up'
                else
                    direction = 'down'              
                end
            end
        end
        table.insert(dir,direction)
    end
    self:placeArrow(dir)
end

为了方便直观,通过placeArrow方法添加方向指引的箭头,体力足够到达的瓦片放置绿色箭头,不够到达的放置红色箭头:

function MainScene:placeArrow(dir)
    self.rbas = {}
    for i = 1 , #dir do
        local direction = dir[i]
        local point = self.pathList[i+1]
        local img 
        if self.currentHero:getMyTl() >= 5 * i then
            if i == #dir then
                img = 'reach.png'
            else
                img = 'dir.png'
            end
        else
            if i == #dir then
                img = 'reach_n.png'
            else
                img = 'dir_n.png'
            end
        end
        local sp = cc.Sprite:create(img)
        if i ~= #dir then
            if direction == 'right' then
                sp:setFlippedX(true)
            elseif direction == 'up' then
                sp:setRotation(90)
            elseif direction == 'down' then
                sp:setRotation(-90)
            end
        end
        table.insert(self.rbas,sp)
        local tile = self.mapLayer:getTileByPos(point:getR(),point:getC())
        self.mapLayer:addChild(sp)
        sp:setPosition(tile:getPositionX(),tile:getPositionY()+tile:getNodeSize().height/2)
    end
    self:playAction(dir,1)
end

最后就是根据路径进行英雄的自动行走,在自动寻路前进时不显示手动操作的箭头:

function MainScene:playAction(dir,start)
    if dir[start] then
        local isContinue = true
        local seq = cc.Sequence:create(cc.CallFunc:create(function()
            isContinue = self:heroAction(self.currentHero,dir[start],true)
            if start == #dir then
                print("--到达指定地点--")
            end
            start = start + 1
        end),cc.DelayTime:create(1),cc.CallFunc:create(function() 
            if isContinue then
                local sp = self.rbas[1]
                sp:removeFromParent()
                table.remove(self.rbas,1)
                self:playAction(dir,start)
            else
                self.auto = false
                self:showArrow(self.currentHero)
                self:stopAction(self.routeAction)
                for i = 1 , #self.rbas do
                    local sp = self.rbas[#self.rbas]
                    sp:removeFromParent()
                    table.remove(self.rbas,#self.rbas)
                end
            end
        end))
        self.routeAction = self:runAction(seq)
    else
        self.auto = false
        self:showArrow(self.currentHero)
        self:stopAction(self.routeAction)
    end
end

self:runAction(seq),顺序执行动作,self:heroAction(self.currentHero,dir[start],true)返回的isContinue为true就还有体力前进,每走一步删除一个指引箭头。isContinue为false时,没有体力前进,那么显示手动操作的箭头,并删除指引箭头。

运行效果:

MainScene完整代码:

local MainScene = class("MainScene", cc.load("mvc").ViewBase)

function MainScene:ctor()
    self.heros = {}
    self.auto = false
    self:addMap()
    self:addHero()
end

function MainScene:addMap()
    local mapDataStr = cc.FileUtils:getInstance():getStringFromFile('res/MapData.json')
    local mapData = json.decode(mapDataStr)

    local mapStr = mapData['map']
    local mapInfo = self.split(mapStr,'#')

    self.mapLayer = require('app/views/MapEditor').new(mapInfo)
    self:addChild(self.mapLayer)
    self.range = self.mapLayer:getRange()

    local winSize = cc.Director:getInstance():getWinSize()
    self.mapLayer:setPosition(winSize.width/2-self.mapLayer:getMapWidth()/2,winSize.height/2-self.mapLayer:getMapHeight()/2)
    self.mapLayer:openTouch()
end

function MainScene:addHero()
    local heroDataStr = cc.FileUtils:getInstance():getStringFromFile('HeroData.json')
    local heroData = json.decode(heroDataStr)
    local data = heroData['data']

    ccs.ArmatureDataManager:getInstance():addArmatureFileInfo('qishi.ExportJson')
    for i=1,#data do
        local hero = require('app/views/Hero').new(data[i]['id'])
        hero:setR(data[i]['r'])
        hero:setC(data[i]['c'])
        -- 设定初始体力为100
        hero:setMyTl(100)
        self.mapLayer:addChild(hero,50)
        local tile = self.mapLayer:getTileByPos(data[i]['r'],data[i]['c'])
        hero:setPosition(tile:getPosition())
        table.insert(self.heros,hero)
        local isShow = false
        if i == 1 then
            isShow = true
            self.currentHero = hero
        end
        self:addArrow(hero,isShow)
    end
end

function MainScene:addArrow(hero,isShow)
    local arrow = require('app/views/Arrow').new(hero:getNodeSize().width)
    hero:addChild(arrow)
    arrow:setPositionY(hero:getNodeSize().height/2)
    arrow:setName('Arrow')
    -- 检查箭头该不该显示
    if isShow then
        self:showArrow(hero)
    else
        arrow:showSomeone('all',false)
    end
    arrow:setBtnFunc(function(sender,_type)
        if _type == ccui.TouchEventType.ended then
            self:heroAction(hero,sender:getName())                               
        end
    end)
end

function MainScene:showArrow(hero)
    local arr = hero:getChildByName('Arrow')

    local id = hero:getMyId()
    local heroR = hero:getR()
    local heroC = hero:getC()

    local mapC = self.mapLayer:getC()
    local mapR = self.mapLayer:getR()

    if heroC==1 then
        arr:showSomeone('left',false)
    elseif heroC==mapC then
        arr:showSomeone('right',false)
    end

    if heroC-1>0 then
        arr:showSomeone('left',self:checkArrowShow(heroR,heroC-1,id))
    end

    if heroC+1<=mapC then
        arr:showSomeone('right',self:checkArrowShow(heroR,heroC+1,id))
    end

    if heroR==1 then
        arr:showSomeone('up',false)
    elseif heroR==mapR then
        arr:showSomeone('down',false)
    end

    if heroR-1>0 then
        arr:showSomeone('up',self:checkArrowShow(heroR-1,heroC,id))
    end

    if heroR+1<=mapR then
        arr:showSomeone('down',self:checkArrowShow(heroR+1,heroC,id))
    end
end

function MainScene:checkArrowShow(r,c,id)
    local tile  = self.mapLayer:getTileByPos(r,c)
    local have = false
    if tonumber(tile:getId()) >= 3 and tonumber(tile:getId()) <= 5 then  -- 障碍不可站 
        return false
    else
        for i=1,#self.heros do
            local hero = self.heros[i]
            if hero:getMyId() ~= id then
                local _r = hero:getR()
                local _c = hero:getC()
                if _r == r and _c == c then
                    have = true
                    break
                end
            end
        end
        if have then
            return false
        else
            return true
        end
    end
end

function MainScene:heroAction(hero,arr_dir,isAuto)
    local heroR = hero:getR()
    local heroC = hero:getC()
    local arrow = hero:getChildByName('Arrow')
    local heroTl = hero:getMyTl()
    local heroX,heroY = hero:getPosition()

    if heroTl <= 0 then
        print("--体力不足--")
        return false
    end

    if arr_dir == 'left' then
        heroX = heroX - self.range
        heroC = heroC - 1 
    elseif arr_dir == 'up' then
        heroY = heroY + self.range
        heroR = heroR - 1
    elseif arr_dir == 'right' then
        heroX = heroX + self.range
        heroC = heroC + 1
    else  -- down
        heroY = heroY - self.range
        heroR = heroR + 1 
    end

    if heroTl >= 5 then
        if arr_dir == 'left' or arr_dir == 'right' then
            hero:setDir(arr_dir)
        end

        local move = cc.MoveTo:create(0.7,cc.p(heroX,heroY))
        local function playA()
            arrow:showSomeone('all',false)
            hero:playAnimation('move')
        end
        local spawn = cc.Spawn:create(move,cc.CallFunc:create(playA))

        local function playB()
            hero:playAnimation('stand')

            hero:setR(heroR)
            hero:setC(heroC)

            if not isAuto then
                self:showArrow(hero)
            end

            hero:setMyTl(heroTl - 5)
        end
        hero:runAction(cc.Sequence:create(spawn,cc.CallFunc:create(playB)))
        return true
    end
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

local function isPointEqualXY(point,ccp)
    if point:getR() == ccp.x and point:getC() == ccp.y then
        return true
    end
    return false
end

local function isPointEqual(point1,point2)
    if point1:getR() == point2:getR() and point1:getC() == point2:getC() then
        return true
    end
    return false
end

local function listContainPoint(list,point)
    if #list > 0 then
        for i = 1 , #list do
            local tmp = list[i]
            if tmp:getR() == point:getR() and tmp:getC() == point:getC() then
                return true
            end
        end
    end
    return false
end

local function listContainObj(list,obj)
    if #list > 0 then
        for i = 1 , #list do
            local tmp = list[i]
            if tmp:getR() == obj.x and tmp:getC() == obj.y then
                return true
            end
        end
    end
    return false
end

function MainScene:PointCanOpen(ccp)
    local tile = self.mapLayer:getTileByPos(ccp.x,ccp.y)
    if not tile then
        return false
    end 
    if tonumber(tile:getId()) >= 3 and tonumber(tile:getId()) <= 5 then  -- 障碍
        return false
    elseif tonumber(tile:getId()) == 2 then -- 陷阱
        return false
    elseif listContainObj(self.closeList,ccp) then -- 在close表中
        return false
    else  -- 有英雄
        for i=1,#self.heros do
            local hero = self.heros[i]
            local _r = hero:getR()
            local _c = hero:getC()
            if _r==ccp.x and _c==ccp.y then
                return false
            end
        end
    end
    return true
end

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

function MainScene:getG(r,c)
    -- 从起点到该点需要几步
    return math.abs(self.startPoint:getC() - c) +  math.abs(self.startPoint:getR() - r)
end

function MainScene:getH(r,c)
    -- 该点到终点的估算值
    return math.abs(self.endPoint:getR() - r) + math.abs(self.endPoint:getC() - c)
end

function MainScene:getF(r,c)
    -- F = G + H
    return self:getG(r,c) + self:getH(r,c)
end

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

function MainScene:findPath()
    self.pathList = {}
    table.insert(self.pathList,self.closeList[#self.closeList])
    self:getPointParent(self.endPoint)
end

function MainScene:getPointParent(point)
    local parent = point:getP()
    if parent then
        table.insert(self.pathList,1,parent)
        if isPointEqual(self.startPoint,parent) then
            self:getActionDirection()
            return 
        else
            self:getPointParent(parent)
        end
    else
        print('--有一个Point对象没有父节点--')
    end
end

function MainScene:getActionDirection()
    local dir = {}
    for i = 1 , #self.pathList do
        local direction 
        if i ~= #self.pathList then
            local p1 = self.pathList[i]
            local p2 = self.pathList[i+1]
            if p1:getR() == p2:getR() then  -- 左右
                if p1:getC() < p2:getC() then
                    direction = 'right'           
                else
                    direction = 'left'
                end
            else  -- 上下
                if p1:getR() > p2:getR() then
                    direction = 'up'
                else
                    direction = 'down'              
                end
            end
        end
        table.insert(dir,direction)
    end
    self:placeArrow(dir)
end

function MainScene:placeArrow(dir)
    self.rbas = {}
    for i = 1 , #dir do
        local direction = dir[i]
        local point = self.pathList[i+1]
        local img 
        if self.currentHero:getMyTl() >= 5 * i then
            if i == #dir then
                img = 'reach.png'
            else
                img = 'dir.png'
            end
        else
            if i == #dir then
                img = 'reach_n.png'
            else
                img = 'dir_n.png'
            end
        end
        local sp = cc.Sprite:create(img)
        if i ~= #dir then
            if direction == 'right' then
                sp:setFlippedX(true)
            elseif direction == 'up' then
                sp:setRotation(90)
            elseif direction == 'down' then
                sp:setRotation(-90)
            end
        end
        table.insert(self.rbas,sp)
        local tile = self.mapLayer:getTileByPos(point:getR(),point:getC())
        self.mapLayer:addChild(sp)
        sp:setPosition(tile:getPositionX(),tile:getPositionY()+tile:getNodeSize().height/2)
    end
    self:playAction(dir,1)
end

function MainScene:playAction(dir,start)
    if dir[start] then
        local isContinue = true
        local seq = cc.Sequence:create(cc.CallFunc:create(function()
            isContinue = self:heroAction(self.currentHero,dir[start],true)
            if start == #dir then
                print("--到达指定地点--")
            end
            start = start + 1
        end),cc.DelayTime:create(1),cc.CallFunc:create(function() 
            if isContinue then
                local sp = self.rbas[1]
                sp:removeFromParent()
                table.remove(self.rbas,1)
                self:playAction(dir,start)
            else
                self.auto = false
                self:showArrow(self.currentHero)
                self:stopAction(self.routeAction)
                for i = 1 , #self.rbas do
                    local sp = self.rbas[#self.rbas]
                    sp:removeFromParent()
                    table.remove(self.rbas,#self.rbas)
                end
            end
        end))
        self.routeAction = self:runAction(seq)
    else
        self.auto = false
        self:showArrow(self.currentHero)
        self:stopAction(self.routeAction)
    end
end

function MainScene.split(str,reps)
    local resultStrList = {}
    string.gsub(str,'[^'..reps..']+',function (w)
        table.insert(resultStrList,w)
    end)
    return resultStrList
end

return MainScene

MapEditor完整代码:

local MapEditor = class('MapEditor',function () 
    return cc.Layer:create() 
end)

function MapEditor:ctor(mapInfo)
    -- 行数
    self.R = #mapInfo

    local tileDataStr = cc.FileUtils:getInstance():getStringFromFile('res/TileData.json')
    local tileData = json.decode(tileDataStr)

    for i=1,#mapInfo do
        local c = self.split(mapInfo[i],',')
        for j=1,#c do
            local img = tileData[c[j]]
            local tile = require('app/views/TileNode').new(c[j],img)

            if i==1 and j==1 then
                -- 列数
                self.C = #c
                self:setMapWidth(tile:getNodeSize().width * #c)
                self:setMapHeight(tile:getNodeSize().height * #mapInfo)
                self:setRange(tile:getNodeSize().width)
            end

            self:addChild(tile)

            local _x = (j*2-1) * (tile:getNodeSize().width/2)
            local _y = (#mapInfo-i) * (tile:getNodeSize().height)

            tile:setPosition(_x,_y)
            tile:setMyTag(i,j)
            tile:setName('Tile')
        end
    end
end

function MapEditor.split(str,reps)
    local resultStrList = {}
    string.gsub(str,'[^'..reps..']+',function (w)
        table.insert(resultStrList,w)
    end)
    return resultStrList
end

function MapEditor:setMapWidth(_w)
    self.mapWidth = _w
end

function MapEditor:setMapHeight(_h)
    self.mapHeight = _h 
end

function MapEditor:getMapWidth()
    return self.mapWidth
end

function MapEditor:getMapHeight()
    return self.mapHeight
end

function MapEditor:getR()
    return self.R
end

function MapEditor:getC()
    return self.C
end

function MapEditor:setRange(_range)
    self.range = _range
end

function MapEditor:getRange()
    return self.range
end

function MapEditor:getTileByPos(_x,_y)
    local chs = self:getChildren()
    for i=1,#chs do
        local t = chs[i]
        if t:getName()=='Tile' then
            local r,c = t:getMyTag()
            if r==_x and c==_y then
                return t
            end
        end
    end
end

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

return MapEditor

Arrow、Hero、TileNode没有更改。

你可能感兴趣的:(游戏)