【Delphi / C++Builder Starter チュートリアルシリーズ】 第9回 ‟シューティングゲーム 敵を攻撃しよう„ [JAPAN]

Posted by on in Programming

【Delphi / C++Builder Starter チュートリアルシリーズ】

第9回 ‟シューティングゲーム 敵を攻撃しよう

10月24日より始まりました 「Delphi / C++Builder Starter チュートリアルシリーズ」。全10回、12月26日まで、毎週17時より30分間で、無料でダウンロード&利用できる開発環境のDelphi / C++Builder Starter エディションを使用して、ゲームを作るまで一通り、セミナーを実施してまいります。

 

なお、前回および初回分の内容に関するブログ記事は以下のリンクからお読み頂けます。

<< 第8回 ‟シューティングゲーム 敵を動かしてみよう„  

<< 第1回 ‟無料で始めよう アプリ作成„ 

 

[アジェンダ]

  • アニメーション終了時の物体判定
  • タイマーを使った物体判定

 

[開発環境インストール] 

[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より  “シューティングゲーム スコアとゲームオーバーを付けよう„ をお送りします。

 

<<第8回 ‟シューティングゲーム 敵を動かしてみよう„  



About
Gold User, No rank,
Delphi / C++Builder blogger

Comments

Check out more tips and tricks in this development video: