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

多数のブロックを表すデータ

前回は1つあるいは2つのブロックを表示する、小さなブロック崩しを 作りました。今回は、たくさん並んだブロックを扱えるようにして、 ブロック崩しを仕上げましょう。

ブロックの横の列の数をBLOCK_COLS、 縦の行の数をBLOCK_ROWSとします。 タテヨコに並んだBLOCK_COLS * BLOCK_ROWS個のブロックを表現するには、 block.c の中に 以下のようなデータを持っておいて、START状態で初期化するようにすれば よいでしょう。

static struct box boxes[BLOCK_COLS][BLOCK_ROWS]
ブロックを画面上で表示するboxの二次元配列
static char flags[BLOCK_COLS][BLOCK_ROWS]
ブロックが表示されているか、ボールが当たって消えているかの状態を 表す二次元配列(たとえば、表示中なら1, 消えていれば0)
static int num_blocks
残りブロック数

BLOCK_COLSは、画面の幅 LCD_WIDTH を割り切る値にするのが良いでしょう。 その他、以下のような定数を決めておくと良いでしょう。

BLOCK_TOP
ブロックの最上行を表示するY座標
BLOCK_WIDTH
1個のブロックの幅 (LCD_WIDTH / BLOCK_COLS)
BLOCK_HEIGHT
1個のブロックの高さ

boxes[i][j]の位置は、(i * BLOCK_WIDTH, BLOCK_TOP + j * BLOCK_HEIGHT)となります。 もしブロックの間に間隔を空けたければ、上下左右1ドットずつ小さくしたboxを作るようにすれば良いでしょう。

また、num_blocks の初期値は (BLOCK_COLS * BLOCK_ROWS) となります。

geometry of blocks

練習

ボールに当たる可能性のあるブロック

blockタスクの中で、ボールがブロックのいずれかと衝突したかどうかを判定するには、 cross()関数を使って調べればできますが、すべてのブロックとの衝突を調べるのは 明らかにムダです。

点(x, y)にあるブロックの番号(i, j)は、

  i = x / BLOCK_WIDTH
  j = (y - BLOCK_TOP) / BLOCK_HEIGHT
  (いずれもあまりは切捨て)
で簡単に調べることができます。 flags[i][j]が0でなければ、(x, y)には まだ消えていないブロックがあるということになります。 ただし、もしiやjが負になったり、 BLOCK_COLS, BLOCK_ROWS以上の値になれば、(x, y)はどのブロックの中にもありません。 点(x, y)に、まだ消えていないブロックがあるかどうか調べる関数
  static int hit(int x, int y)
を作っておくと良いでしょう。

ここで、

と仮定すると、RUNNING状態の1ステップではボールの四隅の点の場所にある ブロックだけをhit()で調べれば十分です。

練習

ブロックにあたったボールの反射

さて、ボールと衝突したブロックは消えるわけですが、 衝突した後のボールの向きはどうすればよいでしょうか? 以下に一部の例を示すとおり、 ボールとブロックの衝突の仕方にはいろいろな場合があります。

hit patterns

いかに簡単な(高速な)プログラムで、プレイヤーから見て自然に反射させるか 工夫のしがいがあるところです。 ここでは一つの考え方を説明しますが、これが絶対と言うわけではありません。 (これではうまくいかない場合もあります。)

まず上下について反射するかどうかを調べる変数updownを用意し、 初期値を0とします。そして、ボールの左上隅、右上隅、左下隅、右下隅の4箇所についてhit()で調べたとき、

ようにします。最終的に集計した結果、 ようにします。

左右方向に対してもleftrightという変数で調べて同様に判定します。 つまり、左上隅が当たったならばupdownとleftrightを1増やし、 左下隅が当たったならばupdownを減らしてleftrightを増やす、というように して、最後にupdownでy方向の速度を、leftrightでx方向の速度を決めるわけです。

このとき注意することは、hit()で四隅を調べ終るまでブロックを 消してはいけないということです。たとえば下の図のような場合、

hit pattern

ボールの左上隅を調べた時にブロックを消して、 その後で右上隅をhitで調べると、 右上隅は「当たっていない」と判定してしまいます。 すると、正しくは「y方向の速度だけ反転してx方向はそのまま」にしなければいけないのに、 「y方向もx方向も反転」してしまうことになります。 したがって、ボールの反射方向を判定した後で、あらためて四隅のブロックを 消す処理を行ないます。当たったかどうか判定するhit()の他に、 当たったならばブロックを消すdelete()という関数を作るとよいでしょう。

このやり方の他、cross()が単に「重なったかどうか」を返すだけでなく、 2つのboxの位置関係(「b2の方が右にある」「上にある」等)を返すようにするとか、 ブロックの「どの辺に当たったか」を判定するなど、いろいろな方法が考えられます。 ここで説明したよりもうまい方法を思いついたらぜひ教えて下さい。

以上でブロック崩しの材料は全て揃いました。 ブロックを消すごとにnum_blocksを減らしていき、0になったらCLEAR状態に 遷移するようにすれば、ひとまず完成です。

練習

ヒント

ラケットにあたったボールの反射の改良

これで最低限「ブロック崩し」と呼べるものができたわけですが、 今後はさらに改良したり、機能を増やしたりしていきましょう。 ここでは手始めに、上で説明した考え方を応用して、ラケットにあたったボールの反射を より自然にすることを考えてみましょう。

ラケットのどこにボールが当たっても、単にy方向に跳ね返るだけというのは 少しつまらないので、たとえばラケットの端に当たったらx方向の速度も変わる ようにするにはどうすれば良いか考えてみましょう。 これもいろいろな考え方ができますが、たとえばボールが十分大きい(16ドットくらいある)ならば、 cross()でラケットとボールが重なっていると判定された時、 ボールの方が右にはみ出している(ボールの右端がラケットの右端より右にある) ならばx方向の速度を正にし、 左にはみ出しているならばx方向の速度を負にするというやり方で、 プレイヤーにとってより面白みのあるゲームになるでしょう。

ボールが小さいと、ボールがはみ出すようにラケットに当てるのが難しすぎるので、 たとえば「ラケットの右端からある範囲にボールの左端があれば」のような 判定の方がよいでしょう。

練習