GBA開発Wiki
チュートリアルの最初で少し解説しましたVBlankとHBlankについて再度解説していこうと思います。HBlank期間を利用することで特殊な画面効果を実際にやってみましょう。
GBAの液晶画面は1秒間に約60回(59.727... hz)更新されます。1回の画面更新では、上のラインから1ラインずつ更新していき、画面を更新する160ライン分の期間と、直接描画は行わない68ライン分のブランク期間(VBlank)があります。通常のフレームごとの処理は画面のチラつき防止のためVBlank期間に行います。

画面を更新する1ラインごとの期間のうち、横1ラインを描画する期間が960サイクル、描画を行わない期間が272サイクル(HBlank)あります。1フレームの処理内容で縦ラインが160+68=228ライン、1ラインの処理が960+272=1232サイクルで1フレームあたりのサイクル数が228×1232=280896サイクルとなり、GBAのCPUは16.78MHz=16777216サイクルなので16777216/280896=59.727... hzとなり、ほぼ計算が合うことになります。
The drawing time for each dot is 4 CPU cycles. Visible 240 dots, 57.221 us, 960 cycles - 78% of h-time H-Blanking 68 dots, 16.212 us, 272 cycles - 22% of h-time Total 308 dots, 73.433 us, 1232 cycles - ca. 13.620 kHz
Visible (*) 160 lines, 11.749 ms, 197120 cycles - 70% of v-time V-Blanking 68 lines, 4.994 ms, 83776 cycles - 30% of v-time Total 228 lines, 16.743 ms, 280896 cycles - ca. 59.737 Hz
横1ラインの描画時に、横1ライン分のドットの色をBGやスプライト、
拡大縮小回転、モザイクなどの特殊効果を考慮して描画されるのですが、
1ラインの描画が終わって次のラインを描画するまでのブランク期間(HBlank)に、
BGの表示位置などを変更することによって通常の描画では行えないような特殊な画面効果を出すことができます。
具体的にはHBlank中に横方向のスクロール値を変更したりします。
このことを特にラスタースクロールと言いいます。
ラスタースクロール効果を出すには、HBlank中にBGのスクロール値を変更することが必要です。
HBlank期間を判定する方法としては、割り込みのHBlank割り込みを使うことができます。
また、現在描画中のライン番号(0~227)を取得することも必要です。
これは、I/OレジスタのREG_VCOUNT(0x4000006)の値を取得することでできます。
#include "lib/gba.h"
#include "sintbl.h"
#include "res.h"
#define SHAKE_CYCLE 4 // 横揺れの周期
#define SHAKE_CX 64 // 横振れの幅
//---------------------------------------------------------------------------
volatile s32 angle; // 横揺れの1周期内の位置
//---------------------------------------------------------------------------
void WaitForVsync(void)
{
while (*(volatile u16*)0x4000006 >= 160) {};
while (*(volatile u16*)0x4000006 < 160) {};
}
//---------------------------------------------------------------------------
void IrqHandler(void)
{
REG_IME = 0;
u16 flag = REG_IF;
REG_IF = flag;
if(flag & IRQ_HBLANK)
{
if(REG_VCOUNT < 160)
{
// 横方向を揺らす
REG_BG0HOFS = GetSin(angle * SHAKE_CYCLE + REG_VCOUNT) * SHAKE_CX / 256;
}
else if(REG_VCOUNT == 227)
{
// 次のフレームのVCOUNT 0用
REG_BG0HOFS = GetSin(angle * SHAKE_CYCLE + 0) * SHAKE_CX / 256;
}
}
REG_IME = 1;
}
//---------------------------------------------------------------------------
void IrqSetup(void)
{
REG_IME = 0;
INT_VECTOR = (u32)IrqHandler;
REG_DISPSTAT = LCDC_HBL;
REG_IE = IRQ_HBLANK;
REG_IME = 1;
}
//---------------------------------------------------------------------------
int main(void)
{
// モード設定
SetMode(MODE_0 | BG0_ENABLE);
REG_BG0CNT = (BG_SIZE_0 | BG_256_COLOR | CHAR_BASE(0) | MAP_BASE(28));
u16* pal = BG_PALETTE; // BGパレット
u16* chr = CHAR_BASE_ADR(0); // キャラクタデータ
u16* map = MAP_BASE_ADR(28); // マップデータ
u32 i;
// パレット設定
for(i=0; i<256; i++)
{
pal[i] = imagePal[i];
}
// キャラクタ格納
for(i=0; i<(240*160)/2; i++)
{
chr[i] = imageTiles[i];
}
// マップ格納
i = 0;
u32 x, y;
for(y=0; y<20; y++)
{
for(x=0; x<30; x++)
{
map[y*32+x] = i++;
}
}
angle = 0;
IrqSetup();
for(;;)
{
WaitForVsync();
if(++angle == (360 / SHAKE_CYCLE))
{
angle = 0;
}
}
}
// 横方向を揺らす REG_BG0HOFS = GetSin(angle * SHAKE_CYCLE + REG_VCOUNT) * SHAKE_CX / 256;
そのラインの左右のスクロール値を決定している部分です。
GetSin(angle * SHAKE_CYCLE + REG_VCOUNT)の部分は、現在のフレーム数を元にsinの値を取得します。
サインテーブルは360で1周期となっているので、angleにSHAKE_CYCLEをかけて360で1周期になるようにします。
縦の1ラインごとにスクロール値を変更する必要がある(これをしないとラスタースクロールにならない)ので、
REG_VCOUNTで現在のライン番号を取得してラインごとにスクロール値を変更しています。
* SHAKE_CX / 256
はゆれ幅の調整部分です。
GetSin()で取得する値は-256~256の範囲なので、-SHAKE_CX~SHAKE_CXの範囲になるように調節します。
