Roguelike, step 5: health talk

Previous tutorial – Download files – Next tutorial

Introductory remarks on the color of death – Print jobs listened for and framed – Warning:  graphic violence  

Introductory remarks on the color of death

The old Roguelikes are famous for their complete disregard for all things related to graphics, including color.  By the 1990s though, even a pure ASCII Roguelike like Angband dabbled in different colors for various dungeon features.


Today, developers recognize that well done color schemes give even pure ASCII Roguelikes a certain charm.


The reason not to think too much about color theory is that it quickly becomes a a huge side tangent of choices that quickly wastes a perfectly good afternoon that could have been time well spent adding another cool feature to your game.  On the plus side, there are those who have spent perfectly good afternoons obsessing about the perfect palette.  So let’s just stand on the shoulders of giants, and type into Constants.lua Dawnbringer‘s 16 color scheme.

COLOR_BLACK @ 0x140C1C   -- slightly off black
COLOR_DKGREY @ 0x4E4A4E  -- dark grey
COLOR_GREY @ 0x757161    -- light grey, brown hint (main background)
COLOR_LTGREY @ 0x8595A1  -- lighter grey, blue hint
COLOR_DKBLUE @ 0x30346D  -- dark blue, night sky?
COLOR_BLUE @ 0x597DCE    -- medium to light blue
COLOR_LTBLUE @ 0x6DC2CA  -- light teal
COLOR_DKBROWN @ 0x442434 -- purple/brown
COLOR_BROWN @ 0x854C30   -- medium to light brown
COLOR_GREEN @ 0x346524   -- leaf green
COLOR_LTGREEN @ 0x6DAA2C -- light green
COLOR_RED @ 0xD04648     -- reddish pink
COLOR_APRICOT @ 0xD2AA99 -- white skin flesh tone
COLOR_WHITE @ 0xF9FAFA   -- slightly creamy white

Why this emphasis on color?  My feeling is we should use color to

  1. avoid garishness (at the very least).
  2. better communicate game information (ideally)

With point #2 in mind, let’s go ahead and design an in-game, on-screen print messages function.  I would like it to report different kinds of messages in different colors.  As an example, here the hero has just killed a monster.


The older damage messages are displayed in a different color than the newer death message.  We’ll also have the messages fade over time.  .

Print jobs listened for and framed

Fire up a new file called Messages.lua.  We’ll create a Messages class that is based on the Fading Stars example in your installation folder.  Messages:init() will create two variables and an EventListener.

Messages = Core.class(Sprite)
function Messages:init()

  self.board = {}
   --set up a background for the messageboard =, 1, MSG_BOARD_WIDTH, 300), MSG_Y)
  self:addEventListener(Event.ENTER_FRAME, self.onEnterFrame, self)

self.board is simply a table to hold all the messages we’ll add to the screen.  And because we want to make sure they’re readable, we’ll create a nice dark blue background as well. is perfect for that.  We also define some constants so everything will be easy to move around if we decide on a different place later on.  The final thing Messages:init() does is add an EventListener.  Let’s talk about what exactly that’s doing here.

We last thought about events when creating the compass buttons.  There, the Button class dispatched a ‘click’ event and we then wrote an EventListener to respond to each compass button ‘click’.  Here, addEventListener() creates a frame event and a function to respond to it.  Messages:onEnterFrame() will now be called every frame so it’s up to us to write it.  What we want it to do is check for and remove messages as they’re added to the stage.  But first, let’s write add(), our print-to-the-screen function.

function Messages:add(text, type)

  -- text could be damage which is just an integer
  local t = tostring(text)
  local msg =, t, true)

  --set color based on type
  if type == MSG_DESCRIPTION then
  elseif type == MSG_ATTACK then
  elseif type == MSG_DEATH then
  --add up all the heights of the current messages in the sidebar
  local height = 0
  if self.board then
    for key, m in pairs(self.board) do
      height = height + m:getHeight() 
  msg:setPosition(MSG_X, MSG_Y + height + msg:getHeight())

  table.insert(self.board, msg)


Messages:add() creates a TextField and assigns a type-based color.  It’s position is based on how many messages are already in self.board.  Then it gets added to self.board.’s alpha is refreshed to it’s full, but still mostly transparent, value of 0.25.  Finally,  the message is added to the stage.

What happens next is why we created an onEnterFrame event listener in Messages:init().  The center of Messages:onEnterFrame() is a for-loop that loops through self.board, fading the alpha of each message and tagging messages that need to be removed.  The rest of the function removes the message from the stage and the self.board table.

