EVADE – Using Text in an Arduboy Game

   Front End Development
Arduboy Game Evade Text

EVADE!

Welcome to our first in-depth post on the programming of our Arduboy game, EVADE. This is a continuation of a series of posts. So, if you missed our introductory one: Creating EVADE, a Side Scrolling Shooter Game for Arduboy give it a quick read. There are a lot of great tips for getting your Arduboy development environment up and running.

In this post, we’re going to discuss how to use text in your game:

  • Text Basics
  • How to Create a Simple “Typing Effect”
  • How to Create a Custom Font

Basic Text

Out of the box, the Arduboy has no font or ability to display text characters. So, there’s no way to print text on the screen as such. Thankfully, the Arduboy library helps out here by providing a bitmap font where each individual character is represented in code as an array of pixels. The library also provides functions for using this font to display characters in a specific size at a specific location on screen.

For Evade, wherever we wanted to use this “default” font, we called a function that we created to wrap the lower level Arduboy library function calls as needed:

void printText(const char *message, byte x, byte y, byte textSize) {
  arduboy.setCursor(x, y);
  arduboy.setTextSize(textSize);
  arduboy.print(message);
}

This gave us a single function call to handle text display as a one liner throughout the game.

Typewriter Effect

However, as development progressed we realized we wanted to treat some of our text differently. To help with telling the story, we wanted to “print” our text to the screen one character at a time, using a different typeface and with a sound effect as each character appeared on the screen. This required us to design our own bitmap font, convert it to code, and develop a function to display characters one at a time.

As we only wanted to use this effect for “fixed” strings that would always be known at compile time (credits, menu items, introductory text etc), we could store all of these in the Arduboy’s flash memory (PROGMEM). This freed up precious SRAM (an area of memory where runtime variables are stored) but meant that we would have to read the strings that we wanted to display byte by byte (character by character). This suited our desire for a typewriter effect quite well, and is demonstrated in the video below.

If you’re interested in other memory management tricks for Arduboy, be sure to check back for our upcoming post focussed on it.

Custom Font

We created our custom font as a series of bitmaps, one for each letter, numeral and symbol we wanted to be able to display. Using the same method we employed to encode our bitmap graphics for the game, each letter was then turned into an array in a C header file, containing the height and width of the character plus the hex values for each column of pixels that form it.

For example, here’s “P”:

PROGMEM const unsigned char P[] = {
  5, 6,
  0x3F, 0x01, 0x09, 0x09, 0x0F
};

To make things easy on ourselves, we designed a fixed width font, eliminating the need to space characters at variable intervals whilst displaying a string of text. We used a 5 pixel gap (the width of each of our characters) as the space character. In order to use the full font more easily in our code, we then created an “alphabet” array, with each array item mapping to a letter:

const unsigned char *alphabet[29];

alphabet[0] = A;
alphabet[1] = B;
alphabet[2] = C;
...

Getting Things Done

This gave us a 29 element array as our font using all 26 uppercase letters plus additional punctuation “: ! and .” . To “type” out characters from the font with a typewriter-like effect, we wrote the following code:

