衝突判定には大きく2種類ある。
①範囲で検知する。
②ポリゴンごとに検知する。(3Dのみ)
たいていの場合は①を使う。
たとえば、シューティングゲームで弾が敵に当たったかどうか調べたい場合は、
弾と敵それぞれに「この範囲に入ったらぶつかったと見なす」という枠を作成する。
その枠のことをコライダー(Collider)と呼ぶ。
コライダーは球体と箱型の2種類を用意してある。
球体の方が処理が早い。
オブジェクトに球体のコライダーを付ける場合は、クラス内で次のように書く。
#include "Engine/SphereCollider.h"
SphereCollider* collision = new SphereCollider(XMFLOAT3(0, 0, 0), 1.2f);
AddCollider(collision);
まず、SphereCollider型のオブジェクトを作成する。
このときの引数は、球体の「中心位置」と「半径」で、位置はゲームオブジェクトの原点から見た位置。
そして、AddCollider関数を呼ぶことで、作成したコライダーがゲームオブジェクトに付けられる。
コライダーは複数付けても良い。
オブジェクトに箱型のコライダーを付ける場合は、クラス内で次のように書く。
#include "Engine/BoxCollider.h"
BoxCollider* collision = new BoxCollider(XMFLOAT3(0, 0, 0), XMFLOAT3(1, 1, 1));
AddCollider(collision);
まず、BoxCollider型のオブジェクトを作成する。
このときの引数は、コライダーの「中心位置」と「サイズ」で、位置はゲームオブジェクトの原点から見た位置、サイズは「幅」「高さ」「奥行き」。
そして、AddCollider関数を呼ぶことで、作成したコライダーがゲームオブジェクトに付けられる。
コライダーは複数付けても良い。
コライダーを付けたもの同士がぶつかると、それぞれのゲームオブジェクトの「OnCollision」関数が呼ばれる。
この関数が無ければ何も呼ばれない。
つまり、ぶつかった時に何かしたい場合は「OnCollision」関数を作れば良い。
例えばプレイヤーが何かとぶつかったときの処理を書きたい場合は
<Player.h>
#pragma once
#include "Engine/GameObject.h"
class Player : public GameObject
{
public:
Player(GameObject* parent);
~Player();
//初期化
void Initialize() override;
//更新
void Update() override;
//描画
void Draw() override;
//開放
void Release() override;
//何かに当たった
//引数:pTarget 当たった相手
void OnCollision(GameObject *pTarget) override;
};
<Player.cpp>
:
:
//何かに当たった
void Player::OnCollision(GameObject * pTarget)
{
//当たったときの処理
}
という感じ。
AとBがぶつかったときの処理をAクラスに書くか、Bクラスに書くかは「ぶつかって変化がある方」を選ぶ。
例えばプレイヤーが壁にぶつかったとき、壁は変化が無いがプレイヤーは止まるので、プレイヤークラスに処理を書けばいい。
両方に変化があるならどっちでも良い。
また、OnCollision関数の引数には、ぶつかった相手のオブジェクトが入っている。
ぶつかった相手次第で処理を分けたい場合(普通そうでしょうけど)は、次のように名前で判断するのが手っ取り早い。
//何かに当たった
void Player::OnCollision(GameObject * pTarget)
{
//弾に当たったとき
if (pTarget->GetObjectName() == "Bullet")
{
}
//敵に当たったとき
if (pTarget->GetObjectName() == "Enemy")
{
}
}
たとえばデコボコした地面に沿ってプレイヤーを動かしたい場合は、上記のような範囲で衝突検知するやり方では実現できない。
こういう場合は「レイキャスト」と呼ばれる手法を使う。
これは「ある位置」から「ある方向」に向かって見えない光線(レイ)を飛ばす。
そのレイが任意のゲームオブジェクトに「何メートル先でぶつかるか」を求めるもの。
プレイヤーの足元の位置から真下にレイを飛ばすと、3m先で地面にぶつかった。
この場合は、プレイヤーの位置を3m下げれば地面に沿う。
プレイヤーの位置を動かす処理はプレイヤークラスに書く。
しかし、プレイヤーが地面のモデル番号を知らないと、上の例は実現できない。
そこで、まずは地面クラス側に「ロードしたモデル番号を教える関数」を用意する。
<Stage.h>
#pragma once
#include "Engine/GameObject.h"
class Stage : public GameObject
{
private:
int hModel_;
public:
:
:
:
int GetModelHandle() { return hModel_; }
};
これで、地面クラスのGetModelHandle関数を呼べば、hModel_に入っているモデル番号が返ってくる。
残りの処理はプレイヤークラスに書くことになる。
プレイヤー側で地面のモデル番号を知るためには
①地面を探す
②モデル番号を知る
という手順が必要。
<Player.cpp>
#include "Stage.h"
:
:
:
void Player::Update()
{
Stage* pStage = (Stage*)FindObject("Stage"); //ステージオブジェクトを探す
int hGroundModel = pStage->GetModelHandle(); //モデル番号を取得
}
ステージを扱うためにはそのクラスが宣言されてるヘッダ(Stage.h)をインクルードする必要がある。
FindObjectは引数に指定した名前のゲームオブジェクトを探す関数。
そしてステージが見つかれば、先ほど作ったGetModelHandle関数でモデル番号が取得できる。
準備ができたのでレイを飛ばす。
void Player::Update()
{
Stage* pStage = (Stage*)FindObject("Stage"); //ステージオブジェクトを探す
int hGroundModel = pStage->GetModelHandle(); //モデル番号を取得
RayCastData data;
data.start = transform_.position_; //レイの発射位置
data.dir = XMFLOAT3(0, -1, 0); //レイの方向
Model::RayCast(hGroundModel, &data); //レイを発射
}
まず、RayCastData型の変数を用意。これは必要な変数をまとめた構造体。
そのメンバのstartにレイの発射位置、dirに発射方向を指定する。
今回はプレイヤーの原点から、真下方向に発射する例。
そして、Model::RayCast関数で例を発射。引数は対象のモデル(今回は地面)の番号と、先ほどの構造体のアドレス。
レイを飛ばした結果は、引数で指定した構造体に入ってくる。
そもそもレイが当たったかどうかがhitというメンバに入る(当たればtrue)。
地面が下にあるのにレイを上に撃ったら当然当たらない。
そして、当たった場合はdistにレイの発射位置から衝突位置までの距離が入る。
つまり、プレイヤーを地面の高さまで移動したい場合はこう書く。
void Player::Update()
{
Stage* pStage = (Stage*)FindObject("Stage"); //ステージオブジェクトを探す
int hGroundModel = pStage->GetModelHandle(); //モデル番号を取得
RayCastData data;
data.start = _position; //レイの発射位置
data.dir = D3DXVECTOR3(0, -1, 0); //レイの方向
Model::RayCast(_hStageModel, &data); //レイを発射
//レイが当たったら
if(data.hit)
{
//その分位置を下げる
transform_.position_.y -= data.dist;
}
}
これで、例えば地面が3m下にあれば3m下がるので、地面の上にぴったり乗るはず。
※ただし、このやり方だと地面がプレイヤーより上にあると判定できないので、上り坂だと地面にめり込んでしまう。
実際はもう少し上のほうからレイを撃つ必要がある。