ウィンドウとDMA HBLANK機能を使ったサンプルコードです。メトロイド序盤で表示されるライトをほぼ再現しています。
構成は以下の通りです。mode0で全て16色。bg0:ASCII文字(256x256)、bg1:マスク(256x256)、bg2:ステージ(512x256)となっておりステージのみ512x256です。
REG_BG2HOFS = 120; REG_BG2VOFS = 64; REG_WIN0H = 0; REG_WIN0V = SCREEN_HEIGHT; REG_WININ = WIN_0_BG0 | WIN_0_BG2; REG_WINOUT = WIN_0_BG0 | WIN_0_BG1;
ステージ表示には始点座標を変えています。さらにウィンドウの内部と外部にどれを表示させるかの設定を加えています。
/* Create an array of horizontal offsets for a circular window.
* The offsets are to be copied to REG_WINxH each HBlank, either
* by HDMA or HBlank isr. Offsets provided by modified
* Bresenham's circle routine (of course); the clipping code is not
* optional.
*
* \param x0 X-coord of circle center.
* \param y0 Y-coord of circle center.
* \param rr Circle radius.
*/
u16 BgWinh[SCREEN_HEIGHT+1] ALIGN(4);
IWRAM_CODE void BgCreateWindowCircleDma(s32 x0, s32 y0, s32 rr)
{
// Zero clear
for(vs32 i=0; i<SCREEN_HEIGHT+1; i++)
{
BgWinh[i] = 0;
}
s32 x=0, y=rr, d=1-rr;
u32 tmp;
while(y >= x)
{
// Side octs
tmp = BgClamp(x0 + y, 0, SCREEN_WIDTH+1);
tmp += BgClamp(x0 - y, 0, SCREEN_WIDTH+1) << 8;
// o4, o7
if(BgInRange(y0-x, 0, SCREEN_HEIGHT))
{
BgWinh[y0 - x]= tmp;
}
// o0, o3
if(BgInRange(y0+x, 0, SCREEN_HEIGHT))
{
BgWinh[y0 + x]= tmp;
}
// Change in y: top/bottom octs
if(d >= 0)
{
tmp = BgClamp(x0 + x, 0, SCREEN_WIDTH+1);
tmp += BgClamp(x0 - x, 0, SCREEN_WIDTH+1) << 8;
// o5, o6
if(BgInRange(y0-y, 0, SCREEN_HEIGHT))
{
BgWinh[y0 - y]= tmp;
}
// o1, o2
if(BgInRange(y0+y, 0, SCREEN_HEIGHT))
{
BgWinh[y0 + y]= tmp;
}
d -= 2 * (--y);
}
d += 2 * (x++) + 3;
}
/*
for(vs32 i=0; i<SCREEN_HEIGHT+1; i++)
{
TRACE("%d: %4x\n", i, BgWinh[i]);
}
for(;;){}
*/
REG_DMA3CNT = 0;
REG_DMA3SAD = (u32)&BgWinh[1];
REG_DMA3DAD = (u32)®_WIN0H;
REG_DMA3CNT = 1 | (DMA_DST_RELOAD | DMA_REPEAT | DMA_HBLANK | DMA_ENABLE);
}
//---------------------------------------------------------------------------
IWRAM_CODE s32 BgClamp(s32 val, s32 min, s32 max)
{
if(val < min)
{
return min;
}
if(val > max)
{
return max;
}
return val;
}
//---------------------------------------------------------------------------
IWRAM_CODE bool BgInRange(s32 x, s32 min, s32 max)
{
return ((x)>=(min)) && ((x)<(max)) ? TRUE : FALSE;
}
注目してほしいのは、以下の部分でHBLANK毎にDMAを発生させていることです。GBAの画面の高さは160ですので160回HBLANKを発生させています。1ライン分のウィンドウサイズを2バイト、REG_WIN0Hに格納しています。チラツキもなくハードウェア機能で実現しており非常に芸術点が高いです。
REG_DMA3CNT = 0; REG_DMA3SAD = (u32)&BgWinh[1]; REG_DMA3DAD = (u32)®_WIN0H; REG_DMA3CNT = 1 | (DMA_DST_RELOAD | DMA_REPEAT | DMA_HBLANK | DMA_ENABLE);