Main Page | Related Pages

Using Tiles

Todo:
(A02) Document how to use tiles in the various graphics modes.

Tile Overview

An easy way to think of tiles are by dividing the NDS screens into a series of 8x8 squares. In each square, there can be a tile. Each visible screen is 32 tiles wide and 24 tiles high.

Tiles consist of two parts. The tile data, often referred to as character data, contains the actual graphical data. A second section, referred to as the map or screen data is simply a list of tile numbers that should be displayed. A few basic tiles can create complex displays quite nicely.

Each screen supports up to four backgrounds of tiles. Referred as BG0

Note that there are two types of backgrounds. The first type is typically called "Text Backgrounds" while the second type is called "Rotation Backgrounds."

Technical Cross-referneces

Text Backgrounds

Rotation Backgrounds

Examples

List of examples:
Tile Example #1 -- Static Text and Graphics Most basic example. Presents the minimum amount of code with relationship to activating registers to present a simple scene on each screen using text backgrounds.
Tile Example #2 -- Multiple Background Layers

Tile Example #1 -- Static Text and Graphics

The first example will not feature any type of user input, but simply display a text message on the top screen, and a forest map with a castle on the bottom map. This example will use Mode 0 graphics.

Start by copying the template directory to tile01 for this example.

cd ~/ds_projects cp -R template tile01

Creating the Graphics

Before getting to coding, two sets of graphics are required for the example. First, a set of alphabetic characters are needed to be able to display text on the top screen. Then a second set of graphics will be for the bottom display.

Creating the Text Tiles

To start, create the text that will be on the top screen. Start The GIMP and create a new image using File->New. Specify that the width of the image should be 760 pixels, and the height should be 8 pixels and click ok. The 760 pixels is wide enough for 95 characters. Next, double click on the foreground color to bring up the "Change Foreground Color" dialog and specify the "Hex Triplet" FF00FF and click ok. This specifies a pink color which will be the background/transparent color for the letters. Click on the view menu of the new image, and zoom the display to 16:1. Now click on the bucket and fill the background of the new image with the pink color. Ensure that the upper left pixel is visible on the new image. Click on the "Add Text to Image" button. In the Text Options dialog, click on the font button specify "Console 8x8". Then set the Size of the font to 8. Finally, change the color of the text to black. Now, click on the upper left hand pixel of the new image. This will cause the GIMP Text Editor dialog to be displayed. In this area, type the following characters:

!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~

Note:
The first character in the characters to type is the "space" character. Be sure not to miss it.
This is the order that characters appear in the ASCII chart, which will allow us to easily determine the tile number by adding or subtracting a constant to each character in a string.

Now, on the image menu, click view and select grid lines. You should notice that The GIMP, by default, has left the top row and right column of the 8x8 tile clear. This causes a problem with descending characters, such as "y". To fix this, click on the move tool, and shift up all of the text one pixel.

220text_pre_move.png

The text with broken descenders.

220text_post_move.png

The text with descenders fixed.

Save the image thus far as alpha8.xcf in ~/ds_projects/tile01/arm9. Next, generating text tiles requires a special text palette. While The GIMP has focus, pres Ctrl-P to popup the list of palettes. Next, create a new palette by pressing the button with a "+" on it. Name the palette "NDS Text". Now add a color by clicking on the button with a "+" on it. Double click on the start of the line to bring up the "Edit Color Palette Entry" and specify FF00FF for the hex triplet, and then press OK. Now ensure that the foreground is set to black, and press the "+" button again. This results in a palette with the first entry pink (our background color) and second entry black (our foreground color.) Finally, click on the save button and close the palette related windows.

Now, generate the alpha8.h and alpha8.c files by running the "Python-Fu/NDS/Create Tiles" script from the image menu. A dialog is displayed prompting for information on the tileset being created. Specify the output .c/.h file as ../arm9/alpha8. Depending on the current directory for gimp, specify the path to the arm9 directory. In the screenshot below, gimp was run from the tile01 directory, making the arm9 directory readily available. Set the palette to use as "Use Specified" and then place "NDS Text" in the palette name area. Finally, specify to generate 256 color tiles (16 color tiles can be generated, but then can only be used with other 16 color tiles, so generate the tiles appropriately.)

220create_tiles.png

CreateTiles Dialog

Creating Map Graphics

