Lua的Astar实现
用Lua实现了一个简单的方块A*寻路。
A*寻路的原理是会计算路径,产生一个估值f值,这个估值是由起点到走过的路径g值和当前的位置到终点的估值h组成的。取这个估值的最小值来进行寻路,所以A*寻路的效率是由估值函数的效率决定的。
代码分析:
创建一个节点,该节点保存位置和估值信息,并保留父节点,相当于整个路径是由链式节点组成。
1234567891011121314151617181920212223242526local node = {}node.__index = nodefunction (pos, h, p)local n = {}n.pos = {x = pos.x, y = pos.y}n.h = hn.parent = pn.g = 0if p ~= nil thenn.g = p.g + 1endn.f = n.g + n.hreturn setmetatable(n, node)endfunction node:get_pos()return self.posendfunction node:get_g()return self.gendfunction node:get_f()return self.fend开始寻路的时候,会把当前节点的四周节点加入开放列表中(寻找路径只会在开放列表中),把障碍物或者已经走过的路径放入关闭列表中(防止重复行走)
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485local astar = {}astar.close = {}astar.open = {}local function estimate_hvalue(srcPos, dstPos)return math.abs(srcPos.x - dstPos.x) + math.abs(srcPos.y - dstPos.y);end--f值排序local function nodeSort(l, r)if l:get_f() == r:get_f() thenreturn l:get_g() > r:get_g()endreturn l:get_f() < r:get_f()endlocal function contain(t, pos)for _, v in ipairs(t) doif v:get_pos().x == pos.x and v:get_pos().y == pos.y thenreturn vendendendfunction astar:fitNode(pos)if self.map[pos.x] ~= nil and self.map[pos.x][pos.y] == 0 thenreturn trueendreturn falseend--把周围4个节点不是墙或者没走过的节点加入开放列表中,开放列表中按f值从少到多排序function astar:addOtherNode(curNode)if curNode == nil then return endlocal d = {{x = 0, y = 1},{x = 0, y = -1},{x = -1, y = 0},{x = 1, y = 0}}table.insert(self.close, curNode)local newPos, h = {x = 0, y = 0}, 0for _, v in ipairs(d) donewPos.x = curNode:get_pos().x + v.xnewPos.y = curNode:get_pos().y + v.yh = estimate_hvalue(newPos, self.dstPos)if contain(self.close, newPos) == nil and self:fitNode(newPos) thentable.insert(self.open, node.new(newPos, h, curNode))endendtable.sort(self.open, nodeSort)end--重复从开放列表中取f值低的节点,直到终点,然后把终点的节点和一连串的父节点组成路径function astar:makePath()self.close = {}self.open = {}local path = {}local h = estimate_hvalue(self.srcPos, self.dstPos)if h == 0 then return endlocal n = node.new(self.srcPos, h, nil)table.insert(self.open, n)local last = nilwhile #self.open ~= 0 and last == nil don = table.remove(self.open, 1)self:addOtherNode(n)last = contain(self.open, self.dstPos)endwhile last ~= nil dotable.insert(path, 1, last)last = last.parentendreturn pathend加载地图等资源进行寻路
12345678910111213141516171819202122232425262728293031323334353637383940local astar = require "astar"local map = {{0, 0, 0, 0, 0, 0},{0, 0, 1, 1, 1, 0},{0, 0, 1, 0, 1, 0},{0, 1, 1, 0, 1, 0},{0, 1, 0, 0, 1, 0},{0, 0, 0, 0, 0, 0}}function printMap()for i=1, #map dolocal s = ""for j=1, #map[i] dos = s..string.format("%d ", map[i][j])endprint(s)endendfunction main()local path = astar:getPath({x = 1, y = 1}, {x = 3, y = 4}, map)if path ~= nil thenprint("before")printMap()local n = nilfor i=1, #path don = path[i]map[n:get_pos().x][n:get_pos().y] = 2endprint("after")printMap()endendmain()
0是空地,1是墙壁,2是行走的路径
A*寻路的优化
- 当地图比较大的时候,使用A*寻路的效率就会变低,这时候不用再一次函数计算中得到结果,可以在游戏帧中分几帧来计算。
- 像魔兽或者帝国中,一大堆兵团进行寻路时候,可以使用队列中间的一个来进行计算,其他兵种靠偏移量来确定位置。