In preparation of the next gaming jam, I started evaluating Moai SDK, a LUA game framework which gained more widespread recognition last year, when Double Fine selected it to use be used for their Kickstarter adventure game. To get a good idea of a few different aspects of the SDK, I decided to try to make a pathfinding demo, specifically implementing the well-known, and equally well-documented A* algorithm. This tutorial assumes that you have a basic idea of Moai concepts – if that is not the case, I recommend their wiki page.
To get started, let’s create a Moai window, and add a layer to it we can use to render to:
-- set up window MOAISim.openWindow("Pathfinder", 640, 360) viewport = MOAIViewport.new() viewport:setScale(320, 180) viewport:setSize(640, 360) -- Create render stack renderTable = {} MOAIRenderMgr.setRenderTable(renderTable) -- Create layer layer = MOAILayer2D.new() layer:setViewport(viewport) table.insert(renderTable, layer)
To keep things simple, we’ll represent the world as a grid of empty and filled squares. Moai provides us with a class for this:
-- set up collision map local map = {} -- Create tile grid grid = MOAIGrid.new() grid:initRectGrid(10, 10, 16, 16)
Next, we load up the tiles to display obstacles, and our path around them:
-- Load sprite map mapTiles = MOAITileDeck2D.new() mapTiles:setTexture("map.png") mapTiles:setSize(4,1) -- Create a prop for tile grid prop = MOAIProp2D.new() prop:setDeck(mapTiles) prop:setGrid(grid) prop:setLoc(-80, -80) layer:insertProp(prop)
In order to keep the MOAIGrid in sync with our collision map, we need a couple of helper functions:
function resetGrid() -- reset collision map for i = 1, 100 do map[i] = false end updateGrid() end function updateGrid() local x = 1 local y = 1 -- set each grid tile according to the collision map for i = 1, #map do local val = 4 if map[i] then val = 2 end grid:setTile(x, y, val) x = x + 1 if x > 10 then x = 1 y = y + 1 end end end
We will allow the user to set tiles as empty / blocked by clicking on them:
MOAIInputMgr.device.mouseLeft:setCallback( function(down) -- only handle mouse down even if not down then return end -- convert mouse position to tile position x,y = layer:wndToWorld(MOAIInputMgr.device.pointer:getLoc()) x = math.floor((x + 80) / 16) + 1 y = math.floor((y + 80) / 16) + 1 if x < 1 or x > 10 or y < 1 or y > 10 then return end -- update collision map local idx = (y - 1) * 10 + x map[idx] = not map[idx] updateGrid() end )
Additionally, the user will be able to reset the map using the ‘R’ key, and to search for a path using ‘Space’:
MOAIInputMgr.device.keyboard:setCallback ( function(key, down) -- only handle key down event if not down then return end if key == 32 then findPath() elseif key == 114 then reset() end end ) function findPath() local finder = Pathfinder:new() finder:setMap(map, 10) local path = finder:findPath({x=1, y=1}, {x=10,y=10}) local i = path while i do grid:setTile(i.x, i.y, 3) i = i.next end end
This concludes the MOAI-specific parts of this tutorial. For a great introduction to A*, and boat-loads of other pathfinding algorithms, please see Amit’s A* Pages. My own implementation of A* in LUA can be found under pathfinder.lua. The demo code and tile map are also available.
Leave a Reply