情報システム実験 K-13 組み込みオペレーティングシステム
No.8

動作の改善

前回まででブロック崩しゲームは一応の完成を見たわけですが、 さらに改良してみましょう。

時間間隔を一定に

第3週の資料で説明したwait()やdelay()による 速度の調整では、速度が完全に一定にはなりません。

interval

  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()で待つ時間)を変えることであるていど軽減できますが、 完全に解決するには次のような方法があります。

video refresh

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')や、画面からはみ出した時の処理は、とりあえず考えなくても良いでしょう。

その他、以下のような機能拡張が考えられます。

練習

上のいずれかの機能拡張を加えてみましょう。

別のゲーム

ブロック崩しで学んだ技術を使えば、テトリスやシューティングゲームなどの 他のアクションゲームや、マインスイーパー、倉庫番などのリアルタイムでない ゲームも作ることができるでしょう。また、実験資料では説明しませんでしたが 参考書籍の内容を理解すれば、サウンドを出したり、スプライトを用いてより高速な 画面表示を行なうこともできます。

ぜひチャレンジしてみて下さい。