Roguelike, step 2: maps and movement

Previous tutorial – Download files – Next tutorial

Right-clicks create class – Errors tracked down and killed – Tables two ways – Tiles lovingly ripped off – Moving worlds – How walls become walls and how heroes run into them

Right-clicks create class

So far we’ve created a main.lua file that puts some game elements on the screen.   The elements are stored in a fonts folder, an images folder and a classes folder.  Let’s reorganize some more.  Right-click on the classes folder and add a new file called Constants.lua.  We’ll move all the constant macros from main.lua over to the new Constants.lua.  Right-click again on the classes folder and add a new file called Mainscreen.lua.  Here, let’s create a MainScreen class that does everything we previously did directly in main.lua.  Add the following code to the top of Mainscreen.lua.

MainScreen = Core.class(Sprite)

function MainScreen:init()


Now copy everything except the application settings and the EventListeners from main.lua into MainScreen:init().  The foreground image and the compass buttons will no longer be added directly to the stage. Instead, we create a local instance of our MainScreen class and add the instance to the stage.

local main =

That being the case, we need to change all references of ‘stage’ to ‘self’ in MainScreen:init().  In addition, we define the buttons, not as local variables, but also as self. variables.

self.north = {}
self.south = {}
self.west = {}
self.east = {}

We can now refer to them directly as main. variables.  So the rest of main.lua looks like this:

main.north:addEventListener("click", function() print("you clicked north") end) 
main.south:addEventListener("click", function() print("you clicked south") end) 
main.west:addEventListener("click", function() print("you clicked west") end) 
main.east:addEventListener("click", function() print("you clicked east") end)

What do we have at the end of all this rearranging?  We have two more classes in our classes folder:  Mainscreen.lua and Constants.lua.  And we have a main.lua that does exactly what it did before all the rearranging.  Ta dah!  Actually, this may not seem like a benefit at all.  That’s true in a small program like this, but as our game grows, you’ll appreciate keeping the world building code separate from the monster AI code, separate from the main screen graphics, etc..

Errors tracked down and killed 

With different classes and variables on different tabs of your project, you may run into problems where your constants aren’t loaded before they’re needed.

In this case, right-click on one of the file that depends on Constants.lua and choose Code Dependencies.  You can make sure that Constants.lua and other files are loaded first by checking the box next to the file name.


In general, the Gideros environment makes it really easy to type in some code, hit play, troubleshoot small typos and repeat.  Usually it’s pretty clear exactly where errors occur.  My biggest mistakes are using ‘.’ when I should have used a ‘:’ and vice versa.  Class variables are referred to with a ‘.’ while class functions are called with ‘:’.  It’s also easy to forget an ‘end’ or ‘)’ to finish a section of code that is expecting one.

Tables two ways:  arrays and TileMaps

TileMap is a Gideros class that allows us to create maps of certain sizes made out of tiles of certain sizes.  This is exactly what we want so add a TileMaps.lua file to the classes folder.  In it, we’ll create a WorldMap class that has two tables in it:

WorldMap = Core.class(Sprite)
function WorldMap:init()
  self.mapArrays = {}
  self.mapLayers = {}

Our WorldMap will be two layers of TileMaps.  One is for the background tiles and one for the monster tiles (including the hero).  self.mapArrays will be the two matrices of numbers that our program will reference to determine what’s where whilte self.mapLayers will be the actual TileMaps.

So to quickly define the mapArrays:

self.mapArrays[LAYER_TERRAIN] = self:generateTerrain()
self.mapArrays[LAYER_MONSTERS] = self:placeMonsters()

For now, both generateTerrain and placeMonsters simply return arrays that are 20×20 in size.  Of course, I define new constants in Constants.lua so I can change my mind later.


Importantly, each array only looks like a 20×20 matrix array.  There are no arrays in Lua.  In fact, Lua is all tables, all the time.  So self.mapArrays are actually 1×400 lists; more specifically, they’re  tables with 400 integers.  To convert between map coordinates and the array index, we’ll use this formula for the index:  x + (y – 1) * LAYER_COLUMNS.

