クラスまみれのゲームプログラミング入門

第5回:敵vsプレイヤー(1)

 第4回で作成した敵とプレイヤーを実際に戦わせてみましょう。シューティングゲームで勝敗が付くケースは主にこんなところでしょう。

  • 敵とプレイヤーが衝突
  • プレイヤーの弾が敵に衝突
  • 敵の弾がプレイヤーに衝突

 いずれのパターンにしても共通しているのはオブジェクト同士が衝突していることです。そこでCCharactorクラスに衝突判定関数を付け加えることにしましょう。

 衝突の判定に使われるもっとも有名な方法は、オブジェクトAの領域とオブジェクトBの領域が重なっているかを知ることです。当プログラミング講座では、オブジェクトの領域を四角形に統一し、その領域同士の重ね合わせを衝突判定の結果に用いることにします。


転職、求人情報ならリクルートの転職サイト【リクナビNEXT】

 まずは、領域の数値を管理するクラス「RECTF」を作成することにします。以下のコーディング例では、ゲームプログラミングで用意すると便利だろうと思われる関数を実装していますが、必要に応じて追加・修正していくといいでしょう。

 このクラスの肝は、領域同士が重なっているかどうかを判定するRECTF::IsCloss()です。条件は「オブジェクトA(以下A)の右端がオブジェクトB(以下B)の左端よりも右にある かつ Bの左端がAの右端よりも左にある かつ Aの上端がBの下端よりも下にある かつ Bの上端がAの下端よりもしたにある」を満たしているときです。判定方法がシンプルゆえに処理も高速です。

Charactor.h
class RECTF
{
public:
	// 領域四隅の数値を格納
	float left, top, right, bottom;

	RECTF();
	RECTF(float x1, float y1, float x2, float y2, bool center = false);

	void SetRect(float left, float top, float right, float bottom);

	void Shift(float mx, float my);
	void ReverseX();
	void ReverseY();
	void Inflate(float x, float y);
	void Deflate(float x, float y);
	bool IsCross(RECTF *target);

	void operator =(const RECTF& origin);  
};

Charactor.cpp
RECTF::RECTF()
{
	left = top = right = bottom = 0.0f;
}

RECTF::RECTF(float x1, float y1, float x2, float y2, bool center)
{
	if(center == true){
		// x1, y1は矩形の中央座標、
		// x2, y2は横幅、縦幅とみなす
		left = x1 - x2;
		right = x1 + x2;
		top = y1 - y2;
		bottom = y1 + y2;
	}else{
		// x1,y1は左上、x2,y2は右下の座標と見なす
		SetRect(x1, y1, x2, y2);
	}
}

void RECTF::SetRect(float left, float top, float right, float bottom)
{
	// 座標を格納する
	this->left = left;
	this->right = right;
	this->top = top;
	this->bottom = bottom;
}

void RECTF::Shift(float mx, float my)
{
	// 矩形の座標を指定値だけずらす
	left += mx;
	right += mx;
	top += my;
	bottom += my;
}

void RECTF::ReverseX()
{
	// y軸を中心に座標を反転する
	left = -left;
	right = -right;

	// 左右の大小関係が逆転するので修正
	float f = left;
	left = right;
	right = f;
}

void RECTF::ReverseY()
{
	// x軸を中心に座標を反転する
	top = -top;
	bottom = -bottom;

	float f = top;
	top = bottom;
	bottom = f;
}

void RECTF::Inflate(float x, float y)
{
	// 矩形を指定値分拡大する
	left -= x;
	right += x;
	top -= y;
	bottom += y;
}

void RECTF::Deflate(float x, float y)
{
	// 矩形を指定値分縮小する
	left += x;
	right -= x;
	if(left > right) left = right = (left - right) / 2.0f;

	top += y;
	bottom -= y;
	if(top > bottom) top = bottom = (top - bottom) / 2.0f;
}

bool RECTF::IsCross(RECTF *target)
{
	// targetの矩形とこのクラスの矩形が重なるか判定
	if(this->left <= target->right && target->left <= this->right
	&& this->top <= target->bottom && target->top <= this->bottom)
	{
		return true;
	}

	return false;
}

void RECTF::operator =(const RECTF &origin)
{
	// クラスの代入によるデータのコピー
	SetRect(origin.left, origin.top, origin.right, origin.bottom);
}


高速モバイル通信がこんなに安い!【イー・モバイル】月々1,000円〜

 CCharactorクラスに衝突の判定を行うための関数(HitTest)を追加します。HitTestでは自分自身の当たり判定領域と対象の当たり判定領域をそれぞれGetHitRect()で取得したものに対して行っています。なお、GetSpriteRect()は、CSpriteが保有しているサイズや中心位置を元に領域を計算するための関数です。

Charactor2.h
class CCharactor : public CGameObject
{
	/* 省略 */
public:
	void GetSpriteRect(RECTF &result);
	virtual void GetHitRect(RECTF &result);
	virtual bool HitTest(CCharactor *target);
};

Charactor2.cpp
bool CCharactor::HitTest(CCharactor *target)
{
	RECTF r1, r2;
	this->GetHitRect(r1);
	target->GetHitRect(r2);

	return r1.IsCross(&r2);
}

void CCharactor::GetSpriteRect(RECTF &result)
{
	int w, h;
	sprite.GetSpriteSize(&w, &h);

	BYTE pos = sprite.GetCenterPosition();
	if(pos & CP_LEFT){
		result.left = x;
		result.right = result.left + (float)w;
	}else if(pos & CP_RIGHT){
		result.left = x - (float)w;
		result.right = x;
	}else{
		result.left = x - (float)w / 2.0f;
		result.right = result.left + (float)w;
	}

	if(pos & CP_TOP){
		result.top = y;
		result.bottom = result.top + (float)h;
	}else if(pos & CP_BOTTOM){
		result.top = y - (float)h;
		result.right = y;
	}else{
		result.top = y - (float)h / 2.0f;
		result.bottom = result.top + (float)h;
	}
}

void CCharactor::GetHitRect(RECTF &result)
{
	GetSpriteRect(result);
}

 プレイヤーと敵との勝負を行う部分の実装については次回にて。