タイルモード1

タイルモードはmode0~2に該当します。モード0の動作画面から見てみましょう。なお結構難しいと思うので何日か分けて取り組んでみてください。

Mode名称
0-2タイルモード
3-5ビットマップモード
 
1.png

何やらRPGの洞窟風景に見えますね。これらを表示するには以下の画像データを使います。

2.png

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ドット=1byte64byte

こちらもスプライトの時に使った表と同じです。単位は1キャラクタ8x8ドットです。たとえば壁(16x16ドット)のキャラクタ番号は、6, 7, 14, 15となっています。

67
1415
 
4.png

マップデータ

キャラクタデータの並び方を決めたものがマップデータです。16色の場合は2バイト(16ビット)単位であり、少々のオプションも含まれています。

Tile Numberと書かれている0-9Bit目にキャラクタ番号を使います。マップデータは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による加工

今回は慎重にgritのモード0 256色タイルオプションと出力結果を見てみましょう。キャラクタデータは256色のため64*16*1 = 1024バイト。パレットデータも同様に256色のため、256*2 = 512バイトとなっています。

格納領域を決める

スプライトの場合、キャラクタとマップデータを格納する場所は固定されおり、単純なコピーで済んでいました。ところでタイルモードは可変であり、自分で格納する場所を指定しなくてはいけません。対応表を以下に表します。

マップは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

REG_BGxCNTの内容をGBATEKから抜粋してみましょう。

BG0~3というのは画面(back ground)の意味です。なぜ可変として面倒なことをしていたか、理由はここにあります。GBA(モード0)はマップデータを最大4つ持つことができ、重ね合わせ表示が可能になっています。また、14-15ビット目を見ると256x256~512x512ドットに変えることも可能です。先ほどの対応表の区切りをもう一度見てください。マップは2K=2048(0x800)バイト単位でした。512x512をしたい場合は8Kとなり、区切り4つ分が必要になります。個人的にはスクロール処理が必要ない規模ならBGの大きさ指定は有功でしょう。RPGを作るのならスクロール処理は必須なので256x256一択になるのではないかと思います。

モード0で、BG0のみを使用する

覚えることが大量に出てきたところでいったんクールダウンしましょう。とりあえず先ほどの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分の始点をどこにするか決めるものです。ぜひエミュレータで実行してみてください。

動作画面とデバッグ

1.png
3.png

履歴


トップ   一覧 検索 最終更新   ヘルプ   最終更新のRSS