With our self.mapArrays defining what our maps will actually look like, we now need to actually create the maps.  To define the mapLayers, we’re going to need a function that uses Gideros’ TileMap:setTile to create a TileMap based on values in the mapArrays and some tileset images.   returntileMap  will do just that.

function WorldMap:returnTileMap(array, tileset)
  local texture =, true)
  local tileMap =, LAYER_ROWS, texture, 
                              TILE_WIDTH, TILE_HEIGHT)
  for y = 1, LAYER_ROWS do
    for x = 1, LAYER_COLUMNS do
      local tX = array[x + (y - 1) * LAYER_COLUMNS]
      local tY = 1
      tileMap:setTile(x, y, tX, tY)
  return tileMap

And here are the tileset images we can use.



More on these shortly.  For now, notice that the key assignment is

tileMap:setTile(x, y, tX, tY)

where the big tileMap is created from tiles taken from the little tileset images.  tX and tY are the coordinates in the tileset images.  tX is from the self.mapArrays (either 1, 2 or 3) while tY is 1 because our tileset images are only one row.  If we wanted to use a huge sprite sheet to create our game with, we would need to redefine tY.

With the images saved and added to our images folder, we can now call returnTileMap with an array and a image and actually create each self.mapLayer.  Here’s the rest of WorldMap:init().

