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."
| 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 | |
Start by copying the template directory to tile01 for this example.
cd ~/ds_projects cp -R template tile01
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
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.
The text with broken descenders.
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.)
CreateTiles Dialog
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.
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.
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:
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) { }
Resulting Display
1.3.6