9-4 命中判定をしよう

「インベーダー」フォームの「命中判定」ボタンに命中判定させるようにしましょう。プログラムは次のようになります。

// 「命中判定」ボタン
procedure TFormInvader.ButtonHitCheckClick(Sender: TObject);
begin
  if (FormBullet.CheckBoxAlive.Checked = True) and
      (abs(SpinEditX.Value -100) <= 10) and
      (abs(SpinEditY.Value - FormBullet.SpinEditY.Value) <= 10)
    then // 弾が飛んでいて、しかも X, Y 座標ともに10以内の位置にあれば
      begin
        FormBullet.ButtonDestClick(Sender); // 弾の「終了処理」
        ButtonDestClick(Sender); // 自分自身の「終了処理」
      end
  ;
end;

これは、インベーダーの中心から上下左右10ドット以内に弾の先端が入っていたら命中と判定し、インベーダーと弾の両方を消す、という設定になっています。

当たり判定の図

なお、この「命中判定」ボタンは、インベーダーが移動するたびに「命中したかどうか」を判定されなければなりません。そこで、「移動」ボタンが押されたときの処理の最後に「命中判定」ボタンが押される (ButtonHitCheckClick が呼び出される) ようにします。プログラムを次のように変更してください。(下線部が変更部分です。)

// 「移動」ボタン
procedure TFormInvader.ButtonMoveClick(Sender: TObject);
begin
  if (CheckBoxAlive.Checked = True)
    then // もし「動いている」なら
      begin
        if (SpinEditX.Value + SpinEditSpeed.Value > 180)
            or (SpinEditX.Value + SpinEditSpeed.Value < 20)
          then // もし画面の端にいれば
            begin
              ButtonGoDownClick(Sender); // 「下移動」ボタンを押す
              SpinEditSpeed.Value := -SpinEditSpeed.Value; // 速度反転
            end
          else // もし画面の端以外の場所にいれば
            begin
              ButtonGoHorizontalClick(Sender); // 「左右移動」ボタンを押す
            end
        ;
        ButtonHitCheckClick(Sender); // 「命中判定」ボタンを押す
      end
  ;
end;

また、操作盤上のコンポーネントを操作するだけでゲームできるように (操作盤以外のフォームには触れなくても良いように)、操作盤の「弾発射」ボタンを押すと弾フォームの「初期処理」ボタンを押したことになるようプログラミングします。

// 「弾発射」ボタン
procedure TFormControl.ButtonFireClick(Sender: TObject);
begin
  FormBullet.ButtonConstClick(Sender); // 弾フォームの「初期処理」ボタンを押す
end;

コラム 弾フォームの「初期処理」ボタンは無駄ではないの?

ここでプログラミングした、操作盤の「弾発射」ボタンの機能は、弾フォームの「初期処理」ボタンを押す (ButtonFireClick を呼び出す)だけ です。「それなら、最初から弾フォームには『初期処理』ボタンなんかつけずに、操作盤の『弾発射』ボタンに処理を書いておけばいいのではないか?」という疑問を感じた人もいるのではないでしょうか。もっともな疑問ですね。

この仕組みは一見無駄に見えますが、実は、「弾フォームをまとまりあるものとする」という意義があるのです。こうしておけば、例えば弾を発射したときに発射音を出すようにする、あるいは弾の発射位置を変える等の変更は弾フォームのみを改良すれば良く、操作盤をいじる必要がなくなります。つまり、修正の対象が限定されるので保守管理 (修正や改良等のことです) が容易になるのです。このように、弾やインベーダー等、ある“もの”の属性や操作に関するものをひとまりにしておくと、プログラムの管理や (他のプログラムへの) 再利用が容易になることがよく知られています。

ところで、この“ひとまとまりになったもの”が実はオブジェクトなのです。本章では「インベーダー」オブジェクトや「弾」オブジェクト等を作り、それらを組み合わせることで、インベーダーゲームを作っています。皆は知らず知らずのうちに、本格的なオブジェクト指向プログラミングの入り口に差し掛かっているのです。

インベーダーフォームの「初期処理」を変更して、インベーダーの出現位置を乱数で決めるようにしましょう。「初期処理」ボタンのイベントハンドラを次のように変更してください (下線部が変更部分です)。

// 「初期処理」ボタン
procedure TFormInvader.ButtonConstClick(Sender: TObject);
begin
  ButtonEraseClick(Sender); // 万一のため今いる場所の絵を消しておく
  SpinEditY.Value := 10; // Y の値の初期化
  SpinEditX.Value := Random(180) + 10; // X 座標を乱数で決める
  SpinEditSpeed.Value := 10; // 速度の値の初期化
  ButtonDrawClick(Sender); // インベーダーを描く
  CheckBoxAlive.Checked := True; // 「動いている」をチェック
end;

この「初期処理」ボタンのプログラムで乱数 Random を使っているので、ここで操作盤の「開始」ボタンを押したときに乱数の初期化もしておくことにしましょう。プログラムを次のように変更してください (下線部が変更部分です)。

// 「開始」ボタン
procedure TFormControl.ButtonStartClick(Sender: TObject);
begin
  Randomize; // 乱数の初期化
  FormInvader.ButtonConstClick(Sender); // インベーダーを出現させる
  TimerMain.Enabled := True; // タイマー開始
end;

これで、インベーダーは1機だけではありますが、一応インベーダーゲームができました。

ここまでのプログラムをダウンロード (D)

【基礎課題 9-6】

実行して遊んでみましょう。操作盤の「開始」ボタンを押すとインベーダーが動き出します。続けて、操作盤の「弾発射」ボタンを押してインベーダーを狙ってみてください。命中したときにインベーダーと弾が消えることを確認しましょう。