local group =
local layer = self:returnTileMap(self.mapArrays[LAYER_TERRAIN], 
table.insert(self.mapLayers, layer)
layer = self:returnTileMap(self.mapArrays[LAYER_MONSTERS], 
table.insert(self.mapLayers, layer)

As each layer is created by our returnTileMap function, it is added to our table variable self.mapLayers and to a Sprite named group.  At the end of WorldMap:init(), the parent Sprite group is added to the stage.

Tiles lovingly ripped-off

So back in Rogue’s day, graphics weren’t even an option.  Nowadays we have so many graphics and arts options.   2d?  3d?  Pseudo-3d?  Top-down or bird’s eye?  Retro or fully animated?  For this tutorial, the background images are based off of this artist’s lovely work and the monsters are from the amazing  Additional layers of TileMaps made from additional tilesets can be included in self.mapArrays and self.mapLayers as we keep developing our Roguelike.  Weather tiles anyone?

Tile widths and heights are important design decisions for your game.  Too small and they’re hard to click on.  Too big and it’s hard to have much of a world on the screen at any one time.  I chose a tile size of 108px because I wanted to fix an odd number of tiles (5 above, 5 below and 1 right where the hero is standing) in my screen height of 1200.  With this odd size , I’m able to fit a 11×11 grid of tiles on the screen at a time (1188px leaves 12px to spare).  Definitely spend some time thinking about the tile size in relation to the screen size before moving forward. Not too much time though.  You can always change your mind later by redrawing, or at least, rescaling each tileset in a paint program and changing the constants.


Our TileMaps will appear onscreen with just two additional lines in main.lua.

local world =  

Voila!  Our two-layer world is now visible.

Moving worlds

Once we have everything on the screen, it’s a simple matter of getting everything to interact.  If we change our button code in main.lua to call a function checkMove instead of simply printing “north,” then we can write another function that will move the hero.

main.north:addEventListener("click", function() checkMove(0, -1) end)

North is -1 in the Y-direction and 0 in the X-direction.  We also need to know where the hero is to tell the program where to move the hero tile to.  checkMove will simply call moveHero with the hero’s location, but shortly it will also check to see if a move is valid and later on, whether an attackMonster function should be called.

Local hero = {}
hero.x, hero.y = 6, 6

local function checkMove(dx, dy)
     world:moveHero(hero, dx, dy)

moveHero then is the key.  moveHero looks at self.mapArrays[LAYER_MONSTERS] and changes the 1 to a 0 and puts the 1 in the new location.  Meanwhile, in the TileMap self.mapLayers[LAYER_MONSTERS], we use TileMap:clearTile at the old location and TileMap:setTile at the new location.   Also, both TileMaps are shifted to keep the hero centered on the screen.

function WorldMap:moveHero(heroX, heroY, dx, dy)

  local array = self.mapArrays[LAYER_MONSTERS]
  array[hero.x + (hero.y - 1) * LAYER_COLUMNS] = 0
  self.mapLayers[LAYER_MONSTERS]:clearTile(heroX, heroY)
  array[hero.x + dx + (hero.y + dy - 1) * LAYER_COLUMNS] = 1
  self.mapLayers[LAYER_MONSTERS]:setTile(heroX + dx, heroY + dy, 1, 1)

  for i = 1, #self.mapLayers do
    local layer = self.mapLayers[i]
    layer:setX(layer:getX() - (dx * TILE_WIDTH)) 
    layer:setY(layer:getY() - (dy * TILE_HEIGHT))

  hero.x = hero.x + dx
  hero.y = hero.y + dy 

At the end, the hero variables .x and .y are updated with their new location too.  So moveHero makes sure all the game variables (self.mapArrays, self.mapLayers and hero) all represent the same thing when it comes to movement.

How walls become walls and how heroes run into them

If you implement the above code you’ll quickly realize the hero can just walk all over everyone and everything because there’s no way to tell what tile should be walkable or even what tile is what.  We need another variable telling us which self.mapArray values are places we can walk or not.  That variable is cyclopedia.  Add the following definition to Constants.lua

cyclopedia = {
  ["monsters"] = 
  {[1] = {name = "hero"},
   [2] = {name = "goblin"},
   [3] = {name = "bugbear"},
   [4] = {name = "orc"},
  ["background"] = 
  {[1] = {name = "floor", blocked = false},
   [2] = {name = "floor", blocked = false},
   [3] = {name = "wall", blocked = true},
   [4] = {name = "wall", blocked = true}}}

Now we can rewrite the checkMove function and reference the cyclopedia entries to see what’s what when the player tries to move.  But first we need a WorldMap function that tells us what the self.mapArray entry is that they’re trying to move on to.  Add getTileKey to TileMaps.lua.

function WorldMap:getTileKey(x, y) 
  local index = x + (y - 1) * LAYER_COLUMNS
  for n = #self.mapArrays, 1, -1 do 
    local array = self.mapArrays[n]
    if array[index] ~= 0 then 
      return array[index], n 

The main logic behind getTileKey is to look at the mapArrays, highest layer first and pull the first entry that isn’t zero out.  getTileKey works because the background layer is filled with non-zero entries.  getTileKey returns both the entry and the layer it’s on.  This will be the final piece of glue that ties the mapArrays and mapTiles together.  Here the compass buttons call checkMove with a tile they want the hero to move onto.  getTileKey figures out what tile that is.  If it’s a monster or a cyclopedia.background entry that is blocked, then the move doesn’t happen.

local function checkMove(dx, dy)

  local entry, layer = world:getTileKey(hero.x + dx, hero.y + dy) 
  if layer == LAYER_MONSTERS then 
    local tile = cyclopedia.monsters[entry] 
    print("that's a " ..
  elseif layer == LAYER_TERRAIN then 
    local tile = cyclopedia.background[entry] 
    if tile.blocked then 
      print("that's a " .. 
      world:moveHero(hero, dx, dy)

Now when our buttons call checkMove to call moveHero, we can intervene.  We now have the beginnings of a world the hero can interact with.


At the end of Step 1, we could have moved on to create a calculator or another application.  Now it definitely looks like a game.   But it’s not a Roguelike yet.  It’s true we have turn-based gameplay and tile-based graphics, but one of the defining features of a Roguelike is procedurally generated levels.

Next up:  procedurally generated levels and monsters.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s