충돌체크
충돌검사는
게임 오브젝트끼리 충돌이 일어났는지 검사하고 또 특정 처리를 하는 것을 말한다.
충돌 검사는 여러방식이 존재한다.
Collider
Collider 컴포넌트는 물리 충돌 처리를 위한 오브젝트를 의미합니다.
게임을 만드는 과정에서는 콜라이더가 우리눈에 보이면 편리하지만 실제로 게임이 서비스 될때는 콜라이더는 눈에 보이지 않는 요소가 됩니다.
콜라이더는 꼭 오브젝트와 동일한 모습일 필요는 없으며, 실제로 게임 플레이 시에는 대략적인 근사치 형태로 만들어주는게 더 효율적입니다.
위와 같이 보통은 실제 모델을 대략적으로 표현할수 있는 충돌체(보라색 도형) 크기로 설정합니다.
class Collider : public Component
{
public:
Collider(eColliderType type);
~Collider();
virtual void Initialize();
virtual void Update();
virtual void LateUpdate();
virtual void Render(HDC hdc);
virtual void OnCollisionEnter(Collider* other);
virtual void OnCollisionStay(Collider* other);
virtual void OnCollisionExit(Collider* other);
Vector2 GetOffset() { return mOffset; }
void SetOffset(Vector2 offset) { mOffset = offset; }
UINT32 GetID() { return mID; }
Vector2 GetSize() { return mSize; }
void SetSize(Vector2 size) { mSize = size; }
eColliderType GetColliderType() { return mType; }
private:
static UINT CollisionID;
UINT32 mID;
Vector2 mOffset;
Vector2 mSize;
eColliderType mType;
//Charcater type;
};
class BoxCollider2D : public Collider
{
public:
BoxCollider2D();
~BoxCollider2D();
virtual void Initialize();
virtual void Update();
virtual void LateUpdate();
virtual void Render(HDC hdc);
private:
};
class CircleCollider2D : public Collider
{
public:
CircleCollider2D();
~CircleCollider2D();
virtual void Initialize();
virtual void Update();
virtual void LateUpdate();
virtual void Render(HDC hdc);
private:
float mRadius;
};
JavaScript
복사
Collider 컴포넌트를 상속받아 BoxCollider2D, CircleCollider2D 2개의 컬라이더를 구현 하였습니다.
각각 네모와 원을 이용해 충돌체를 그려주고 있습니다.
전 장까지에서 우리는 충돌체를 구현했다.
그럼 각각의 제작된 충돌체로 어떻게 충돌 처리를 해줄것인가?
충돌처리를 도와줄 CollisionManager를 제작해보도록 하겠다.
union CollisionID
{
struct
{
UINT32 left;
UINT32 right;
};
UINT64 id;
};
class CollisionManager
{
public:
static void Initialize();
static void Update();
static void LateUpdate();
static void Render(HDC hdc);
static void Clear();
static void CollisionLayerCheck(eLayerType left, eLayerType right, bool enable);
static void LayerCollision(class Scene* scene, eLayerType left, eLayerType right);
static void ColliderCollision(Collider* left, Collider* right);
static bool Intersect(Collider* left, Collider* right);
private:
//static bool isBump(Vector2 leftPos, Vector2 rigtPos, Vector2 leftSize, Vector2 rightSize);
private:
static std::bitset<(UINT)eLayerType::Max> mCollisionLayerMatrix[(UINT)eLayerType::Max];
static std::unordered_map<UINT64, bool> mCollisionMap;
};
JavaScript
복사
일단
어떤 충돌체 끼리 충돌을 해줘야 할지 정의하기 위해서 2차원 배열을 사용하겠다.
예를들어 Player 1번 과 Monster 3번이 충돌을 서로 해야하는 존재라면 배열에 다음과 같이 표시하는 것이다.
1 | 2 | 3 | 4 | 5 | 6 | |
1 | true | true | ||||
2 | true | true | true | true | ||
3 | true | true | ||||
4 | true | true | ||||
5 | true | true | ||||
6 | true |
그리고 또한
자세히보면 1,3 과 3,1 이 결론적으로 둘다 같은 충돌을 사용하기 떄문에
다음과 같은 연산으로 두수의 크기를 비교하여 작은 숫자를 row 쪽으로 사용하여
한쪽에만 표시해주기로 하겠다. 결론적으로는 대각선을 기준으로 한쪽만 메모리를 사용될 것이다.
void CollisionManager::CollisionLayerCheck(eLayerType left, eLayerType right, bool enable)
{
int row = 0;
int col = 0;
if (left <= right)
{
row = (UINT)left;
col = (UINT)right;
}
else
{
row = (UINT)right;
col = (UINT)left;
}
mCollisionLayerMatrix[row][col] = enable;
}
JavaScript
복사
충돌할 두 레이어의 정보가 입력 됬다면 매프레임마다 체크해준후 충돌처리를 진행해주면 된다.
void CollisionManager::Update()
{
Scene* scene = SceneManager::GetActiveScene();
for (UINT row = 0; row < (UINT)eLayerType::Max; row++)
{
for (UINT col = 0; col < (UINT)eLayerType::Max; col++)
{
if (mCollisionLayerMatrix[row][col] == true)
{
LayerCollision(scene, (eLayerType)row, (eLayerType)col);
}
}
}
}
JavaScript
복사
LayerCollision에서는 각 레이어의 모든 게임오브젝트들을 가져와서 서로 충돌이 되는지 확인하는 과정을 거친다.
void CollisionManager::LayerCollision(Scene* scene, eLayerType left, eLayerType right)
{
const std::vector<GameObject*>& lefts = SceneManager::GetGameObjects(left);//scene->GetLayer(left)->GetGameObjects();
const std::vector<GameObject*>& rights = SceneManager::GetGameObjects(right); // scene->GetLayer(right)->GetGameObjects();
for (GameObject* left : lefts)
{
if (left->IsActive() == false)
continue;
Collider* leftCol = left->GetComponent<Collider>();
if (leftCol == nullptr)
continue;
for (GameObject* right : rights)
{
if (right->IsActive() == false)
continue;
Collider* rightCol = right->GetComponent<Collider>();
if (rightCol == nullptr)
continue;
if (left == right)
continue;
ColliderCollision(leftCol, rightCol);
}
}
}
JavaScript
복사
충돌에는 총 3가지의 경우가 있다.
충돌이 막 시작한 접점 상태, 서로가 충돌중인 상태, 막 충돌을 빠져나온 상태
void CollisionManager::ColliderCollision(Collider* left, Collider* right)
{
// 두 충돌체 번호로 가져온 ID 확인하여 CollisionID 세팅
CollisionID id = {};
id.left = left->GetID();
id.right = right->GetID();
// 이전 충돌 정보를 검색한다.
// 만약에 충돌정보가 없는 상태라면
// 충돌정보를 생성해준다.
auto iter = mCollisionMap.find(id.id);
if (iter == mCollisionMap.end())
{
mCollisionMap.insert(std::make_pair(id.id, false));
iter = mCollisionMap.find(id.id);
}
// 충돌 체크를 해준다
if (Intersect(left, right))
{
//최초 충돌할떄
if (iter->second == false)
{
left->OnCollisionEnter(right);
right->OnCollisionEnter(left);
iter->second = true;
}
else // 이미 충돌 중
{
left->OnCollisionStay(right);
right->OnCollisionStay(left);
}
}
else
{
//충돌을 하지 않은 상태
if (iter->second == true)
{
left->OnCollisionExit(right);
right->OnCollisionExit(left);
iter->second = false;
}
}
}
JavaScript
복사
각각 충돌체의 타입에 따라서 충돌처리를 진행한다.
bool CollisionManager::Intersect(Collider* left, Collider* right)
{
Transform* leftTr = left->GetOwner()->GetComponent<Transform>();
Transform* rightTr = right->GetOwner()->GetComponent<Transform>();
Vector2 leftPos = leftTr->GetPosition() + left->GetOffset();
Vector2 rightPos = rightTr->GetPosition() + right->GetOffset();
// size 1,1 일떄 기본크기가 100픽셀
Vector2 leftSize = left->GetSize() * 100.0f;
Vector2 rightSize = right->GetSize() * 100.0f;
//AABB 충돌
enums::eColliderType leftType = left->GetColliderType();
enums::eColliderType rightType = right->GetColliderType();
if (leftType == enums::eColliderType::Rect2D
&& rightType == enums::eColliderType::Rect2D)
{
if (fabs(leftPos.x - rightPos.x) < fabs(leftSize.x / 2.0f + rightSize.x / 2.0f)
&& fabs(leftPos.y - rightPos.y) < fabs(leftSize.y / 2.0f + rightSize.y / 2.0f))
{
return true;
}
}
if (leftType == enums::eColliderType::Circle2D
&& rightType == enums::eColliderType::Circle2D)
{
//circle -circle
Vector2 leftCirclePos = leftPos + (leftSize / 2.0f);
Vector2 rightCirclePos = rightPos + (rightSize / 2.0f);
float distance = (leftCirclePos - rightCirclePos).length();
if (distance <= (leftSize.x / 2.0f + rightSize.x / 2.0f))
{
return true;
}
}
if ((leftType == enums::eColliderType::Circle2D && rightType == enums::eColliderType::Rect2D)
|| (leftType == enums::eColliderType::Rect2D && rightType == enums::eColliderType::Circle2D))
{
// circle - rect
// 숙제
}
return false;
}
JavaScript
복사
네모네모 충돌과 원원충돌 네모,원 충돌등이 있다.
각 충돌로직 같은 경우
네모-네모 충돌
원-원 충돌
로직을 사용한다.