The graphics on the bottom scene will a woodland scene with a castle in the middle. To do this, a set of four tiles work together to create a single item for the display. In the case of the castle, it is a set of eight tiles. Using The GIMP, create a graphic similar to these map graphics:

220map_graphics.png

Map Graphics

When the "Python-Fu/NDS/Create Tiles" script executes, it will reorder the tiles going across. Each tile has an index number. When this number is placed in the map, the tile is displayed.

220map_graphics_with_index.png

Map Graphics With Indexes Shown

In Using Sprites, an alternative script is used for generating sprites. This script can be used to generate tiles as well, and would have changed the ordering, and therefore the indexing, of the tiles. Specifying that the image should be broken into four 16x16 sprites would have made access the tiles more intuative. However, for this example, we will use the normal "Create Tiles" script.

Coding the Tile Display

The main function for the tile01 application is exceedingly simple.

int main(int argc, char** argv) {
  Tutorial01Tile& app(Tutorial01Tile::theApp());

  app.go();

  return 0;
}

The constructor for Tutorial01Tile starts by turning on the screens, enabling VRAMs A and C, and sets the display for use with Mode 0.

VRAM_A is associated with the touch screen and is the background 0 video memory. VRAM_C is associated with the top display and is the background 0 video memory for that screen.

There are two core graphics engines, named Core A and Core B. Core A is associated with the touch screen and Core B is associated with the top screen. These two cores are completely separate, and have their own sprites, tiles, and backgrounds. To use a tile on both screens requires loading the tile into both cores.