function Messages:onEnterFrame()

 --check for messages to fade and remove
 if self.board then 
   --id is the message to be removed when self.board[id]:getAlpha() = 0
   local id = nil 
   --dy is the height of identified message
   local dy = 0 
   --go through the list
   for key, msg in pairs(self.board) do
     --slowly fade each message to 0
     local a = msg:getAlpha() - MSG_FADE
     --if faded, tag for removal
     if a < MSG_FADE then
       id = key
       dy = msg:getHeight() 

   --if found, remove from self.board
   if id then 
     table.remove(self.board, id)
     --move the remaining messages up
     for key, msg in pairs(self.board) do 
       msg:setY(msg:getY() - dy)

 --remove the background if there are no messages to display
 if #self.board == 0 then

With Messages:onEnterFrame() fading and removing messages every frame of the action, we can use Messages:add() to print stuff on the screen whenever we want.  How do we do that?

First, we create an instance of our class in Gamelogic.lua.

 self.msg =

Then, we find and replace print() with self.msg:add().

self.msg:add("that's a " .., MSG_DESCRIPTION)

Warning:  graphic violence

With the Messages class integrated into our game, we can now report in-game action on-screen.  The hero’s mighty blows against the monsters are added to the stage as “4” or “miss”.  Another way to add more information to the screen is by visually showing how many hit points a monster has left.  There are many ways to do this, but HP health bars are the standard way.  They can be either on a specific region of the screen, directly above the monster, or off to the side.  This shot from World of Warcraft has all of the above:

hp would be one solution that Gideros Mobile offers up.  We could set up the hero’s HP as a rectangle off to the side and each monster’s right above them as the hero hacks away.  Instead, I propose health circles.


Our monsters are represented as circular icons after all.  Plus, by working through the logic of getting my poorly drawn circles up on the screen, we can then go ahead and replace them with cooler graphics instead.

We already know how to get them on the screen, because we used the same logic for the monsters.  That is, we can treat the HP bars as another TileMap.  So let’s go ahead an add a health TileMap layer.  If we create a addHP function when we create the world in WorldMap:init(), then we can display it when the time is right.  The above image named tileset-health-108px.png is our tileset and it can be a new layer 4 in our mapArrays and mapLayers variables.

function WorldMap:init(hero, monsters)

  self.mapArrays = {}
  self.mapLayers = {}

  self.mapArrays[LAYER_TERRAIN] = self:generateLevelTerrain()
  self.mapArrays[LAYER_ENVIRONMENT] = self:addEnvironment()
  self.mapArrays[LAYER_MONSTERS] = self:placeMonsters(hero, monsters)
  self.mapArrays[LAYER_HP] = self:addHP(hero, monsters)
  self.mapArrays[LAYER_LIGHT] = self:addLight(hero)

The new function, WorldMap:addHP(hero, monsters) simply returns an array of filled with 0.

function WorldMap:addHP(hero, monsters)

  --tiles are 0 at the start
  local hArray = {}
  for y = 1, LAYER_ROWS do
    for x = 1, LAYER_COLUMNS do
      hArray[index(x, y)] = 0
  return hArray

In addition, we’ll need to add a .HPbar = 0 when we define our monsters and hero.  When we want to change the HPbar picture that’s displayed, we’ll coordinate .HPbar with the health mapArray and mapLayer.  This is easy to do as 2-3 lines of code, but a WorldMap:changeTile() function will make it a one line call in our program when we need it.

function WorldMap:changeTile(layer, entry, x, y)
--Change both the self.mapArrays entry and the tile in self.mapLayers

  local array = self.mapArrays[layer]
  array[index(x, y)] = entry
  self.mapLayers[layer]:setTile(x, y, entry, 1)

With that, we make our changes live with only two lines of code in Game:attackMonster().

 --roll for damage
 roll = self:roll(weapon.bonus, weapon.damage, weapon.dice)
 self.msg:add(roll, MSG_ATTACK)
 --adjust the monster's hp and the HP arrays
 monster.hp = monster.hp - roll
 monster.HPbar = 8 - math.floor((monster.hp / * 8), monster.HPbar, monster.x, monster.y)
 if monster.hp <= 0 then

After adjusting the monster.hp when damage is dealt, we calculate which HPbar picture we need and call WorldMap:changeTile() to make it happen.  Here we deal damage, but the same function can be used to heal too.


In this step, we got a lot more game information on the screen.  Our game now conveys a little bit more about what’s going on as it’s going on.  The more we can go this, the more the player is going to feel their hero is embedded in a world where their actions have consequences.  Of course, the only consequence of their actions is monster death.  In the next step, let’s give the monsters a chance to move and attack.

Next up:  The monsters fight back!