9-3 インベーダー・弾を自動的に動かそう

【基礎課題 9-2】で行ったように、今考えているインベーダーの動きは、

のどちらかです。そして左右に動くか、あるいは下に動くかはインベーダー自身の場所 (座標) によって決まることになります (両端に来ているかどうかで決まります)。そこで、

という命令を人間が使い分けなくても、ただ単に

と命令するだけで、どちらに動くべきかを自分で判断して動く、という賢いインベーダーを作ることができそうです。ついでに、「動いている」チェックボックスがチェックされていないときは動かない、つまりチェックされているときだけ動くようにしてみましょう。

この「移動」ボタンのイベントハンドラは次のようになります。

// 「移動」ボタン
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
        ;
      end
  ;
end;

また、インベーダーを画面に登場させる「初期処理」ボタン、インベーダーを消滅させる「終了処理」ボタンのイベントハンドラを書きましょう。

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

「初期処理」ボタンのイベントハンドラの中で「万一のため…」という注釈が書いてある最初の行は、いつでも (たとえば、ゲーム開始時だけでなく、インベーダーが飛んでいる最中、つまり画面に描画された状態で) 「初期処理」ボタンが押されても大丈夫であるように書かれた命令です。このプログラムでは、インベーダーが画面に現れていないときに「消す」ボタンを押しても (ButtonEraseClick を呼び出しても) 何の弊害もありません。表示されていたら消す、という効果があるだけです。このように、色々な可能性を考慮してプログラムにちょっとした工夫をしておくと、プログラムはより安定します。

【基礎課題 9-3】

このプログラムを実行後、「初期処理」ボタンを押してインベーダーを出現させ、「移動」ボタンを連続的にクリックしてジグザグの動きになることを確認し、最後に「終了処理」ボタンを押してインベーダーを消してください。

ここまでくれば、「インベーダー」フォームのプログラミングの移動部分がほぼ出来上がりです。後は、人間の代わりにコンピュータ (操作盤フォームの TimerMain) に「移動」ボタンを連射 (連続クリック) させることでインベーダーを動かしてみましょう。

まず、TimerMainTimer イベントのプログラムは次のようになります。

// タイマーが時間を刻むごとに
procedure TFormControl.TimerMainTimer(Sender: TObject);
begin
  FormInvader.ButtonMoveClick(Sender); // インベーダーを動かす
end;

そして、「開始」ボタンを押すと TimerMain が動き出す (Enabled プロパティが True になる) ようにします。

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

エラー画面

実行しようとしたときに「フォーム 'FormControl' は USERS リストに無い 'invader' ユニットで定義されているフォーム 'FormInvader' を参照しています。このユニットを追加してよいですか?」というエラーが出た場合、「はい」を押して下さい。この後も、同様のエラーが出たら「はい」を選んでください。

「インベーダー」フォームのうち、“動き”に関する部分はこれで完成しました。

【基礎課題 9-4】

試しに、プログラムを実行し、操作盤の「開始」ボタンを押してみましょう。インベーダーが動き出すはずです。

インベーダーがちゃんと動くようになったので、今度は弾を動かしましょう。つまり「弾」フォームのプログラミングに進みます。基本的に、「インベーダー」フォームでやったことと内容は同じです (より簡単です)。

最初は「描く」ボタンのプログラムを作ります。このボタンが押されると、下のように y 座標スピンエディットの示す位置に (縦線で示される) 弾を描きます。ここに x 座標は常に100で固定することにします。

弾の描画

プログラムは次のようになります。

// 「描く」ボタン
procedure TFormBullet.ButtonDrawClick(Sender: TObject);
begin
  FormDisplay.ImageField.Canvas.Pen.Color := clBlack; // 黒で弾を描く
  FormDisplay.ImageField.Canvas.MoveTo(100, SpinEditY.Value);
  FormDisplay.ImageField.Canvas.LineTo(100, SpinEditY.Value + 5);
end;

「消す」ボタンも、インベーダーのときと同様に白線で弾を描くだけです。

// 「消す」ボタン
procedure TFormBullet.ButtonEraseClick(Sender: TObject);
begin
  FormDisplay.ImageField.Canvas.Pen.Color := clWhite; // 白で弾を描く
  FormDisplay.ImageField.Canvas.MoveTo(100, SpinEditY.Value);
  FormDisplay.ImageField.Canvas.LineTo(100, SpinEditY.Value + 5);
end;

「初期処理」ボタン、「終了処理」ボタンの処理も書いてしまいましょう。

// 「初期処理」ボタン
procedure TFormBullet.ButtonConstClick(Sender: TObject);
begin
  ButtonEraseClick(Sender); // 万一のため今いる場所の絵を消しておく
  SpinEditY.Value := 200; // Y の値の初期化
  ButtonDrawClick(Sender); // 弾を描く
  CheckBoxAlive.Checked := True; // 「動いている」をチェック
end;
// 「終了処理」ボタン
procedure TFormBullet.ButtonDestClick(Sender: TObject);
begin
  CheckBoxAlive.Checked := False; // 「動いている」のチェックを外す
  ButtonEraseClick(Sender); // 弾を消す
end;

「移動」ボタンは、インベーダーよりも簡単です。もし飛んでいるなら上方向 (y 座標が減る方向) に10動かし、もし画面上端からはみ出したら「終了処理」を行います。こうしておくことで、画面から消えても永遠に飛びつづける、という処理をしなくてすみます。プログラムは次のようになります。

// 「移動」ボタン
procedure TFormBullet.ButtonMoveClick(Sender: TObject);
begin
  if (CheckBoxAlive.Checked = True)
    then // もし「動いている」なら
      begin
        ButtonEraseClick(Sender); // 今の場所から消し
        SpinEditY.Value := SpinEditY.Value - 10; // 上方向に移動させて
        if (SpinEditY.Value < 0)
          then // もし画面の上端からはみ出したら
            begin
              ButtonDestClick(Sender); // 「終了処理」をする
            end
          else // まだ画面に表示される位置なら
            begin
              ButtonDrawClick(Sender); // 新しい場所に描く
            end
        ;
      end
  ;
end;

後は、インベーダーフォームと同様、コンピュータに (TimerMain に) 「移動」ボタンを連射させて弾を動かさせるようにします。操作盤の TimerMain のプログラムを次のように変更してください (下線部が変更部分です)。

// タイマーが時間を刻むごとに
procedure TFormControl.TimerMainTimer(Sender: TObject);
begin
  FormInvader.ButtonMoveClick(Sender); // インベーダーを動かす
  FormBullet.ButtonMoveClick(Sender); // 弾を動かす
end;

【基礎課題 9-5】

プログラムを実行し、弾フォームの「初期処理」ボタンを押して弾を発射し、インベーダーを狙ってみましょう。また、弾が画面上端まで飛んでいくかどうか確認してください。

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

これで、インベーダーと弾の移動の部分が完成しました。