情報システム実験 K-13 組み込みオペレーティングシステム No.7
前回は1つあるいは2つのブロックを表示する、小さなブロック崩しを 作りました。今回は、たくさん並んだブロックを扱えるようにして、 ブロック崩しを仕上げましょう。
ブロックの横の列の数をBLOCK_COLS、 縦の行の数をBLOCK_ROWSとします。 タテヨコに並んだBLOCK_COLS * BLOCK_ROWS個のブロックを表現するには、 block.c の中に 以下のようなデータを持っておいて、START状態で初期化するようにすれば よいでしょう。
BLOCK_COLSは、画面の幅 LCD_WIDTH を割り切る値にするのが良いでしょう。 その他、以下のような定数を決めておくと良いでしょう。
boxes[i][j]の位置は、(i * BLOCK_WIDTH, BLOCK_TOP + j * BLOCK_HEIGHT)となります。 もしブロックの間に間隔を空けたければ、上下左右1ドットずつ小さくしたboxを作るようにすれば良いでしょう。
また、num_blocks の初期値は (BLOCK_COLS * BLOCK_ROWS) となります。
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)を作っておくと良いでしょう。
ここで、
さて、ボールと衝突したブロックは消えるわけですが、 衝突した後のボールの向きはどうすればよいでしょうか? 以下に一部の例を示すとおり、 ボールとブロックの衝突の仕方にはいろいろな場合があります。
いかに簡単な(高速な)プログラムで、プレイヤーから見て自然に反射させるか 工夫のしがいがあるところです。 ここでは一つの考え方を説明しますが、これが絶対と言うわけではありません。 (これではうまくいかない場合もあります。)
まず上下について反射するかどうかを調べる変数updownを用意し、 初期値を0とします。そして、ボールの左上隅、右上隅、左下隅、右下隅の4箇所についてhit()で調べたとき、
左右方向に対してもleftrightという変数で調べて同様に判定します。 つまり、左上隅が当たったならばupdownとleftrightを1増やし、 左下隅が当たったならばupdownを減らしてleftrightを増やす、というように して、最後にupdownでy方向の速度を、leftrightでx方向の速度を決めるわけです。
このとき注意することは、hit()で四隅を調べ終るまでブロックを 消してはいけないということです。たとえば下の図のような場合、
ボールの左上隅を調べた時にブロックを消して、 その後で右上隅をhitで調べると、 右上隅は「当たっていない」と判定してしまいます。 すると、正しくは「y方向の速度だけ反転してx方向はそのまま」にしなければいけないのに、 「y方向もx方向も反転」してしまうことになります。 したがって、ボールの反射方向を判定した後で、あらためて四隅のブロックを 消す処理を行ないます。当たったかどうか判定するhit()の他に、 当たったならばブロックを消すdelete()という関数を作るとよいでしょう。
このやり方の他、cross()が単に「重なったかどうか」を返すだけでなく、 2つのboxの位置関係(「b2の方が右にある」「上にある」等)を返すようにするとか、 ブロックの「どの辺に当たったか」を判定するなど、いろいろな方法が考えられます。 ここで説明したよりもうまい方法を思いついたらぜひ教えて下さい。
以上でブロック崩しの材料は全て揃いました。 ブロックを消すごとにnum_blocksを減らしていき、0になったらCLEAR状態に 遷移するようにすれば、ひとまず完成です。
これで最低限「ブロック崩し」と呼べるものができたわけですが、 今後はさらに改良したり、機能を増やしたりしていきましょう。 ここでは手始めに、上で説明した考え方を応用して、ラケットにあたったボールの反射を より自然にすることを考えてみましょう。
ラケットのどこにボールが当たっても、単にy方向に跳ね返るだけというのは 少しつまらないので、たとえばラケットの端に当たったらx方向の速度も変わる ようにするにはどうすれば良いか考えてみましょう。 これもいろいろな考え方ができますが、たとえばボールが十分大きい(16ドットくらいある)ならば、 cross()でラケットとボールが重なっていると判定された時、 ボールの方が右にはみ出している(ボールの右端がラケットの右端より右にある) ならばx方向の速度を正にし、 左にはみ出しているならばx方向の速度を負にするというやり方で、 プレイヤーにとってより面白みのあるゲームになるでしょう。
ボールが小さいと、ボールがはみ出すようにラケットに当てるのが難しすぎるので、 たとえば「ラケットの右端からある範囲にボールの左端があれば」のような 判定の方がよいでしょう。