DXライブラリで3Dマップとの当たり判定(衝突判定)の実装について説明しています。
3Dマップとの当たり判定の基本
どのジャンルにせよ3Dゲームなら大事な処理となる3Dマップとの当たり判定処理。処理の流れは基本以下のようになる。
- 3Dモデル(プレイヤーなど)から一定距離内にある3Dマップのポリゴンを検出
- 検出したポリゴンを壁と床に分ける
- 検出した壁ポリゴンと3Dモデルが衝突しているか走査。衝突していれば壁から押し出す
- 検出した床ポリゴンと3Dモデルが衝突しているか走査。衝突していれば床に着地しているとして床から押し出す(ジャンプ中の場合は天井と衝突していると判断し同様に天井から押し出す)。そうでなければ空中にいると判断
- 最後に3Dモデルの座標を更新する
全くの情報なしでやると1つの難所となるが親切にもDXライブラリ置き場のサンプルコードで基本となる処理を書いてくれているのでそれを踏襲する。
敵やNPCなどとの当たり判定については別記事にて紹介予定。
サンプルコード
それでは実装。3Dモデルを動かすとき同じくと長いコードになるので喰らいついてほしい。
DXライブラリ置き場のサンプルコードをなぞると Player.cpp/h に処理を書き込むことになるが敵やNPCにも同じ処理をするのでマップ関連のソースファイル Map.cpp/h に当たり判定処理を書いて使いまわせるようにしていく。
まず Map.cpp/h に今回必要となる変数を定義していく。
Map.cpp
//以下のファイルをインクルード
#include "DxLib.h"
#include "Input.h"
#include "Map.h"
#include "Player.h"
#include <math.h>
#include "Define.h"
//マップの構造体
typedef struct {
int ModelHandle; //モデルハンドル
VECTOR Position; //マップの座標
}map_t;
map_t map;
const static int OBJ_MAX_HITCOLL = 1024; //処理するコリジョンポリゴンの最大数
const static float PLAYER_ENUM_DEFAULT_SIZE = 5.0f; //周囲のポリゴン検出に使用するサイズ(プレイヤー用)
const static float PLAYER_HIT_WIDTH = 0.5f; //当たり判定カプセルの半径
const static int HIT_TRYNUM = 16; //壁押し出し処理の最大試行回数
const static float HIT_SLIDE_LENGTH = 0.1f; //一度の壁押し出し処理でスライドさせる距離
//マップの初期化
void Map_Initialize() {
//モデルの読み込み
map.ModelHandle = MV1LoadModel("(読み込みたい3Dマップのモデルのパス)");
//座標の初期化
map.Position = VGet(0.0f, 0.0f, 0.0f);
}
//マップの更新
void Map_Update() {
}
//マップの描画
void Map_Draw() {
MV1DrawModel(map.ModelHandle);
}
//マップの終了処理
void Map_Finalize() {
MV1DeleteModel(map.ModelHandle);
}
プレイヤーの情報が必要になるので Player.h を、計算の過程で絶対値を使うので <math.h> をインクルードする。
当たり判定の際に使う定数 MAX_HITCOLL は小さい数にしないこと(処理を軽くするために128とかにするとすり抜けが発生する)。
今回のメインなる当たり判定の処理をする関数 Map_CheckCollision() を Map.cpp/h 書く。
Map.cpp
//マップとの当たり判定
void Map_CheckCollision(VECTOR* Position, VECTOR PolyPos1, VECTOR PolyPos2, VECTOR MoveVec, int* JumpStatus, float* JumpPower)}
//中身は後述
}
Map.h
#ifndef DEF_MAP_H #define DEF_MAP_H void Map_Initialize(); void Map_CheckCollision(VECTOR* Position, VECTOR PolyPos1, VECTOR PolyPos2, VECTOR MoveVec, int *JumpStatus, float* JumpPower); void Map_Update(); void Map_Draw(); void Map_Finalize(); #endif
当たり判定処理を行うのに以下のものが必要になるので引数に指定する。
- プレイヤーなどの3Dモデルの座標
- 当たり判定計算用の座標2つ(足元と頭のイメージ)
- 移動ベクトル
- ジャンプ状態
- ジャンプ力
ここからメイン処理。まずこの関数内で必要となる変数を定義。処理の前に座標をジャンプ状態などを一度保存しておき、3Dモデルの周辺にある3Dマップのポリゴンを検出する。
Map.cpp
//マップとの当たり判定その1
void Map_CheckCollision(VECTOR* Position, VECTOR PolyPos1, VECTOR PolyPos2, VECTOR MoveVec, int* JumpStatus, float* JumpPower) {
//以下の変数や処理を追加
int i, j, k; // 汎用カウンタ変数
bool MoveFlag; // 水平方向に移動したかどうかのフラグ( false:移動していない true:移動した )
bool HitFlag; // ポリゴンに当たったかどうかを記憶しておくのに使う変数( 0:当たっていない 1:当たった )
MV1_COLL_RESULT_POLY_DIM HitDim; // プレイヤーの周囲にあるポリゴンを検出した結果が代入される当たり判定結果構造体
int KabeNum; // 壁ポリゴンと判断されたポリゴンの数
int YukaNum; // 床ポリゴンと判断されたポリゴンの数
MV1_COLL_RESULT_POLY* Kabe[OBJ_MAX_HITCOLL]; // 壁ポリゴンと判断されたポリゴンの構造体のアドレスを保存しておくためのポインタ配列
MV1_COLL_RESULT_POLY* Yuka[OBJ_MAX_HITCOLL]; // 床ポリゴンと判断されたポリゴンの構造体のアドレスを保存しておくためのポインタ配列
MV1_COLL_RESULT_POLY* Poly; // ポリゴンの構造体にアクセスするために使用するポインタ( 使わなくても済ませられますがプログラムが長くなるので・・・ )
HITRESULT_LINE LineRes; // 線分とポリゴンとの当たり判定の結果を代入する構造体
VECTOR PrevPos;
VECTOR NowPos;
char PrevJumpStatus = 0;
float PolyOffset;
//移動前の座標を保存
PrevPos = *Position;
//Poly1とPoly2のY座標の差を保存
PolyOffset = PolyPos2.y - PolyPos1.y;
//ジャンプ状態を保存
PrevJumpStatus = *JumpStatus;
// 移動後の座標を算出
NowPos = VAdd(PrevPos, MoveVec);
// x軸かy軸方向に 0.01f 以上移動した場合は「移動した」フラグを1にする
if (fabs(MoveVec.x) > 0.01f || fabs(MoveVec.z) > 0.01f)
{
MoveFlag = true;
}
else
{
MoveFlag = false;
}
// プレイヤーの周囲にあるステージポリゴンを取得する
// ( 検出する範囲は移動距離も考慮する )
HitDim = MV1CollCheck_Sphere(map.ModelHandle, -1, *Position, PLAYER_ENUM_DEFAULT_SIZE + VSize(MoveVec));
/*****その2に続く*****/
}
| 宣言 | MV1_COLL_RESULT_POLY_DIM MV1CollCheck_Sphere(int MHandle, int FrameIndex, VECTOR CenterPos, float r) | |
| 概要 | 球とモデルの当たり判定 | |
| 引数 | int MHandle | モデルのハンドル |
| int FrameIndex | コリジョンの情報を更新するフレーム番号 | |
| VECTOR CenterPos | 当たり判定で使用する球の中心座標 | |
| float r | 当たり判定で使用する球の半径 | |
| 戻り値 | 当たり判定結果ポリゴン配列構造体 | |
3Dマップのポリゴンを検出したらそれを壁と床に分ける。ここでは検出したポリゴンの法線ベクトルのY成分で分けている。0だと90°の壁、1だと0°の床。0.7で大体45°の坂。別の値にしたい場合は三角比のsinの値を参考に。
//マップとの当たり判定その2
void Map_CheckCollision(VECTOR* Position, VECTOR PolyPos1, VECTOR PolyPos2, VECTOR MoveVec, int* JumpStatus, float* JumpPower) {
/*****その1の続き*****/
// 検出されたポリゴンが壁ポリゴン( XZ平面に垂直なポリゴン )か床ポリゴン( XZ平面に垂直ではないポリゴン )かを判断する
{
// 壁ポリゴンと床ポリゴンの数を初期化する
KabeNum = 0;
YukaNum = 0;
// 検出されたポリゴンの数だけ繰り返し
for (i = 0; i < HitDim.HitNum; i++)
{
// XZ平面に垂直かどうかはポリゴンの法線のY成分の値で判断する
// 角度45度の坂で0.7071...(=sinθ(θは度数)の値)
if (HitDim.Dim[i].Normal.y < 0.7f && HitDim.Dim[i].Normal.y > -0.7f)
{
// 壁ポリゴンと判断された場合でも、プレイヤーのY座標+0.3fより高いポリゴンのみ当たり判定を行う
if (HitDim.Dim[i].Position[0].y > PrevPos.y + 0.3f ||
HitDim.Dim[i].Position[1].y > PrevPos.y + 0.3f ||
HitDim.Dim[i].Position[2].y > PrevPos.y + 0.3f)
{
// ポリゴンの数が列挙できる限界数に達していなかったらポリゴンを配列に追加
if (KabeNum < OBJ_MAX_HITCOLL)
{
// ポリゴンの構造体のアドレスを壁ポリゴンポインタ配列に保存する
Kabe[KabeNum] = &HitDim.Dim[i];
// 壁ポリゴンの数を加算する
KabeNum++;
}
}
}
else
{
// ポリゴンの数が列挙できる限界数に達していなかったらポリゴンを配列に追加
if (YukaNum < OBJ_MAX_HITCOLL)
{
// ポリゴンの構造体のアドレスを床ポリゴンポインタ配列に保存する
Yuka[YukaNum] = &HitDim.Dim[i];
// 床ポリゴンの数を加算する
YukaNum++;
}
}
}
}
/*****その3に続く*****/
}
壁ポリゴンと検出した場合でもプレイヤーのY座標より一定以上高いところにあるもののみ取り扱うのがポイント。これがないと崖から落ちれなかったり、ちょっとした段差も壁扱いになってしまうのでこの処理入れている。
検出したポリゴンを壁と床に分けたら次は壁と衝突しているか走査する。衝突した場合、衝突しなくなるまでか HIT_TRYNUM 回分壁の法線ベクトルに HIT_SLIDE_LENGTH 分移動させ続ける。
//マップとの当たり判定その3
void Map_CheckCollision(VECTOR* Position, VECTOR PolyPos1, VECTOR PolyPos2, VECTOR MoveVec, int* JumpStatus, float* JumpPower) {
/*****その2からの続き*****/
if (KabeNum != 0)
{
// 壁に当たったかどうかのフラグは初期状態では「当たっていない」にしておく
HitFlag = false;
// 移動したかどうかで処理を分岐
if (MoveFlag == true)
{
// 壁ポリゴンの数だけ繰り返し
for (i = 0; i < KabeNum; i++)
{
// i番目の壁ポリゴンのアドレスを壁ポリゴンポインタ配列から取得
Poly = Kabe[i];
// ポリゴンとプレイヤーが当たっていなかったら次のカウントへ
if (HitCheck_Capsule_Triangle(NowPos, VAdd(NowPos, VGet(0.0f, PolyOffset, 0.0f)), PLAYER_HIT_WIDTH, Poly->Position[0], Poly->Position[1], Poly->Position[2]) == FALSE) continue;
// ここにきたらポリゴンとプレイヤーが当たっているということなので、ポリゴンに当たったフラグを立てる
HitFlag = true;
// 壁に当たったら壁に遮られない移動成分分だけ移動する
{
VECTOR SlideVec; // プレイヤーをスライドさせるベクトル
// 進行方向ベクトルと壁ポリゴンの法線ベクトルに垂直なベクトルを算出
SlideVec = VCross(MoveVec, Poly->Normal);
// 算出したベクトルと壁ポリゴンの法線ベクトルに垂直なベクトルを算出、これが
// 元の移動成分から壁方向の移動成分を抜いたベクトル
SlideVec = VCross(Poly->Normal, SlideVec);
// それを移動前の座標に足したものを新たな座標とする
NowPos = VAdd(PrevPos, SlideVec);
}
// 新たな移動座標で壁ポリゴンと当たっていないかどうかを判定する
for (j = 0; j < KabeNum; j++)
{
// j番目の壁ポリゴンのアドレスを壁ポリゴンポインタ配列から取得
Poly = Kabe[j];
// 当たっていたらループから抜ける
if (HitCheck_Capsule_Triangle(NowPos, VAdd(NowPos, VGet(0.0f, PolyOffset, 0.0f)), PLAYER_HIT_WIDTH, Poly->Position[0], Poly->Position[1], Poly->Position[2]) == TRUE) break;
}
// j が KabeNum だった場合はどのポリゴンとも当たらなかったということなので
// 壁に当たったフラグを倒した上でループから抜ける
if (j == KabeNum)
{
HitFlag = false;
break;
}
}
}
else
{
// 移動していない場合の処理
// 壁ポリゴンの数だけ繰り返し
for (i = 0; i < KabeNum; i++)
{
// i番目の壁ポリゴンのアドレスを壁ポリゴンポインタ配列から取得
Poly = Kabe[i];
// ポリゴンに当たっていたら当たったフラグを立てた上でループから抜ける
if (HitCheck_Capsule_Triangle(NowPos, VAdd(NowPos, VGet(0.0f, PolyOffset, 0.0f)), PLAYER_HIT_WIDTH, Poly->Position[0], Poly->Position[1], Poly->Position[2]) == TRUE)
{
HitFlag = true;
break;
}
}
}
// 壁に当たっていたら壁から押し出す処理を行う
if (HitFlag == true)
{
// 壁からの押し出し処理を試みる最大数だけ繰り返し
for (k = 0; k < HIT_TRYNUM; k++)
{
// 壁ポリゴンの数だけ繰り返し
for (i = 0; i < KabeNum; i++)
{
// i番目の壁ポリゴンのアドレスを壁ポリゴンポインタ配列から取得
Poly = Kabe[i];
// プレイヤーと当たっているかを判定
if (HitCheck_Capsule_Triangle(NowPos, VAdd(NowPos, VGet(0.0f, PolyOffset, 0.0f)), PLAYER_HIT_WIDTH, Poly->Position[0], Poly->Position[1], Poly->Position[2]) == FALSE) continue;
// 当たっていたら規定距離分プレイヤーを壁の法線方向に移動させる
NowPos = VAdd(NowPos, VScale(Poly->Normal, HIT_SLIDE_LENGTH));
// 移動した上で壁ポリゴンと接触しているかどうかを判定
for (j = 0; j < KabeNum; j++)
{
// 当たっていたらループを抜ける
Poly = Kabe[j];
if (HitCheck_Capsule_Triangle(NowPos, VAdd(NowPos, VGet(0.0f, PolyOffset, 0.0f)), PLAYER_HIT_WIDTH, Poly->Position[0], Poly->Position[1], Poly->Position[2]) == TRUE) break;
}
// 全てのポリゴンと当たっていなかったらここでループ終了
if (j == KabeNum) break;
}
// i が KabeNum ではない場合は全部のポリゴンで押し出しを試みる前に全ての壁ポリゴンと接触しなくなったということなのでループから抜ける
if (i != KabeNum) break;
}
}
}
/*****その4に続く*****/
}
| 宣言 | int HitCheck_Capsule_Triangle(VECTOR CapPos1, VECTOR CapPos2, float CapR, VECTOR TrianglePos1, VECTOR TrianglePos2, VECTOR TrianglePos3) | |
| 概要 | カプセルと三角形の当たり判定 | |
| 引数 | VECTOR CapPos1 | 当たり判定で使用するカプセルの座標1 |
| VECTOR CapPos2 | 当たり判定で使用するカプセルの座標2 | |
| float CapR | 当たり判定で使用するカプセルの半径 | |
| VECTOR TrianglePos1 | 当たり判定で使用する三角形の座標1 | |
| VECTOR TrianglePos2 | 当たり判定で使用する三角形の座標2 | |
| VECTOR TrianglePos3 | 当たり判定で使用する三角形の座標3 | |
| 戻り値 | TRUE | 当たっている |
| FALSE | 当たっていない | |
| 宣言 | VECTOR VCross( VECTOR In1, VECTOR In2 ) | |
| 概要 | 2つのベクトルの外積を取得 | |
| 引数 | VECTOR In1 | 外積するベクトル1 |
| VECTOR In2 | 外積するベクトル2 | |
| 戻り値 | In1とIn2の外積値 | 正常 |
最後に床と衝突しているか走査する。床と衝突していた場合、衝突した床のポリゴンの中で一番Y座標が高いものを3DモデルのY座標とし、着地をしたとしてジャンプをしていない状態にする。
//マップとの当たり判定その4
void Map_CheckCollision(VECTOR* Position, VECTOR PolyPos1, VECTOR PolyPos2, VECTOR MoveVec, int* JumpStatus, float* JumpPower) {
/*****その3からの続き*****/
// 壁に当たっていたら壁から押し出す処理を行う
if (HitFlag == true)
{
// 壁からの押し出し処理を試みる最大数だけ繰り返し
for (k = 0; k < HIT_TRYNUM; k++)
{
// 壁ポリゴンの数だけ繰り返し
for (i = 0; i < KabeNum; i++)
{
// i番目の壁ポリゴンのアドレスを壁ポリゴンポインタ配列から取得
Poly = Kabe[i];
// プレイヤーと当たっているかを判定
if (HitCheck_Capsule_Triangle(NowPos, VAdd(NowPos, VGet(0.0f, PolyOffset, 0.0f)), PLAYER_HIT_WIDTH, Poly->Position[0], Poly->Position[1], Poly->Position[2]) == FALSE) continue;
// 当たっていたら規定距離分プレイヤーを壁の法線方向に移動させる
NowPos = VAdd(NowPos, VScale(Poly->Normal, HIT_SLIDE_LENGTH));
// 移動した上で壁ポリゴンと接触しているかどうかを判定
for (j = 0; j < KabeNum; j++)
{
// 当たっていたらループを抜ける
Poly = Kabe[j];
if (HitCheck_Capsule_Triangle(NowPos, VAdd(NowPos, VGet(0.0f, PolyOffset, 0.0f)), PLAYER_HIT_WIDTH, Poly->Position[0], Poly->Position[1], Poly->Position[2]) == TRUE) break;
}
// 全てのポリゴンと当たっていなかったらここでループ終了
if (j == KabeNum) break;
}
// i が KabeNum ではない場合は全部のポリゴンで押し出しを試みる前に全ての壁ポリゴンと接触しなくなったということなのでループから抜ける
if (i != KabeNum) break;
}
}
}
if (YukaNum != 0)
{
// ジャンプ中且つ上昇中の場合は処理を分岐
if ((PrevJumpStatus == OBJ_JUMP) && *JumpPower > 0)
{
float MinY;
// 天井に頭をぶつける処理を行う
// 一番低い天井にぶつける為の判定用変数を初期化
MinY = 0.0f;
// 当たったかどうかのフラグを当たっていないを意味する0にしておく
HitFlag = false;
// 床ポリゴンの数だけ繰り返し
for (i = 0; i < YukaNum; i++)
{
// i番目の床ポリゴンのアドレスを床ポリゴンポインタ配列から取得
Poly = Yuka[i];
// 足先から頭の高さまでの間でポリゴンと接触しているかどうかを判定
LineRes = HitCheck_Line_Triangle(NowPos, VAdd(NowPos, VGet(0.0f, PolyOffset, 0.0f)), Poly->Position[0], Poly->Position[1], Poly->Position[2]);
// 接触していなかったら何もしない
if (LineRes.HitFlag == FALSE) continue;
// 既にポリゴンに当たっていて、且つ今まで検出した天井ポリゴンより高い場合は何もしない
if (HitFlag == true && MinY < LineRes.Position.y) continue;
// ポリゴンに当たったフラグを立てる
HitFlag = true;
// 接触したY座標を保存する
MinY = LineRes.Position.y;
}
// 接触したポリゴンがあったかどうかで処理を分岐
if (HitFlag == true)
{
// 接触した場合はプレイヤーのY座標を接触座標を元に更新
NowPos.y = MinY - PolyOffset;
// Y軸方向の速度は反転
*JumpPower = *JumpPower * -1.0f;
}
}
else
{
float MaxY;
// 下降中かジャンプ中ではない場合の処理
// 床ポリゴンに当たったかどうかのフラグを倒しておく
HitFlag = false;
// 一番高い床ポリゴンにぶつける為の判定用変数を初期化
MaxY = 0.0f;
// 床ポリゴンの数だけ繰り返し
for (i = 0; i < YukaNum; i++)
{
// i番目の床ポリゴンのアドレスを床ポリゴンポインタ配列から取得
Poly = Yuka[i];
// ジャンプ中かどうかで処理を分岐
if (PrevJumpStatus == OBJ_JUMP || PrevJumpStatus == OBJ_FALL)
{
// ジャンプ中の場合は頭の先から足先より少し低い位置の間で当たっているかを判定
LineRes = HitCheck_Line_Triangle(VAdd(NowPos, VGet(0.0f, PolyOffset, 0.0f)), VAdd(NowPos, VGet(0.0f, -0.2f, 0.0f)), Poly->Position[0], Poly->Position[1], Poly->Position[2]);
}
else
{
// 走っている場合は頭の先からそこそこ低い位置の間で当たっているかを判定( 傾斜で落下状態に移行してしまわない為 )
LineRes = HitCheck_Line_Triangle(VAdd(NowPos, VGet(0.0f, PolyOffset, 0.0f)), VAdd(NowPos, VGet(0.0f, -0.3f, 0.0f)), Poly->Position[0], Poly->Position[1], Poly->Position[2]);
}
// 当たっていなかったら何もしない
if (LineRes.HitFlag == FALSE) continue;
// 既に当たったポリゴンがあり、且つ今まで検出した床ポリゴンより低い場合は何もしない
if (HitFlag == true && MaxY > LineRes.Position.y) continue;
// ポリゴンに当たったフラグを立てる
HitFlag = true;
// 接触したY座標を保存する
MaxY = LineRes.Position.y;
}
// 床ポリゴンに当たったかどうかで処理を分岐
if (HitFlag == true)
{
// 当たった場合
// 接触したポリゴンで一番高いY座標をプレイヤーのY座標にする
NowPos.y = MaxY;
// Y軸方向の移動速度は0に
*JumpPower = 0.0f;
// もしジャンプ中だった場合は着地状態にする
if (PrevJumpStatus == OBJ_JUMP || PrevJumpStatus == OBJ_FALL)
{
*JumpStatus = OBJ_NO_JUMP;
}
}
else
{
// 床コリジョンに当たっていなくて且つジャンプ状態ではなかった場合は
if (PrevJumpStatus == OBJ_NO_JUMP)
{
// 落下中にする
*JumpStatus = OBJ_FALL;
// ちょっとだけ落下する
*JumpPower = -0.01f;
}
}
}
}
/*****その5に続く*****/
}
| 宣言 | HitCheck_Line_Triangle(VECTOR LinePos1, VECTOR LinePos2, VECTOR TrianglePos1, VECTOR TrianglePos2, VECTOR TrianglePos3) | |
| 概要 | 直線と三角形の当たり判定 | |
| 引数 | VECTOR LinePos1 | 線分の端点1 |
| VECTOR LinePos2 | 線分の端点2 | |
| VECTOR TrianglePos1 | 当たり判定で使用する三角形の座標1 | |
| VECTOR TrianglePos2 | 当たり判定で使用する三角形の座標2 | |
| VECTOR TrianglePos3 | 当たり判定で使用する三角形の座標3 | |
| 戻り値 | TRUE | 当たっている |
| FALSE | 当たっていない | |
ジャンプかつ上昇中だった場合は天井に衝突したということになるのでその場合は天井のY座標から PolyOffset 分だけ下のY座標を3DモデルのY座標とし、JumpPower を反転させる。
床に衝突していなかった場合は空中にいることになるので落下状態にして JumpPower も設定する。
最後に当たり判定処理後の座標を更新し、検出したポリゴン情報を開放する。ポリゴン情報の開放を忘れるとメモリが圧迫するので絶対に開放すること。
//マップとの当たり判定その5
void Map_CheckCollision(VECTOR* Position, VECTOR PolyPos1, VECTOR PolyPos2, VECTOR MoveVec, int* JumpStatus, float* JumpPower) {
/*****その4からの続き*****/
//座標を更新する
*Position = NowPos;
// 検出したモデルの周囲のポリゴン情報を開放する
MV1CollResultPolyDimTerminate(HitDim);
}
| 宣言 | int MV1CollResultPolyDimTerminate( MV1_COLL_RESULT_POLY_DIM ResultPolyDim ) | |
| 概要 | 当たり判定結果のポリゴン配列の後始末をする | |
| 引数 | MV1_COLL_RESULT_POLY_DIM ResultPolyDim | 当たり判定結果のポリゴン配列構造体 |
| 戻り値 | 0 | 成功 |
| -1 | エラー発生 | |
そしてPlayer.cppに当たり判定処理を行う関数と必要な準備を書く。
//プレイヤーの移動処理
void Player_MoveUpdate() {
//座標の更新まで略
//座標の更新
NowPos = VAdd(player.Position, MoveVec);
//当たり判定処理
{
//当たり判定に使用するための下準備
VECTOR PolyPos1, PolyPos2;
//PolyPos1を求める
PolyPos1 = player.Position;
//PolyPos2はPolyPos1より指定分高い座標
PolyPos2 = PolyPos1;
PolyPos2.y += PLAYER_HIT_HEIGHT;
//マップとの当たり判定処理を行う
Map_CheckCollision(&NowPos, PolyPos1, PolyPos2, MoveVec, &player.JumpStatus, &player.JumpPower);
}
player.Position = NowPos;
//プレイヤーの座標の更新
MV1SetPosition(player.ModelHandle, player.Position);
}
最後にGame.cppにマップ関連の処理を追加する。
注意事項
今回DXライブラリ置き場のサンプルコードを参考にしたが、そこにも書いてある通り90度以下の鋭角な角に入り込んだ場合すり抜けやハマリが発生する場合がある。
DXライブラリもしくはコードのバグに思うかもしれないが、Unityでも同じように90度以下の鋭角な角ですり抜けやハマリが発生するのを確認しているので3Dモデルの移動速度や当たり判定の大きさ、押し出しの距離などが複合的に絡んでこうなっていると思われる。
ゲームによっては鋭角な角がないマップを作ることで回避できるが、無理な場合は一度実装したら鋭角な角に衝突してもすり抜けない、ハマらない値をがんばって見つけましょう。

コメント