DXライブラリとC言語で3Dゲーム制作。今回は敵との当たり判定の実装。3Dマップとの当たり判定と大体は同じですが細かいところは違います。
敵との当たり判定
前回敵を実装したので今度は敵との当たり判定の実装。流れとしてはこんな感じ。
- プレイヤーから一定範囲内にいる敵がいるか確認
- いればその敵と衝突しているか確認
- 衝突していれば衝突しなくなるまでプレイヤーか敵を押し出す
- プレイヤーか敵の座標を更新
3Dマップとの当たり判定と同じようで違う処理をするので新しく衝突判定関数を書いていく。衝突していた場合敵を押し出すかプレイヤーを押し出すかは自由ですがここではプレイヤーを押し出す方で行きます。
ちなみにマリオみたいに敵と衝突するとダメージを受けてノックバックする、という場合は押し出す処理はせずノックバック処理用の関数を書いてそこでプレイヤーを動かします。
サンプルコード
それでは実装。Player.cpp/h に以下のコードを書く。
Player.h
//以下の関数を定義 void Player_CollisionEnemy();
Player.cpp
//以下のヘッダーファイルをインクルード
#include "Enemy.h"
//以下の定義を追加
const static float PLAYER_HIT_RANGE = 3.0f; //プレイヤーから一定範囲内にいるかどうかに使う距離
//以下の関数を追加
//プレイヤーと敵の衝突処理
void Player_CollisionEnemy() {
int i = 0;
VECTOR PlPolyPos1, PlPolyPos2, EmPos, EmPolyPos1, EmPolyPos2, EmMoveVec;
VECTOR PrevPos, NowPos;
int PrevJumpStatus = 0;
bool RangeFlag = false;
bool HitFlag = false;
//敵のデータ(座標など)を取得する
if (Enemy_GetDataForCollision(&EmPos, &EmPolyPos1, &EmMoveVec)) {
/*----------まずは下準備----------*/
//移動前の座標を保存
PrevPos = player.Position;
//PolyPos1を求める
PlPolyPos1 = player.Position;
//PlPolyPos2はPolyPos1より指定分高い座標
PlPolyPos2 = PlPolyPos1;
PlPolyPos2.y += PLAYER_HIT_HEIGHT;
EmPolyPos2 = EmPolyPos1;
EmPolyPos2.y += 1.0f;
//ジャンプ状態を保存
PrevJumpStatus = player.JumpStatus;
// 移動後の座標を算出
NowPos = VAdd(PrevPos, player.MoveVec);
/*----------敵が一定範囲内にいるか----------*/
{
float x, y, z;
x = NowPos.x - EmPos.x;
y = NowPos.y - EmPos.y;
z = NowPos.z - EmPos.z;
if (x * x + y * y + z * z < PLAYER_HIT_RANGE * PLAYER_HIT_RANGE) {
RangeFlag = true;
}
else {
RangeFlag = false;
}
}
/*----------敵との当たり判定処理----------*/
if (RangeFlag) {
VECTOR SlideVec;
//プレイヤーと敵が衝突しているか(カプセル同士の当たり判定で行う)
if (HitCheck_Capsule_Capsule(PlPolyPos1, PlPolyPos2, PLAYER_HIT_WIDTH, EmPolyPos1, EmPolyPos2, PLAYER_HIT_WIDTH)) {
HitFlag = true;
}
else {
HitFlag = false;
}
//敵と衝突していたら押し出す
if (HitFlag) {
//衝突していたら押し出すベクトルを求める。ここでは敵からプレイヤー方向
SlideVec = VSub(NowPos, EmPos);
SlideVec = VNorm(SlideVec);
NowPos = VAdd(PrevPos, VScale(SlideVec, HIT_SLIDE_LENGTH));
while (i < HIT_TRYNUM) {
//当たり判定座標の更新
PlPolyPos1 = NowPos;
PlPolyPos2 = PlPolyPos1;
PlPolyPos2.y += PLAYER_HIT_HEIGHT;
//座標更新後で衝突しているか確認
//衝突していなければループを抜ける
if (!HitCheck_Capsule_Capsule(PlPolyPos1, PlPolyPos2, PLAYER_HIT_WIDTH, EmPolyPos1, EmPolyPos2, PLAYER_HIT_WIDTH)) break;
//衝突していれば押し出す
NowPos = VAdd(NowPos, VScale(SlideVec, HIT_SLIDE_LENGTH));
i++;
}
//座標を更新する
player.Position = NowPos;
}
}
}
}
//プレイヤーの移動処理関数に以下の処理を追加
void Player_MoveUpdate() {
//略
//当たり判定
{
//当たり判定に使用するための下準備
VECTOR PolyPos1, PolyPos2;
//敵との当たり判定処理を行う
Player_CollisionEnemy();
//現在座標の更新
NowPos = player.Position;
//PolyPos1を求める(以下略)
}
//略
}
まずはプレイヤーおよび敵の座標と当たり判定用の座標、敵の移動ベクトルを取得。そしてプレイヤーの移動後座標を算出する。
次に敵がプレイヤーが一定範囲内にいるか確認する。ここではプレイヤーの座標を中心とした球の中に敵の座標があるかどうかで判定。
敵が一定範囲内にいたら今度はプレイヤーと敵が衝突しているか確認。ここでお互いのモデルそのもので衝突しているか確認すると処理がとてつもなく重くなるのでカプセル同士で衝突しているか判定する HitCheck_Capsule_Capsule() で処理する。
| 宣言 | int HitCheck_Capsule_Capsule( VECTOR Cap1Pos1, VECTOR Cap1Pos2, float Cap1R, VECTOR Cap2Pos1, VECTOR Cap2Pos2, float Cap2R); | |
| 概要 | カプセルとカプセルの当たり判定 | |
| 引数 | VECTOR Cap1Pos1, Cap1Pos2 | カプセル1を形成する二点 |
| float Cap1R | カプセル1の半径 | |
| VECTOR Cap2Pos1, Cap2Pos2 | カプセル2を形成する二点 | |
| float Cap2R | カプセル2の半径 | |
| 戻り値 | TRUE | 当たっている |
| FALSE | 当たっていない | |
衝突していれば敵からプレイヤーの方向にプレイヤーを押し出し、プレイヤーの座標を更新する。
あとは Player_MoveUpdate() に Player_CollisionEnemy() を追加する。追加する場所はマップとの当たり判定処理をする前。この順番でないと敵に押された際にマップから落とされる可能性がある(代わりに敵にめり込んでしまうが…)。
そして Enemy.cpp/h に衝突処理用のデータを渡す関数を追加する。
Enemy.h
//以下の関数を定義 bool Enemy_GetDataForCollision(VECTOR* EmPos, VECTOR* EmPolyPos1, VECTOR* EmMoveVec);
Enemy.cpp
//敵の情報を他ソースファイルから取得する用
bool Enemy_GetDataForCollision(VECTOR *EmPos, VECTOR *EmPolyPos1, VECTOR *EmMoveVec) {
*EmPos = enemy.Position;
*EmPolyPos1 = enemy.Position;
*EmMoveVec = enemy.MoveVec;
return true;
}
最後にGame.cppの Game_Update() でプレイヤーより敵の情報更新を先にするようにする。
Game.cpp
//更新
void Game_Update() {
//マップ情報更新
Map_Update();
//敵の情報更新
Enemy_Update();
//プレイヤー情報更新
Player_Update();
//略
}
処理自体は3Dマップとの当たり判定処理よりは短く済んでいるが「敵に乗れる」処理を入れるとかなり面倒。専用の当たり判定を作って床との衝突判定を入れる必要があるかと。

コメント