【Delphi / C++Builder Starter チュートリアルシリーズ】 第9回 ‟シューティングゲーム 敵を攻撃しよう„ [JAPAN]
【Delphi / C++Builder Starter チュートリアルシリーズ】
第9回 ‟シューティングゲーム 敵を攻撃しよう„
10月24日より始まりました 「Delphi / C++Builder Starter チュートリアルシリーズ」。全10回、12月26日まで、毎週17時より30分間で、無料でダウンロード&利用できる開発環境のDelphi / C++Builder Starter エディションを使用して、ゲームを作るまで一通り、セミナーを実施してまいります。
なお、前回および初回分の内容に関するブログ記事は以下のリンクからお読み頂けます。
[アジェンダ]
- アニメーション終了時の物体判定
- タイマーを使った物体判定
[開発環境インストール]
[Delphi / C++Builder Starter チュートリアルシリーズ] 第1回 ‟無料で始めよう アプリ作成„をご参考になり、開発環境をインストールしてください。
[シューティングゲーム用ドット絵フリー素材]
http://mfstg.web.fc2.com/material/index.html
上記サイトの画像を使わせて頂きました。(ありがとうございます)
[今回の仕様]
ミサイル攻撃判定(プレーヤが敵を攻撃)
物体が重なり判定(敵からの攻撃に当たる
[プレーヤが敵を攻撃]
敵はX=0方向に向かって避ける事なくまっすぐ突進してきます。
なので戦闘機のミサイル発射ボタンクリックされた段階でどの敵に当たるのか判断できます。
ミサイル攻撃ボタンOnClickイベント時に敵が前方に存在するか判断するコードを記述します
Delphi
//// procedure TfmShooting_main.Button_missileClick(Sender: TObject); var iExPos: Single; //プレーヤーミサイル最終Position.X iTemp: Single; //一旦保存用 iEnmX: Single; //敵Position.X一旦保存用 i: Integer; //ループ用 em_buf_: TRectangle; //敵一旦保存 function EnmExist(enm: TRectangle): Single; begin {敵が目的の座標に居た場合敵のPosition.Xを返す} Result := missile_max; if enm.Visible and (enm.Position.X > (Rectangle_player.Position.X + Rectangle_player.Width)) then if ((enm.Position.Y-10) <= Rectangle_missile.Position.Y) and ((enm.Position.Y + (enm.Height*enm.Scale.X))+10 >= Rectangle_missile.Position.Y) then begin Result := enm.Position.X; end; end; begin Button_missile.Enabled := False; //ミサイルボタンを無効 KanokeBuff := nil; //敵保存変数をクリア FloatAnimation_missile.StopValue := missile_max; //ミサイル最終地点設定 Rectangle_missile.Position.Y := Rectangle_player.Position.Y + 25; FloatAnimation_missile.StartValue := Rectangle_player.Position.X + 20; iExPos := missile_max; iEnmX := missile_max; for i := 0 to 2 do {ループで敵3つを順番に撃破可能か調べる} begin case i of 0:em_buf_ := Rectangle_Enm1; 1:em_buf_ := Rectangle_Enm2; 2:em_buf_ := Rectangle_Enm3; end; iTemp := EnmExist(em_buf_); if (iTemp < missile_max) and (iEnmX > em_buf_.Position.X) then begin {敵の前に敵が居る場合, 手前の敵を優先} iExPos := iTemp; KanokeBuff := em_buf_; end; if Assigned(KanokeBuff) then {撃破出来る敵変数に存在するか確認} iEnmX := KanokeBuff.Position.X; end; if iExPos < (Rectangle_player.Position.X + Rectangle_player.Width) then iExPos := missile_max; {敵がプレーヤーより後ろにいる場合標的から外す} FloatAnimation_missile.StopValue := iExPos; {ミサイルの目標値をセット} Rectangle_missile.Visible := True; {ミサイルを表示} FloatAnimation_missile.Start; {ミサイル発射} end;
C++
//// float __fastcall TfmShooting_main::EnmExist(TRectangle* enm) { float f_missile_max{static_cast<float>(missile_max)}; float result_ = f_missile_max; if (enm->Visible && (enm->Position->X > (Rectangle_player->Position->X + Rectangle_player->Width)) ) if ( ((enm->Position->Y-10) <= Rectangle_missile->Position->Y) && ((enm->Position->Y + (enm->Height*enm->Scale->X))+10 >= Rectangle_missile->Position->Y) ) result_ = enm->Position->X; return result_; } void __fastcall TfmShooting_main::Button_missileClick(TObject *Sender) { TRectangle* em_buf_{nullptr}; //敵一旦保存 KanokeBuf = nullptr; //敵保存変数をクリア Button_missile->Enabled = false; Rectangle_missile->Position->Y = Rectangle_player->Position->Y + 25; FloatAnimation_missile->StopValue = missile_max; //ミサイル最終地点設定 FloatAnimation_missile->StartValue = Rectangle_player->Position->X + 20; auto iExPos = missile_max; auto iEnmX = missile_max; for (int i = 0; i < 3; i++) {/*ループで敵3つを順番に撃破可能か調べる*/ switch(i){ case 0: em_buf_ = Rectangle_Enm1; break; case 1: em_buf_ = Rectangle_Enm2; break; case 2: em_buf_ = Rectangle_Enm3; } auto iTemp = EnmExist(em_buf_); if ((iTemp < missile_max) && (iEnmX > em_buf_->Position->X)) { /*敵の前に敵が居る場合, 手前の敵を優先*/ iExPos = iTemp; KanokeBuf = em_buf_; } if (KanokeBuf != nullptr) {//撃破出来る敵変数に存在するか確認 iEnmX = KanokeBuf->Position->X; } } if( iExPos < (Rectangle_player->Position->X + Rectangle_player->Width)) iExPos = missile_max; //敵がプレーヤーより後ろにいる場合標的から外す FloatAnimation_missile->StopValue = iExPos; //ミサイルの目標値をセット Rectangle_missile->Visible = true; //ミサイルを表示 FloatAnimation_missile->Start(); //ミサイル発射 }
TRectangleグローバル変数で1つ用意します。
ミサイルのアニメーション終了OnFinishイベントを使い敵を倒した処理を入れます
Delphi
//// procedure TfmShooting_main.FloatAnimation_missileFinish(Sender: TObject); //第9回 begin Button_missile.Enabled := True; //ミサイルボタンを有効にする Rectangle_missile.Visible := False; //ミサイルを非表示 if Assigned(KanokeBuff) then //敵撃破変数に敵存在するか確認 begin KanokeBuff.Visible := False; //敵を非表示にする end; KanokeBuff := nil; //敵撃破変数を空にする end;
C++
//// void __fastcall TfmShooting_main::FloatAnimation_missileFinish(TObject *Sender) { Button_missile->Enabled = true; Rectangle_missile->Visible = false; if (KanokeBuf != nullptr) { KanokeBuf->Visible = false; } KanokeBuf = nullptr; }
[敵からの攻撃に当たる]
上図の方法で常にプレーヤーと敵が重なっていないかを判断します
敵の攻撃に当たる為のタイマーを追加します
タイマーイベントを追加します
Delphi
//// procedure TfmShooting_main.Timer_gameoverTimer(Sender: TObject); var i: Integer; atari_: Boolean; //プレーヤー戦闘機と敵が接触した場合True function hantei(r1, r2: TRectF): Boolean; //四角どうしが重なっているか判定 begin Result := False; if (r1.Left < r2.Right) and (r1.Right > r2.Left) and (r1.Top < r2.Bottom) and (r1.Bottom > r2.Top) then begin Result := True; end; end; function Rect_hantei(rect1, rect2: TRectangle): Boolean; //TRectangleどうしが重なっているか判定 begin if rect2.Visible then begin Result := Hantei( TRectF.Create(rect1.Position.X, rect1.Position.Y, rect1.Position.X + rect1.Width, rect1.Position.Y + rect1.Height ), TRectF.Create(rect2.Position.X, rect2.Position.Y, rect2.Position.X + rect2.Width, rect2.Position.Y + rect2.Height )); end else Result := False; end; begin atari_ := False; Timer_gameover.Enabled := False; for i := 0 to 3 do //敵1~3とlaserbeamが戦闘機と接触したかを判定 case i of 0: begin atari_ := Rect_hantei(Rectangle_player, Rectangle_Enm1); if atari_ then Break; end; 1: begin atari_ := Rect_hantei(Rectangle_player, Rectangle_Enm2); if atari_ then Break; end; 2: begin atari_ := Rect_hantei(Rectangle_player, Rectangle_Enm3); if atari_ then Break; end; 3: begin atari_ := Rect_hantei(Rectangle_player, Rectangle_Enm_laserbeam); if atari_ then Break; end; end; if atari_ then begin Rectangle_player.Visible := False; ShowMessage('ゲームオーバー'); game_reset; //ゲームをリセットする end else Timer_gameover.Enabled := True; end;
C++
//// bool __fastcall TfmShooting_main::hantei(TRectF& r1,TRectF& r2) { bool b1{false}; if ( (r1.Left < r2.Right) && (r1.Right > r2.Left) && (r1.Top < r2.Bottom) && (r1.Bottom > r2.Top) ) { b1 = true; } return b1; } bool __fastcall TfmShooting_main::Rect_hantei(TRectangle* rect1, TRectangle* rect2) { if (rect2->Visible) { TRectF r1{TRectF(rect1->Position->X, rect1->Position->Y, rect1->Position->X + rect1->Width, rect1->Position->Y + rect1->Height)}; TRectF r2{TRectF(rect2->Position->X, rect2->Position->Y, rect2->Position->X + rect2->Width, rect2->Position->Y + rect2->Height)}; return this->hantei(r1, r2); } else return false; } void __fastcall TfmShooting_main::Timer_gameoverTimer(TObject *Sender) { Timer_gameover->Enabled = false; bool atari_{false}; for (int i =0; i < 4; i++) if (! atari_) { switch(i) { case 0: atari_ = Rect_hantei(Rectangle_player, Rectangle_Enm1); break; case 1: atari_ = Rect_hantei(Rectangle_player, Rectangle_Enm2); break; case 2: atari_ = Rect_hantei(Rectangle_player, Rectangle_Enm3); break; case 3: atari_ = Rect_hantei(Rectangle_player, Rectangle_Enm_laserbeam); } } if (atari_) { Rectangle_player->Visible = false; ShowMessage("ゲームオーバー"); game_reset(); //ゲームをリセットする } else Timer_gameover->Enabled = true; } //---------------------------------------------------------------------------
ゲームオーバーリセットの関数を作ります
Delphi
//// procedure TfmShooting_main.game_reset; begin //ゲームリセット Button_up.Visible := False; //ボタンUp非表示 Button_down.Visible := False; //ボタンDown非表示 Button_missile.Visible := False; //ボタンミサイル非表示 Timer_Enms.Enabled := False; //敵出現タイマーストップ Timer_Enms_laserbeam.Enabled := False; //敵レーザービームタイマーストップ Timer_gameover.Enabled := False; //プレーヤーと敵接触判定タイマーストップ Rectangle_Enm1.Visible := False; //敵1非表示 Rectangle_Enm2.Visible := False; //敵2非表示 Rectangle_Enm3.Visible := False; //敵3非表示 Rectangle_Enm_laserbeam.Visible := False; //敵レーザービーム非表示 Rectangle_startscene.Visible := True; //スタートシーン表示 end;
C++
//// void __fastcall TfmShooting_main::game_reset() { Button_up->Visible = false; //ボタンUp非表示 Button_down->Visible = false; //ボタンDown非表示 Button_missile->Visible = false; //ボタンミサイル非表示 Timer_Enms->Enabled = false; //敵出現タイマーストップ Timer_Enms_laserbeam->Enabled = false; //敵レーザービームタイマーストップ Timer_gameover->Enabled = false; //プレーヤーと敵接触判定タイマーストップ Rectangle_Enm1->Visible = false; //敵1非表示 Rectangle_Enm2->Visible = false; //敵2非表示 Rectangle_Enm3->Visible = false; //敵3非表示 Rectangle_Enm_laserbeam->Visible = false; //敵レーザービーム非表示 Rectangle_startscene->Visible = true; //スタートシーン表示 FiTotal = 0; FdtPlay = 0; }
[githubにソースを公開しております]
https://github.com/mojeld/embarcadero_jp_shooting_game
■次回は12月26日(月)17:00より “シューティングゲーム スコアとゲームオーバーを付けよう„ をお送りします。


Comments
-
Please login first in order for you to submit comments