Tutorial01Tile::Tutorial01Tile()
{
  unsigned int i, j;
  const int char_base = 0;
  const int screen_base = 20;

  // Turn on the screens and 2D cores and switch to mode 0

  POWER_CR = POWER_ALL_2D;
 
  vramSetBankA(VRAM_A_MAIN_BG);
  vramSetBankC(VRAM_C_SUB_BG);
 
  DISPLAY_CR = MODE_0_2D | DISPLAY_BG0_ACTIVE;
  SUB_DISPLAY_CR = MODE_0_2D | DISPLAY_BG0_ACTIVE;

This code configures background 0 for both the top and touch screens. Both sets of tiles are 256 color objects, though they use much less than a full 256 color entries. For a full discussion related to character blocks and screen blocks, see Character and Screen Blocks. The application specifies for both backgrounds that character base 0 is the location of the tile graphic information. Additionally, screen base 20 is the location of the map data determining which tiles to display in each location on the screen. The BG_TILE_BASE and BG_MAP_BASE are macros provided by ndslib that shift the data to the correct location for saving into the background register.

  SUB_BG0_CR = BG_256_COLOR | BG_TILE_BASE(char_base) |
    BG_MAP_BASE(screen_base);
  BG0_CR = BG_256_COLOR | BG_TILE_BASE(char_base) | BG_MAP_BASE(screen_base);

The CHAR_BASE_BLOCK and CHAR_BASE_BLOCK_SUB macros provides the pointer to start of the location of the tile information. SCREEN_BASE_BLOCK and SCREEN_BASE_BLOCK_SUB provide the pointer to the start of the map that represents the screen. These must match the numbers provided to BG0_CR (0x04000008) and SUB_BG0_CR (0x04001008). The sub_map and map variables are used in a little bit to populate the screen, but before that the sub_tile and tile variables are used to load tile graphics in preparation for display.

  u16* sub_tile = (u16*)CHAR_BASE_BLOCK_SUB(char_base);
  u16* sub_map = (u16*)SCREEN_BASE_BLOCK_SUB(screen_base);
  u16* tile = (u16*)CHAR_BASE_BLOCK(char_base);
  u16* map = (u16*)SCREEN_BASE_BLOCK(screen_base);

The tiles.h file defines the tilesPalette, the color palette for the woodland scene and tilesData, the actual graphics data. Likewise, the alpha8.h file defines the alpha8Palette which is the color palette for the alphabetic characters and apha8Data which specifies how to draw the letters.

The palette for the woodland scene is loaded into the background palette and the character palette loaded into the top (sub-) background palette.

Remember when the alphabetic characters were generated, the background color was a pink, and the foreground color was black. In reality, these are extremely unpleasant to view. While it is possible to specify the "real" color palette when generating the image, it can be preferable to set the colors at run time. Simply setting the first and second palette entries to the desired colors will change the background and forground colors of the text. For this reason, it is recommended when generating a palette to be used in an application by many tiles, and where text will be required, to reserve the first two entries. The first entry is the background color in general, and will be transparent for sprites, while the second entry is always the foreground color of the text.

RGB15 is a macro that converts 3 5-bit values (Red, Green, and Blue) into the correct color code for storage in the palette. The application changes the background color to black and the foreground color to green. While the alphabet requires the use of 95 tiles, there is still room for many more tiles. All tiles share the same palette, so the application should be careful in palette generation, to use the same background color for any other tiles in position 0 of the tile palette, and reserve the second entry to the foreground color. This does not require the application to complete reserve that palette entry strictly for foreground colors though.

The code in the loop to determine how many palette entries to copy is the most clear. The following code is all equivalent:

  1. for(i = 0; i < sizeof(alpha8Palette) / sizeof(u16); ++i)
  2. for(i = 0; i < sizeof(alpha8Palette) / 2; ++i)
  3. for(i = 0; i < sizeof(alpha8Palette) >> 1; ++i)
  4. for(i = 0; i < 256; ++i)

There is no difference in efficiency as the compiler will generate code equivalent to what it will generate with example #4.

  // Load our tile information
  for(i = 0; i < sizeof(alpha8Data) / sizeof(u16); ++i) {
    sub_tile[i] = alpha8Data[i];
  }

  for(i = 0; i < sizeof(tilesData) / sizeof(u16); ++i) {
    tile[i] = tilesData[i];
  }

  for(u16 i = 0; i < sizeof(tilesPalette) / sizeof(u16); ++i) {
    BG_PALETTE[i] = tilesPalette[i];
  }

  for(i = 0; i < sizeof(alpha8Palette) / sizeof(u16); ++i) {
    BG_PALETTE_SUB[i] = alpha8Palette[i];
  }

  BG_PALETTE_SUB[0] = RGB15(0, 0, 0);
  BG_PALETTE_SUB[1] = RGB15(0, 31, 0);

  // Tile information loaded

The msg variable contains the data to display. Using tiles, the screen sizes are 32 tiles wide by 24 tiles high. This message can be changed to any values for display on the upper screen.

  char* msg[] = {
    "",
    "Tile Demo.",
    "tile01 project.",
    "",
    "http://www.dspassme.com/",
    "            programmers_guide/",
    "",
    "PhoenixRising",
  //01234567890123456789012345678901
  };

To actually display data on the screen, the map is used to indicate which tile to display at a given location. Assuming that the upper left hand corner of the screen is row 0, column 0, to access the map for a tile at a given location is row + column * 32. First, to populate the text message on the top screen, loop over the array of message pointers and turn on specific tiles at each location. Because of the order specified for the text in the tiles, simply subtracting the value of the ' ' character (0x20) from the value of the character in the string will yield the index of the character into the tile set.

The drawMap method is invoke to draw the lower portion of the screen simply because of the amount of data used in this example to draw the display.

  // Draw the message on the screen.
  for(i = 0; i < sizeof(msg) / sizeof(char*); ++i) {
    for(j = 0; msg[i][j] != '\0'; ++j) {
      sub_map[j + (i * 32)] = msg[i][j] - ' ';
    }
  }

  // Draw the forest scene
  drawMap(map);
  // Both screens updated.

The final block of code in the constructor sets up the interrupt handler to request IRQ_VBLANK interrupts to be delivered to the InterruptHandler. While interrupt handlers and requests are being updated, be sure to displable interrupts by setting IME to 0. Once the updates are complete, turn the interrupts back on by setting IME to 1.

  // Enable the V-blank interrupt
  IME = 0;
  IRQ_HANDLER = &InterruptHandler;
  IE = IRQ_VBLANK;
  IF = ~0;
  DISP_SR = DISP_VBLANK_IRQ;
  IME = 1;
}

Now, the "dreaded" drawmap routine. The toDraw variable represents the entire 32x24 tile screen. The map is represented in this form because it is probably slightly more realistic than simply coding an algorithm to generate the display. More realistic in the sense that map levels in actual games will be much larger than 32x24, and will not be representable by an algorithm. In these cases, the game designer will typically use a tool to generate the data in a manner similar to the way the tiles were generated into .h files. In these cases, sections of the map would be rendered to the screen rather than the entire portion of data.

