情報システム実験 K-13 組み込みオペレーティングシステム No.8
前回まででブロック崩しゲームは一応の完成を見たわけですが、 さらに改良してみましょう。
第3週の資料で説明したwait()やdelay()による 速度の調整では、速度が完全に一定にはなりません。
while (1) {
ゲームの処理
時間Tだけ待つ
}
という方法では、上の図のように、ループを回る間隔が処理の長さによって
変わってしまい、Tにはなりません。そのため、キーを押してラケットを移動する時に
キーを押さない時よりボールのスピードが遅くなったりします。
そこで、タイマの使い方を少し変えて、
void wait_until(hword time)
{
while (time != gba_register(TMR_COUNT0))
;
}
int main(void) {
...
while (1) {
hword begin = gba_register(TMR_COUNT0); // 処理前の時間をbeginに記録
処理
wait_until(begin + T); // begin + Tになるまで待つ
}
...
}
のようにすれば、whileループの内部の時間を一定にできます。
このとき、現状のdelay()は、
void delay(hword val)
{
wait_until(gba_register(TMR_COUNT0) + val);
}
と書けます。
ラケットを移動する際に、ラケットがちらちらと点滅して見えてしまうことがあります。 ちらつく原因は、「消して書き直す」動作、すなわち黒く塗りつぶしてから ラケットを書き直す動作が目に見えてしまうからです。 書き直す時間間隔(wait()で待つ時間)を変えることであるていど軽減できますが、 完全に解決するには次のような方法があります。
GBAは、VRAMに書かれた内容を一定の間隔で液晶画面に表示しています。 これを「画面のリフレッシュ」と呼びます。その繰り返しのうちには、 VRAMを画面に表示している時間(上の図の"screen refresh")の他に、 「垂直ブランク期間」と呼ばれる画面表示を行なわない時間 (上の図の"vertical blank")があります(参考図書p.88, p.99〜100)。
この垂直ブランク期間にVRAMの書き換え(黒での塗りつぶしと書き直し) を行なってやれば、ちらつきが目に見えることはありません。
垂直ブランク期間に合わせてVRAMの書き換えを行なうには、 「割り込み処理」を用いる方法もありますが、LCDステータスレジスタ(p.100の図14)の ビット00(垂直ブランク期間なら1、画面をリフレッシュ中なら0) を見てタイミングを合わせるのが最も簡単です。
void wait_until_vblank(void) {
while ((gba_register(LCD_STATUS) & 1) == 0)
;
}
void wait_while_vblank(void) {
while ((gba_register(LCD_STATUS) & 1))
;
}
int main(void)
{
...
while (1) {
wait_until_vblank(); // 垂直ブランク期間になるまで待つ
処理
wait_while_vblank(); // 垂直ブランク期間が終るまで待つ
}
}
上のプログラムでは、垂直ブランク期間が始まるタイミングに合わせて
ゲームの処理を開始するようにしています。
そして、処理が早く終ったときにはwait_while_vblank()で
垂直ブランク期間が終るまで待つようにしています。
(このwait_while_vblank()の呼び出しがないと、
1回の垂直ブランク期間中に何度もwhileループを回ってしまう可能性が
あります。)
このようにすると、処理が 3.7ms 以内で終るならば、 ちらつきは全く見えなくなります。 また、ループを回る間隔も一定にできます。 ただし、間隔は垂直ブランク間隔(約1/60秒)に固定されてしまい、 自由に速度を調整することはできなくなります。
ゲームを面白くするために、もっと機能を付け加えることも考えられます。 たとえば、点数やハイスコアを表示するのも良いでしょう。
参考書第3章(p.53)に説明のあるprintf()関数(~kumikomios/CDROM/chapter-3/printf.{c,h}) を利用すると、文字列や数値を簡単に表示できます。 ただし、第3章の説明は、我々が利用しているグラフィックモード3ではなく、 グラフィックモード0を前提として説明されているので注意が必要です。 printf()からは1文字を画面に書くputchar()関数が呼び出されていますが、 この章で説明されているputchar()関数はモード3では使えません。
グラフィックモード3でprintf()が動くようにするには、 第2週で説明したdraw_char()関数を元にして putchar()関数を自分で作る必要があります。 また、文字を書く位置を指定するlocate()関数も必要になるでしょう。
ファイルconsole.hを
void locate(int x, int y); void putchar(int c);のように作り、これらの関数の定義をconsole.cに書いてやれば良いでしょう。 console.cの中では「次に文字を書く位置」をstatic変数に覚えておき、 putchar()はdraw_char()を呼び出して覚えておいた位置から文字を書いて、 位置を1文字分すすめるようにすればよいでしょう。 改行('\n')や、画面からはみ出した時の処理は、とりあえず考えなくても良いでしょう。
その他、以下のような機能拡張が考えられます。
ブロック崩しで学んだ技術を使えば、テトリスやシューティングゲームなどの 他のアクションゲームや、マインスイーパー、倉庫番などのリアルタイムでない ゲームも作ることができるでしょう。また、実験資料では説明しませんでしたが 参考書籍の内容を理解すれば、サウンドを出したり、スプライトを用いてより高速な 画面表示を行なうこともできます。
ぜひチャレンジしてみて下さい。