DXライブラリとC言語で3Dゲーム制作。今回は敵との当たり判定の実装。3Dマップとの当たり判定と大体は同じですが細かいところは違います。
敵との当たり判定
前回敵を実装したので今度は敵との当たり判定の実装。流れとしてはこんな感じ。
- プレイヤーから一定範囲内にいる敵がいるか確認
- いればその敵と衝突しているか確認
- 衝突していれば衝突しなくなるまでプレイヤーか敵を押し出す
- プレイヤーか敵の座標を更新
3Dマップとの当たり判定と同じようで違う処理をするので新しく衝突判定関数を書いていく。衝突していた場合敵を押し出すかプレイヤーを押し出すかは自由ですがここではプレイヤーを押し出す方で行きます。
ちなみにマリオみたいに敵と衝突するとダメージを受けてノックバックする、という場合は押し出す処理はせずノックバック処理用の関数を書いてそこでプレイヤーを動かします。
実装
では実装。Player.cppに以下のコードを書く。敵が複数いてかつ管理部を実装している前提です。
Player.cpp
//以下の定義を追加 const static float PLAYER_HIT_RANGE = 3.0f; //プレイヤーから一定範囲内にいるかどうかに使う距離 //プレイヤーと敵の当たり判定 void Player_CollisionEnemy() { int i = 0; int FrameNumber; VECTOR PlPolyPos1, PlPolyPos2, EmPos, EmPolyPos1, EmPolyPos2, EmMoveVec; char PrevJumpStatus = 0; VECTOR PrevPos, NowPos; bool MoveFlag = false; bool RangeFlag = false; bool HitFlag = false; for (int i = 0; i < ENEMY_NUM; i++) { if (EnemyMng_GetDataForCollision(i, &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); // x軸かy軸方向に 0.01f 以上移動した場合は「移動した」フラグを1にする if (fabs(player.MoveVec.x) > 0.01f || fabs(player.MoveVec.z) > 0.01f) { MoveFlag = true; } else { MoveFlag = false; } /*----------敵が一定範囲内にいるか----------*/ { 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; } } } } } //Update関数に敵との当たり判定処理をする関数を追加 void Player_Update() { //中略 //PolyPos2はPolyPos1より指定分高い座標 PolyPos2 = PolyPos1; PolyPos2.y += PLAYER_HIT_HEIGHT; /*----------ここに追加----------*/ Player_CollisionEnemy(); //マップとの当たり判定 Map_CheckCollision(&player.Position, PolyPos1, PolyPos2, player.MoveVec, &player.JumpStatus, &player.JumpPower); //中略 }
まずはプレイヤーおよび敵の座標と当たり判定用の座標、敵の移動ベクトルを取得。そしてプレイヤーの移動後座標と移動しているかのフラグを算出する。
次に敵がプレイヤーが一定範囲内にいるか確認する。ここではプレイヤーの座標を中心とした球の中に敵の座標があるかどうかで判定。
敵が一定範囲内にいたら今度はプレイヤーと敵が衝突しているか確認。ここでお互いのモデルそのもので衝突しているか確認すると処理がとてつもなく重くなるのでカプセル同士で衝突しているか判定する HitCheck_Capsule_Capsule() で処理する。
HitCheck_Capsule_Capsule() だがDXライブラリ置き場のリファレンスにない関数なのでざっと説明するとこんな感じ。
宣言 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_Update() に Player_CollisionDetect() を追加する。追加する場所はマップとの当たり判定処理の前にする。でないと敵に押された際にマップから落とされる可能性がある。
最後に Game.cpp およびEnemyMng.cpp/h を変更する。
EnemyMng.cpp
//以下の関数を追加 //敵のデータを渡す(他のファイルから用) bool EnemyMng_GetDataForCollision(const int i, VECTOR* EmPos, VECTOR* EmPolyPos, VECTOR* MoveVec) { if (i < 0 || i >= ENEMY_NUM) return false; *EmPos = enemy[i].Position; *EmPolyPos = enemy[i].Position; *MoveVec = enemy[i].MoveVec; return true; }
敵との当たり判定処理に必要な情報を渡す関数 EnemyMng_GetDataForCollision()を追加。このとき取得したい敵の番号(i)が負の値またはENEMY_NUM以上だとエラーになるのでその対策を入れている。
EnemyMng.h
#ifndef DEF_ENEMYMNG_H #define DEF_ENEMYMNG_H //以下を追加 bool EnemyMng_GetDataForCollision(const int i, VECTOR* EmPos, VECTOR* EmPolyPos, VECTOR* MoveVec); #endif
Game.cpp
#include "EnemyMng.h" //中略 //更新 void Game_Update() { //マップ情報更新 Map_Update(); //敵の情報更新 EnemyMng_Update(); //プレイヤー情報更新 Player_Update(); //カメラ情報更新 Camera_Update(); //中略 }
プレイヤーが押し出される側ということで敵の更新が先になるようにした。
処理自体は3Dマップとの当たり判定処理よりは短く済んでいるが「敵に乗れる」処理を入れるとかなり面倒。専用の当たり判定を作って床との衝突判定を入れる必要があるかと。