The defines in this section represent portions of the tiles. Defines starting with the letter C represent portions of the castle. Defines starting with the letter P represent parts of the path. Finally, the defines starting with the letter T represent the tree. In a manner similar to the way the text was displayed on the top screen, the castle in the woods is drawn on the bottom screen.

#define C11 0   
#define C12 1   
#define C13 2   
#define C14 3   
#define C21 4   
#define C22 5   
#define C23 6   
#define C24 7   
#define P11 8   
#define P12 9   
#define P21 12  
#define P22 13  
#define T11 10  
#define T12 11  
#define T21 14  
#define T22 15  
void Tutorial01Tile::drawMap(u16* map)
{
  u16 toDraw[] = {
    T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,P11,P12,P11,P12,T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,
    T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,P21,P22,P21,P22,T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,

    T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,P11,P12,P11,P12,T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,
    T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,P21,P22,P21,P22,T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,

    T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,P11,P12,P11,P12,T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,
    T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,P21,P22,P21,P22,T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,

    T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,P11,P12,P11,P12,T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,
    T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,P21,P22,P21,P22,T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,

    T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,P11,P12,P11,P12,P11,P12,P11,P12,T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,
    T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,P21,P22,P21,P22,P21,P22,P21,P22,T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,

    P11,P12,P11,P12,P11,P12,P11,P12,P11,P12,P11,P12,P11,P12,P11,P12,P11,P12,P11,P12,P11,P12,P11,P12,P11,P12,P11,P12,P11,P12,P11,P12,
    P21,P22,P21,P22,P21,P22,P21,P22,P21,P22,P21,P22,P21,P22,C11,C12,C13,C14,P21,P22,P21,P22,P21,P22,P21,P22,P21,P22,P21,P22,P21,P22,

    P11,P12,P11,P12,P11,P12,P11,P12,P11,P12,P11,P12,P11,P12,C21,C22,C23,C24,P11,P12,P11,P12,P11,P12,P11,P12,P11,P12,P11,P12,P11,P12,
    P21,P22,P21,P22,P21,P22,P21,P22,P21,P22,P21,P22,P21,P22,P21,P22,P21,P22,P21,P22,P21,P22,P21,P22,P21,P22,P21,P22,P21,P22,P21,P22,

    T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,P11,P12,P11,P12,P11,P12,P11,P12,T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,
    T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,P21,P22,P21,P22,P21,P22,P21,P22,T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,

    T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,P11,P12,P11,P12,T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,
    T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,P21,P22,P21,P22,T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,

    T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,P11,P12,P11,P12,T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,
    T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,P21,P22,P21,P22,T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,

    T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,P11,P12,P11,P12,T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,
    T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,P21,P22,P21,P22,T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,

    T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,P11,P12,P11,P12,T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,T11,T12,
    T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,P21,P22,P21,P22,T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,T21,T22,

  };

  for(u16 i = 0; i < sizeof(toDraw) / sizeof(u16); ++i) {
    map[i] = toDraw[i];
  }
}
// end Tutorial01Tile::drawMap

In all of the tutorials provided with this guide, the actual InterruptHandler will be exceedingly simple in that it will simply obtain a reference to the application, and pass the interrupt to the updateDisplay method. In future tutorial applications, the interrupt handler will pass parameters to the updateDisplay method to enable the application to know what interrupt has occurred. This will come later as we will see in this appication, our interrupt handler does next to nothing.

static void InterruptHandler(void) {
  if (IF & IRQ_VBLANK) {
    Tutorial01Tile& app(Tutorial01Tile::theApp());

    app.updateDisplay();

    IF = IRQ_VBLANK;
  }
}
// end InterruptHandler

The updateDisplay method simply returns control back to the interrupt handler as it doesn't have any updates to perform.

void Tutorial01Tile::updateDisplay(void) {
}

Results

220results_tile01.png

Resulting Display

Concept Review

Tasks to Undertake as a Result of What was Learned

From this example, the following items are added to libpr for future tuturials. For documentation of the resulting classes, click on the class name to see the documentation within libpr.

Tile Example #2 -- Multiple Background Layers


Generated on Fri Apr 22 13:47:40 2005 for Homebrew Programmers Guide to the Nintendo DS by doxygen 1.3.6