かつてマイコン少年だったころ、インベーダーゲームで強く印象に残った処理が2つありました。
(1)グラフィカルな敵が多数動いている。処理が大変そう
(2)陣地が敵弾にだんだん崩される様子がリアル。あらかじめ用意した画像を使っているのではなさそう
今回は、このうち(1)のインベーダーの移動処理を扱います。
インベーダー群は、左右に行進しつつ、インベーダーが画面の端まで行くと、一段下へ移動します。
画面最下端へインベーダーが到達したらゲームオーバーです。
従って、移動処理では
(1)画面左右端に到達したインベーダーが居るかどうかの判定
(2)画面最下端に到達したインベーダーが居るかどうかの判定
を行う必要があります。
また、画面の端まで到達できるのは生きているインベーダーだけなので、これらの判定処理は、全ての(生きている)インベーダーについて行う必要があります。
また、「どんなゲームだったのか」で書いたとおり、インベーダーは一度に一匹しか動きません。
これらのことを踏まえ、今回はインベーダーの移動処理を以下のように実装しました。
(1)インベーダーの座標は、全体を一括して扱う(一匹ごとの座標は持たない)
(2)インベーダーの生死はフラグ(の配列)で判定する(ブロック崩しと同じ)
(3)インベーダーを一匹移動させる都度、画面左右下端への到達をチェックする
(4)インベーダーがすべて移動し終わったときに、行進方向の変更や下降の処理を行うかどうか判定する
なお、(1)のインベーダーの座標は全体の「現在位置」「次に動くべき位置」で表し、個別のインベーダーの位置は「次に動かすインベーダーの番号」で表現します。
図にすると下のようになります。この図ではインベーダーは右から左へ移動しています。
インベーダー全体としては(cur_left, cur_top)から(nxt_left, nxt_top)へ移動するのですが、実際には0番~(nxt_update -1)番までが移動済みで、nxt_update版~49番までが現在位置に留まっています。
インベーダーの番号は左下が0番で、右へ進むごとに+1、最後の49番は右上のインベーダーです。
オリジナルのインベーダーゲームの映像をよく見ると、最初に動き出すインベーダーは左下で、そこから右隣が順次移動し、次は下から2列目の左端から動き始めていますので、そのような順で番号を振りました。
インベーダーが端に到達したかどうかは、インベーダーを1匹移動させるごとに、そのときの描画座標でチェックします。
(オリジナルのインベーダーでは、画面の端にドットがあるかどうかで判定していたらしいです。)
コードはGitHubで公開していますが、上の説明に対応する部分を載せておきます。
まずインベーダーを表すデータ構造です。
struct aliens_t { boolean exist[ALN_NUM]; uint8_t alive; // number of aliens alive int16_t cur_left; int16_t cur_top; int16_t nxt_left; int16_t nxt_top; int16_t v; // left move or right move uint8_t nxt_update; // which invader will be moved at the next tick uint8_t pose; // index num for iv?_img[] int16_t bottom; // be updated in aliens_draw boolean move_down; // down-move flag (updated in aliens_draw) boolean touch_down; // an alien touched bottom line flag int8_t status; uint8_t hit_idx; // which invader is been hit } g_aliens;
そして、移動ルーチンが以下です。
動かすべきインベーダーが見つかれば(最初のif文)、その番号をnxt_updateに代入して終わりです。
49番まで行っても見つからなければ、生存しているすべてのインベーダーを移動し終えたので、最下端に到達したか(touch_down)の確認、X座標の更新、左右端へ到達した場合はY座標の更新を行います。
void aliens_move(struct aliens_t *a) { uint8_t u = a->nxt_update; do { u++; } while ((u < ALN_NUM) && (!a->exist[u])); if (u < ALN_NUM) { a->nxt_update = u; } else { // all aliens have moved if (a->bottom >= SCRN_BOTTOM) a->touch_down = true; for (u = 0; (u < ALN_NUM) && (!a->exist[u]) ; u++); a->nxt_update = u; a->pose = (a->pose + 1) % 2; a->cur_left = a->nxt_left; a->nxt_left += a->v; a->cur_top = a->nxt_top; if (a->move_down) { // this flag had been set in aliens_draw() a->move_down = false; a->v = -a->v; a->nxt_top = a->cur_top + ALN_VMOVE; a->nxt_left = a->cur_left + a->v; } } }
一匹のインベーダーを描画する処理は以下の通りです。ほとんど3種類を描き分ける処理だけですが、最後のところで左右端への到達判定(move_down)と、インベーダー群の中で最大(最下端)のY座標の保存(bottom)を行っています。
void aliens_draw(struct aliens_t *a) { if (!a->exist[a->nxt_update]) return; uint8_t row = A_ROW(a->nxt_update); uint8_t col = A_COL(a->nxt_update); int16_t x = a->nxt_left + col * (ALN_W + ALN_HSPACING); int16_t y = a->nxt_top + row * (ALN_H + ALN_VSPACING); uint8_t *img; switch (row) { case 0: img = (uint8_t *)aln1_img[a->pose]; break; case 1: case 2: img = (uint8_t *)aln2_img[a->pose]; break; case 3: case 4: img = (uint8_t *)aln3_img[a->pose]; break; } DRAW_BITMAP(x, y, img, ALN_W, ALN_H, WHITE); if (((x <= SCRN_LEFT) && (a->v < 0)) || ((x >= SCRN_RIGHT - ALN_W) && (a->v > 0))) a->move_down = true; a->bottom = max(a->bottom, y + ALN_H); }
コメント