ゲームを作るうえで衝突判定は非常に重要な考え方です。
今回は「円」を使った衝突判定について説明します。
「円」を使った衝突判定とは、下図のように2つの物体を「円」と考え、2つの「円」が重なっているかどうかを判定します。
※ここで言う「円」とは「正円」だと考えてください。「楕円」は扱いません。
画像を転送する際には「左上右下」の座標を指定するため、四角形で画像を転送します。
カラーキーが指定してある画像は、四角形で描画されませんが、上の図の黒い四角が転送されている画像のイメージです。
転送した画像の、どこを中心に、どのくらいの半径で「円」を設定しても構いませんが、今回は下のようにします。
中心 画像の中心
半径 画像の幅(または高さ)の半分
画像の転送先XY座標が分かれば、中心を求めるのは簡単です。
転送先XY座標は画像の左上の座標ですから、
X座標に半径を加える → 中心のX座標
Y座標に半径の半分を加える → 中心のY座標
これで計算出来ます。
中心と半径が決まったら、2つの「円」の中心から中心までの距離を計算します。
これも「三平方の定理」を使えば簡単に求まります。
Xの長さは、
物体Aの中心のX座標 − 物体Bの中心のX座標
で求まります。
※物体B − 物体A でもOKです。
Yの長さも、
物体Aの中心のY座標 − 物体Bの中心のY座標
で求まります。
Zの2乗 = Xの2乗 + Yの2乗
ですから、Zの2乗の平方根を計算すれば「Zの長さ」が分かります。
平方根を計算する関数は、C言語の標準関数に用意されています。
この「Zの長さ」と2つの円の半径を足した数を比較する事で、2つの「円」が重なっているかどうか分かります。
上の図のように、2つの半径を足した長さが「円」同士が接している状態です。
この長さよりも「Zの長さ」が長ければ「円」は接触していませんし、短ければ接触しています。
さて、これを実際にプログラミングしていく訳ですが、どこに書けば良いでしょうか。
2つの物体の衝突判定を行うには、お互いの転送先XY座標と半径(幅(または高さ)から計算)が必要です。
今回は「弾」と「敵キャラクター」の衝突判定を行いたいのですが、「弾クラス」に衝突判定関数を作ってしまうと「敵クラス」の座標などは扱えません。
逆も同じで「敵クラス」に関数を作ったとしても「弾クラス」の座標は扱えません。
しかし、弾クラスは「GetWidth関数」「GetHeight関数」がありますので、幅や高さは取得出来ますね。
例えば「GetX関数」や「GetY関数」などを追加すると言う手段もありますが、あまりスマートではありません。
ここで思い出して欲しいのは「転送先XY座標」や「幅」「高さ」といった情報がどこで用意されているか、です。
class Base { public: Base(const int width, const int height); protected: enum { UP, RIGHT, DOWN, LEFT, }; //幅、高さ const int WIDTH; const int HEIGHT; //転送先座標 int m_x; int m_y; int m_direction; RECT m_sour; RECT m_dest; }; |
衝突判定に必要なデータは、「プレイヤークラス」「弾クラス」「敵クラス」の継承元である「Baseクラス」に全てあります。
ここに衝突判定関数を作れば、どのクラスからも呼び出す事が出来る上、お互いのprotectedメンバにアクセスする事も可能です。
プロトタイプ宣言を追加しましょう。
class Base {
public:
Base(const int width, const int height);
bool CollideCircle(const Base &refTarget);
protected:
enum {
UP,
RIGHT,
DOWN,
LEFT,
};
const int WIDTH;
const int HEIGHT;
int m_x;
int m_y;
int m_direction;
RECT m_sour;
RECT m_dest;
};
|
「Collide」とは、「衝突する」と言う意味です。
引数は「Baseクラス」のconst参照を設定しました。
こうしておけば「プレイヤークラス」「弾クラス」「敵クラス」どれでも引数として渡せます。
戻り値は「bool型」で、衝突していればtrueを返すように作ります。
「Base.cpp」を開き、コンストラクタ関数の下に「CollideCircle関数」を追加します。
説明しながら進めていくので最初は枠組みだけです。
bool Base::CollideCircle(const Base &refTarget) { } |
最初はそれぞれのオブジェクトの半径を求めます。
bool Base::CollideCircle(const Base &refTarget)
{
int myRadius = WIDTH / 2;
int targetRadius = refTarget.WIDTH / 2;
}
|
次に左上の座標から画像の中心座標を求めます。
bool Base::CollideCircle(const Base &refTarget)
{
int myRadius = WIDTH / 2;
int targetRadius = refTarget.WIDTH / 2;
int myCenterX = m_x + myRadius;
int myCenterY = m_y + myRadius;
int targetCenterX = refTarget.m_x + targetRadius;
int targetCenterY = refTarget.m_y + targetRadius;
}
|
続いて中心同士の距離を求めるため、下の図のXとYの長さを求めます。
bool Base::CollideCircle(const Base &refTarget)
{
int myRadius = WIDTH / 2;
int targetRadius = refTarget.WIDTH / 2;
int myCenterX = m_x + myRadius;
int myCenterY = m_y + myRadius;
int targetCenterX = refTarget.m_x + targetRadius;
int targetCenterY = refTarget.m_y + targetRadius;
int x = myCenterX - targetCenterX;
int y = myCenterY - targetCenterY;
}
|
Xの2乗とYの2乗を足した数の平方根を求めます。
先に、平方根を求めるための関数を使うためのヘッダファイルを追加します。
#define _USING_V110_SDK71_ 1 #include "Base.h" #include <Math.h> //----------------------------------------------------------------------------- // クラスメンバの本体を書きます。 //----------------------------------------------------------------------------- |
平方根を求める関数は「sqrt関数」で、戻り値はdouble型です。
bool Base::CollideCircle(const Base &refTarget)
{
int myRadius = WIDTH / 2;
int targetRadius = refTarget.WIDTH / 2;
int myCenterX = m_x + myRadius;
int myCenterY = m_y + myRadius;
int targetCenterX = refTarget.m_x + targetRadius;
int targetCenterY = refTarget.m_y + targetRadius;
int x = myCenterX - targetCenterX;
int y = myCenterY - targetCenterY;
double distance = sqrt(x * x + y * y);
}
|
続いて2つのオブジェクトの半径を合計します。
bool Base::CollideCircle(const Base &refTarget)
{
int myRadius = WIDTH / 2;
int targetRadius = refTarget.WIDTH / 2;
int myCenterX = m_x + myRadius;
int myCenterY = m_y + myRadius;
int targetCenterX = refTarget.m_x + targetRadius;
int targetCenterY = refTarget.m_y + targetRadius;
int x = myCenterX - targetCenterX;
int y = myCenterY - targetCenterY;
double distance = sqrt(x * x + y * y);
int totalRadius = myRadius + targetRadius;
}
|
「distance」と「totalRadius」を比較して「distance」の方が小さいか等しければ、2つの「円」は接しています。
bool Base::CollideCircle(const Base &refTarget)
{
int myRadius = WIDTH / 2;
int targetRadius = refTarget.WIDTH / 2;
int myCenterX = m_x + myRadius;
int myCenterY = m_y + myRadius;
int targetCenterX = refTarget.m_x + targetRadius;
int targetCenterY = refTarget.m_y + targetRadius;
int x = myCenterX - targetCenterX;
int y = myCenterY - targetCenterY;
double distance = sqrt(x * x + y * y);
int totalRadius = myRadius + targetRadius;
if (distance <= totalRadius) {
return true;
}
return false;
}
|
これで「円」の衝突判定の関数は(ひとまず)出来ました。
最後に関数を呼び出す部分を作りましょう。
「SceneGame.cpp」の「Update関数」を開いて次のコードを追加します。
void SceneGame::Update()
{
if (m_pEngine->GetKeyStateSync(DIK_F1)) {
m_pEngine->ScreenShot();
}
m_player.Update(m_pEngine, m_shot);
m_shot.Update();
m_enemy.Update();
if (m_shot.CollideCircle(m_enemy)) {
}
}
|
今回は「弾」と「敵キャラクター」の衝突判定を行いたいので、「弾クラス」から「CollideCircle関数」を呼び出し、ターゲットとして「敵クラス」を渡します。
ただ、if文の中身はまだ書けません。
当たった時に何をするか決まっていないからです。
次回は衝突後に何をするか考えましょう。