void drawChrs(byte cPos, byte yPos, const uint8_t *letters, unsigned long delayTimer) {
  byte curs = cPos,
       strLen = pgm_read_byte(letters),
       letter;

  for (byte i = 0; i <  strLen; i++) {
    letter = pgm_read_byte(++letters);
    
    // Space chr
    if (letter == 255) {
       curs += 5;
    }
    else {
        drawBitmap(curs,yPos, alphabet[letter], 0);
        curs += pgm_read_byte(alphabet[letter]) + 1;
    }
    
    display();

    if (delayTimer) {
      playTone(100, delayTimer - 10);
      delay(delayTimer);
    }
  }
  ... 

Here, we read a string of letters to display from the program memory starting at the location represented by letters. An example message that this function can display looks like this:

//YOU SURVIVED BUT
PROGMEM const uint8_t playerWon0[] = {
  16, // Number characters
  24, 14, 20, 255, 18, 20, 17, 21, 8, 21, 4, 3, 255, 1, 20, 19
};

The first value tells us how many characters are in the string to display (16), subsequent values are array offsets into our alphabet array. For example alphabet[4] contains the encoded data for the bitmap for the letter E.

Having read the byte at letters[0], we use that as a length byte which tells us how many times to execute the loop that reads characters from the program memory at an incrementing offset from the original start location. In the example above, letters[0] is 16 so the code would then read the next 16 byte sized memory locations and interpret the value contained in each as an offset into the alphabet array (24 = Y, 14 = O and so on). We then use the drawBitmap function to display the encoded bitmap for that letter at the specified x, y location on screen. curs (the x coordinate) is then updated to the position for the next character. If the code sees a character with array index 255, we treat that as a space, and simply add 5 to curs as there is no bitmap image to draw in this case.

Before drawing the next character we first play a tone (we will cover generating sound and music in a separate post), then wait a short while, which creates the typewriter effect. We also call display() which is our function that updates the Arduboy's screen.

Rather than create every encoded string that we needed for the game by hand, we wrote a small utility in Javascript to do it. This translates an array of source strings (credits) to the format that the drawChrs function expects, using a mapping object to get the array offset into the alphabet array for each character in the string being translated. The utility looks like this:

var map = {
  'A' :  0, 'B' :  1, 'C' :  2, 'D' :   3, 'E' :  4,
  'F' :  5, 'G' :  6, 'H' :  7, 'I' :   8, 'J' :  9,
  'K' : 10, 'L' : 11, 'M' : 12, 'N' :  13, 'O' : 14,
  'P' : 15, 'Q' : 16, 'R' : 17, 'S' :  18, 'T' : 19,
  'U' : 20, 'V' : 21, 'W' : 22, 'X' :  23, 'Y' : 24,
  'Z' : 25, ':' : 26, '.' : 27, ' ' : 255
}

var credits = [
  "CREDITS", "Jay Garcia", "Simon Prickett",
  "Stan Bershadskiy", "Andrew Owen", "Andy Dennis", 
  "Tim Eagan", "Drew Griffith", "JD Jones", 
  "Jon Van Dalen", "Lucas Still", "Matt McCants",
  "Play", "Settings", "Credits"
]

var x = 0,
    endStr = '\n\n';

credits.forEach(function(creditStr, index) {
  creditStr = creditStr.toUpperCase();
  var header = `\n\n//${creditStr}\nPROGMEM const uint8_t credits${index}[] = {\n`,
      outStr = '';
  
  creditStr = creditStr.split('');
    
  creditStr.forEach(function(str) {
    if (outStr.length > 0) {
      outStr += ', '; 
    }
      outStr += map[str];
  });
    
  outStr = creditStr.length +',// Number characters\n' + outStr + '\n};';
  endStr += (header + outStr);  
})

console.log(endStr);

If you'd like to play with this, feel free to try it out on JSFiddle: open up the console in your browser to see the output, which should look like:

//CREDITS
PROGMEM const uint8_t credits0[] = {
7,// Number characters
2, 17, 4, 3, 8, 19, 18
};

//JAY GARCIA
PROGMEM const uint8_t credits1[] = {
10,// Number characters
9, 0, 24, 255, 6, 0, 17, 2, 8, 0
};
...

Game Over

Adding text to your game is one of the fundamental things you will need for your project. Although the Arduboy does come with some basic functions, you may find yourself needing something custom to help give your game that “polished” look.

Making a custom font and adding a simple font effect are two of the easiest things you can do to make your game shine.

Play Evade!

If you'd like to play our game yourself, you'll need:

  • An Arduboy device ($49) from arduboy.com, Adafruit, or Pimoroni (United Kingdom)
  • The Arduino IDE (free), works on Mac, Windows and Linux, required to install our code
  • Our code, which is open source and downloadable from GitHub where you will also find instructions on compiling and installing it using the Arduino IDE

Let Us Know What You Think

We hope you enjoy playing our game as much as we enjoyed writing it. We'd love to see pictures of your high scores, tag us on Instagram or tweet us a photo of your high scores using the hashtag #evadehighscore.

Also, stay tuned for our next in-depth post on the programming of EVADE - Handling User Input.


Like What You See?

Got any questions?