タイルモードはmode0~2に該当します。モード0の動作画面から見てみましょう。なお結構難しいと思うので何日か分けて取り組んでみてください。
Mode | 名称 |
0-2 | タイルモード |
3-5 | ビットマップモード |
何やらRPGの洞窟風景に見えますね。これらを表示するには以下の画像データを使います。
8x8ドットを1つの単位としてタイルのように並べて実現します。画像の場合だと8x8ドットだと小さすぎるので16x16ドット、4つ分を塊にしています。種類としては砂、沼、床、壁の4つです。洞窟風景を実現するにはパレットデータ、キャラクタデータ、マップデータの3つが必要です。さっそくそれぞれを見ていきましょう。
Color Palette RAM BG and OBJ palettes are using separate memory regions: 05000000-050001FF - BG Palette RAM (512 bytes, 256 colors) 05000200-050003FF - OBJ Palette RAM (512 bytes, 256 colors) Color Definitions Each color occupies two bytes (same as for 32768 color BG modes): Bit Expl. 0-4 Red Intensity (0-31) 5-9 Green Intensity (0-31) 10-14 Blue Intensity (0-31) 15 Not used
スプライトで説明した表を再度引用しました。手抜きではありません(汗。今回はBG Palette RAMがターゲットとなります。
色 | ドット単位の格納方法 | サイズ |
16 | 横2ドット=1byte(下位4bitが右、上位4bitが左) | 32byte |
256 | 横1ドット=1byte | 64byte |
こちらもスプライトの時に使った表と同じです。単位は1キャラクタ8x8ドットです。たとえば壁(16x16ドット)のキャラクタ番号は、6, 7, 14, 15となっています。
6 | 7 |
14 | 15 |
キャラクタデータの並び方を決めたものがマップデータです。16色の場合は2バイト(16ビット)単位であり、少々のオプションも含まれています。
The display background consists of 8x8 dot tiles, the arrangement of these tiles is specified by the BG Screen Data (BG Map). The separate entries in this map are as follows: Text BG Screen (2 bytes per entry) Specifies the tile number and attributes. Note that BG tile numbers are always specified in steps of 1 (unlike OBJ tile numbers which are using steps of two in 256 color/1 palette mode). Bit Expl. 0-9 Tile Number (0-1023) (a bit less in 256 color mode, because there'd be otherwise no room for the bg map) 10 Horizontal Flip (0=Normal, 1=Mirrored) 11 Vertical Flip (0=Normal, 1=Mirrored) 12-15 Palette Number (0-15) (Not used in 256 color/1 palette mode)
Tile Numberと書かれているところにキャラクタ番号を使います。マップデータは32個×32個×2バイトで2048バイトとなります。表示は256x256ドットの大きさですが、GBAの画面サイズは240x160なので一部は見えていない状態です。
#define BG0_MAP_SIZE (32*32*2) // 2048バイト const unsigned short ResBg0Map[] = { 6,7,6,7,6,6,6,6, 6,6,6,6,6,6,6,6, 6,6,6,6,6,6,6,6, 6,6,6,6,6,6,6,6, 14,15,14,15,6,6,6,6, 6,6,6,6,6,6,6,6, 6,6,6,6,6,6,6,6, 6,6,6,6,6,6,6,6, (以下略・・・)
今回は慎重にgritのモード0 256色タイルオプションと出力結果を見てみましょう。キャラクタデータは256色のため、64*16*1 = 1024バイト。パレットデータも同様に256色のため、256*2 = 512バイトとなっています。
-gt -fts -gu16
#ifndef GRIT_BG0_H #define GRIT_BG0_H #define bg0TilesLen 1024 extern const unsigned short bg0Tiles[512]; #define bg0PalLen 512 extern const unsigned short bg0Pal[256]; #endif // GRIT_BG0_H
スプライトの場合、キャラクタとマップデータを格納する場所は固定されおり、単純なコピーで済んでいました。ところでタイルモードは可変であり、自分で格納する場所を指定しなくてはいけません。対応表を以下に表します。
アドレス | マップ | キャラクタ |
0x6000000 | 0 | 0 |
0x6000800 | 1 | 0 |
0x6001000 | 2 | 0 |
0x6001800 | 3 | 0 |
0x6002000 | 4 | 0 |
0x6002800 | 5 | 0 |
0x6003000 | 6 | 0 |
0x6003800 | 7 | 0 |
0x6004000 | 8 | 1 |
0x6004800 | 9 | 1 |
0x6005000 | 10 | 1 |
0x6005800 | 11 | 1 |
0x6006000 | 12 | 1 |
0x6006800 | 13 | 1 |
0x6007000 | 14 | 1 |
0x6007800 | 15 | 1 |
0x6008000 | 16 | 2 |
0x6008800 | 17 | 2 |
0x6009000 | 18 | 2 |
0x6009800 | 19 | 2 |
0x600A000 | 20 | 2 |
0x600A800 | 21 | 2 |
0x600B000 | 22 | 2 |
0x600B800 | 23 | 2 |
0x600C000 | 24 | 3 |
0x600C800 | 25 | 3 |
0x600D000 | 26 | 3 |
0x600D800 | 27 | 3 |
0x600E000 | 28 | 3 |
0x600E800 | 29 | 3 |
0x600F000 | 30 | 3 |
0x600F800 | 31 | 3 |
マップは0~31、キャラクタは0~3が指定可能です。メモリ領域の重複は許されません。たとえばマップ16、キャラクタ2のようにしてはいけません。格納するマップデータとキャラクタサイズのバイト数は必ずチェックします。キャラクタデータが少ない場合、その分をマップで・・・ということもできます。
u32 tile = 0; u32 map = 8; REG_DISPCNT = (MODE_0 | BG0_ON); REG_BG0CNT = (BG_SIZE_0 | BG_256_COLOR | ((tile) << 2) | ((map) << 8) | 0);
今回はこのように設定を行っています。つまりタイルデータは0x6000000から格納し、マップデータは0x6004000から格納します。
REG_BGxCNTの内容をGBATEKから抜粋してみましょう。
4000008h - BG0CNT - BG0 Control (R/W) (BG Modes 0,1 only) 400000Ah - BG1CNT - BG1 Control (R/W) (BG Modes 0,1 only) 400000Ch - BG2CNT - BG2 Control (R/W) (BG Modes 0,1,2 only) 400000Eh - BG3CNT - BG3 Control (R/W) (BG Modes 0,2 only) Bit Expl. 0-1 BG Priority (0-3, 0=Highest) 2-3 Character Base Block (0-3, in units of 16 KBytes) (=BG Tile Data) 4-5 Not used (must be zero) 6 Mosaic (0=Disable, 1=Enable) 7 Colors/Palettes (0=16/16, 1=256/1) 8-12 Screen Base Block (0-31, in units of 2 KBytes) (=BG Map Data) 13 Display Area Overflow (0=Transparent, 1=Wraparound; BG2CNT/BG3CNT only) 14-15 Screen Size (0-3) Internal Screen Size (dots) and size of BG Map (bytes): Value Text Mode Rotation/Scaling Mode 0 256x256 (2K) 128x128 (256 bytes) 1 512x256 (4K) 256x256 (1K) 2 256x512 (4K) 512x512 (4K) 3 512x512 (8K) 1024x1024 (16K) In case that some or all BGs are set to same priority then BG0 is having the highest, and BG3 the lowest priority.
BG0~3というのは画面(back ground)の意味です。なぜ可変として面倒なことをしていたか、理由はここにあります。GBA(モード0)はマップデータを最大4つ持つことができ、重ね合わせ表示が可能になっています。また、14-15ビット目を見ると256x256~512x512ドットに変えることも可能です。先ほどの対応表の区切りをもう一度見てください。マップは2K=2048(0x800)バイト単位でした。512x512をしたい場合は8Kとなり、区切り4つ分が必要になります。
覚えることが大量に出てきたところでいったんクールダウンしましょう。とりあえず先ほどのRPGの洞窟風景のコードを以下に表します。
#include "lib/gba.h" #include "res.h" #define BG_MAX_CNT 4 typedef struct { u32 mapBase; u16* mapBaseAdr; u32 tileBase; u16* tileBaseAdr; } ST_BG; //--------------------------------------------------------------------------- ST_BG Bg[BG_MAX_CNT]; //--------------------------------------------------------------------------- void WaitForVsync(void) { while(*(vu16*)0x4000006 >= 160) {}; while(*(vu16*)0x4000006 < 160) {}; } //--------------------------------------------------------------------------- void BgInitMem(void) { const u32 mapBase[] = { 8, 9, 10, 11 }; const u32 tileBase[] = { 0, 0, 0, 0 }; vs32 i; for(i=0; i<BG_MAX_CNT; i++) { Bg[i].mapBase = MAP_BASE(mapBase[i]); Bg[i].mapBaseAdr = MAP_BASE_ADR(mapBase[i]); Bg[i].tileBase = TILE_BASE(tileBase[i]); Bg[i].tileBaseAdr = TILE_BASE_ADR(tileBase[i]); } for(i=0; i<32*32; i++) { Bg[0].mapBaseAdr[i] = 0; Bg[1].mapBaseAdr[i] = 0; Bg[2].mapBaseAdr[i] = 0; Bg[3].mapBaseAdr[i] = 0; } for(i=0; i<0x2000; i++) { Bg[0].tileBaseAdr[i] = 0; Bg[1].tileBaseAdr[i] = 0; Bg[2].tileBaseAdr[i] = 0; Bg[3].tileBaseAdr[i] = 0; } } //--------------------------------------------------------------------------- void BgInit(void) { BgInitMem(); REG_DISPCNT = (MODE_0 | BG0_ON); REG_BG0CNT = (BG_SIZE_0 | BG_256_COLOR | Bg[0].tileBase | Bg[0].mapBase | 0); } //--------------------------------------------------------------------------- void Bg0SetTile(u16* pDat, u32 size) { vu32 i; for(i=0; i<size; i++) { Bg[0].tileBaseAdr[i] = pDat[i]; } } //--------------------------------------------------------------------------- void Bg0SetPal(u16* pDat) { vu32 i; for(i=0; i<256; i++) { BG_PALETTE[i] = pDat[i]; } } //--------------------------------------------------------------------------- void Bg0SetMap(u16* pDat, u32 size) { vu32 i; for(i=0; i<size; i++) { Bg[0].mapBaseAdr[i] = pDat[i]; } } //--------------------------------------------------------------------------- int main(void) { BgInit(); Bg0SetTile((u16*)&bg0Tiles, bg0TilesLen/2); Bg0SetPal ((u16*)&bg0Pal); Bg0SetMap ((u16*)&ResBg0Map, BG0_MAP_SIZE/2); u32 x = 0; u32 y = 0; for(;;) { WaitForVsync(); if( !(REG_KEYINPUT & KEY_UP) ) y--; if( !(REG_KEYINPUT & KEY_DOWN) ) y++; if( !(REG_KEYINPUT & KEY_LEFT) ) x--; if( !(REG_KEYINPUT & KEY_RIGHT)) x++; x = x & 0xff; y = y & 0xff; REG_BG0HOFS = x; REG_BG0VOFS = y; } }
REG_BG0HOFS、REG_BG0VOFSは何なのかちょっと気になるところです。これは256x256のマップサイズから、GBAの液晶サイズ240x160分の始点をどこにするか決めるものです。ぜひエミュレータで実行してみてください。