タイルモードは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 |
キャラクタデータの並び方を決めたものがマップデータです。
2バイト(16ビット)単位であり、少々のオプションも含まれています。
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と書かれているところにキャラクタ番号を使います。
データについては自前に用意したものを使います。
#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, (以下略・・・)
さて、今回は慎重に「モード0 256色タイル」のオプションと出力結果を見てみましょう。
-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
キャラクタデータは256色のため、64*16*1 = 1024バイト。
パレットデータも同様に256色のため、256*2 = 512バイトとなっています。
スプライトの場合、キャラクタとマップデータを格納する場所は
固定されおり、単純なコピーで済んでいました。
ところでタイルモードは可変であり、自分で格納する場所を
指定しなくてはいけません。対応表を以下に表します。
アドレス | マップ | キャラクタ |
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);
今回はこのようにGBAの設定を行っています。つまり
タイルデータは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 }; s32 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) { u32 i; for(i=0; i<size; i++) { Bg[0].tileBaseAdr[i] = pDat[i]; } } //--------------------------------------------------------------------------- void Bg0SetPal(u16* pDat) { u32 i; for(i=0; i<256; i++) { BG_PALETTE[i] = pDat[i]; } } //--------------------------------------------------------------------------- void Bg0SetMap(u16* pDat, u32 size) { u32 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分の始点を
どこにするか決めるものです。
ぜひエミュレータで実行してみてください。
#ref(): File not found: "clip_1.png" at page "tutorial.10"
#ref(): File not found: "clip_3.png" at page "tutorial.10"