본문 바로가기

게임프로그래밍/C++ 게임 클라

[WinApi] 14 - 충돌2

충돌 로직

불필요한 충돌을 방지하고자 그룹을 이용해 충돌을 구현해 보자.

0 ~ 31의 그룹을 만들어서 각 그룹간 충돌 매트릭스를 만들어보았다.

대각선으로 대칭하면 똑같은 것이 된다. 

예를 들면 x: 00 , y:01 = x:01, y:00 이말은 00그룹과 01그룹간 충돌여부와 01그룹과 00그룹간 충돌여부는 같다는 말이다.

대각선 오른쪽 아래 부분은 그래서 사용 안하기로 한다.

x:00, y:00은 자기 자신의 그룹간의 충돌여부인데 일단은... 사용 안하는 쪽으로..

 

  31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00
00[o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][-]
01[o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][-][-]
02[o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][-][-][-]
03[o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][-][-][-][-]
04[o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][-][-][-][-][-]
05[o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][-][-][-][-][-][-]
06[o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][-][-][-][-][-][-][-]
07[o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][-][-][-][-][-][-][-][-]
08[o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][-][-][-][-][-][-][-][-][-]
09[o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][-][-][-][-][-][-][-][-][-][-]
10[o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][-][-][-][-][-][-][-][-][-][-][-]
11[o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][-][-][-][-][-][-][-][-][-][-][-][-]
12[o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][-][-][-][-][-][-][-][-][-][-][-][-][-]
13[o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][-][-][-][-][-][-][-][-][-][-][-][-][-][-]
14[o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-]
15[o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-]
16[o][o][o][o][o][o][o][o][o][o][o][o][o][o][o][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-]
17[o][o][o][o][o][o][o][o][o][o][o][o][o][o][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-]
18[o][o][o][o][o][o][o][o][o][o][o][o][o][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-]
19[o][o][o][o][o][o][o][o][o][o][o][o][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-]
20[o][o][o][o][o][o][o][o][o][o][o][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-]
21[o][o][o][o][o][o][o][o][o][o][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-]
22[o][o][o][o][o][o][o][o][o][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-]
23[o][o][o][o][o][o][o][o][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-]
24[o][o][o][o][o][o][o][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-]
25[o][o][o][o][o][o][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-]
26[o][o][o][o][o][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-]
27[o][o][o][o][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-]
28[o][o][o][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-]
29[o][o][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-]
30[o][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-]
31[-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-][-]

 

05. Scene -> CScene.h

...
public:
	void AddObject(CObject* _pObj, GROUP_TYPE _eType)
	{
		m_arrObj[(UINT)_eType].push_back(_pObj);
	}

	//new: 원본을 복사하는게 아니라 참조하는 방식으로 전달..(변경은 못하게)
	const vector<CObject*>& GetGroupObject(GROUP_TYPE _eType) { return m_arrObj[(UINT)_eType]; }

...

 

 

03. Manager -> CollisionMgr ->  CCollisionMgr.h

#pragma once

//new
Class CCollider;

class CCollisionMgr
{
	SINGLE(CCollisionMgr)

private:
	//new
	// 충돌체 간의 이전 프레임 충돌 정보
	UINT m_arrCheck[(UINT)GROUP_TYPE::END]; // 그룹간의 충돌 체크 매트릭스

...

//new
private:
	void CollisionGroupUpdate(GROUP_TYPE _eLeft, GROUP_TYPE _eRight);
	bool IsCollision(CCollider* _pLeftCol, CCollider* _pRightCol);
};

 

03. Manager -> CollisionMgr ->  CCollisionMgr.cpp

#include "pch.h"
#include "CCollisionMgr.h"

//new
#include "CSceneMgr.h"
#include "CScene.h"

//new
#include "CObject.h"
#include "CCollider.h"

CCollisionMgr::CCollisionMgr()
	: m_arrCheck{}
{

}

CCollisionMgr::~CCollisionMgr()
{

}

void CCollisionMgr::update()
{
	//new
	for (UINT iRow = 0; iRow < (UINT)GROUP_TYPE::END; ++iRow)
	{
		for (UINT iCol = 0; iCol < (UINT)GROUP_TYPE::END; ++iCol)
		{
			if (m_arrCheck[iRow] & (1 << iCol))
			{
				CollisionGroupUpdate((GROUP_TYPE)iRow, (GROUP_TYPE)iCol);
			}
		}
	}
}

//new
void CCollisionMgr::CollisionGroupUpdate(GROUP_TYPE _eLeft, GROUP_TYPE _eRight)
{
	CScene* pCurScene = CSceneMgr::GetInst()->GetCurScene();

	// 반환타입을 지역변수로 넣으면 원본이 복사된다.
	/*vector<CObject*> vecLeft = pCurScene->GetGroupObject(_eLeft);
	vector<CObject*> vecRight = pCurScene->GetGroupObject(_eRight);*/
	// 원본 그대로 받으려면 아래와 같이 사용해야한다.
	const vector<CObject*>& vecLeft = pCurScene->GetGroupObject(_eLeft);
	const vector<CObject*>& vecRight = pCurScene->GetGroupObject(_eRight);

	for (size_t i = 0; i < vecLeft.size(); ++i)
	{
		// 충돌체를 보유하지 않은 경우
		if (nullptr == vecLeft[i]->GetCollider())
		{
			continue;
		}

		for (size_t j = 0; j < vecRight.size(); ++j)
		{
			// 충돌체가 없거나, 자기 자신과의 충돌인 경우
			if (nullptr == vecRight[j]->GetCollider()
				|| vecLeft[i] == vecRight[j])
			{
				continue;
			}

			if (IsCollision(vecLeft[i]->GetCollider(), vecRight[j]->GetCollider()))
			{

			}
			else
			{

			}
		}
	}
}

//new
bool CCollisionMgr::IsCollision(CCollider* _pLeftCol, CCollider* _pRightCol)
{

	return false;
}

void CCollisionMgr::CheckGroup(GROUP_TYPE _eLeft, GROUP_TYPE _eRight)
{
	//new
	// 더 작은 값의 그룹 타입을 행으로,
	// 큰 값을 열(비트)로 사용
	UINT iRow = (UINT)_eLeft;
	UINT iCol = (UINT)_eRight;

	//new
	if (iCol < iRow)
	{
		iRow = (UINT)_eRight;
		iCol = (UINT)_eLeft;
	}

	//new
	if (m_arrCheck[iRow] & (1 << iCol))
	{
		// 뺀다
		m_arrCheck[iRow] &= ~(1 << iCol);
	}
	else
	{
		m_arrCheck[iRow] |= (1 << iCol);
	}
}

 

 

07.Component -> CCollider.h

...
class CCollider
{
private:
	//new
	static UINT g_iNextID;

	CObject* m_pOwner;

	Vec2 m_vOffsetPos;
	Vec2 m_vFinalPos;
	Vec2 m_vScale;

	//new
	UINT m_iID; // 충돌체 고유한 ID 값
	
...

 

07.Component -> CCollider.cpp

...
#include "SelectGDI.h"

//new
UINT CCollider::g_iNextID = 0;

CCollider::CCollider()
	: m_pOwner(nullptr)
	//new
	, m_iID(g_iNextID++)

{

}
...

 

복사 생성자 직접 구현

기본 디폴트 복사 생성자(default copy constructor)는 얕은 복사(shallow copy)를 해준다.

g_iNextID 값도 복사하면 안되고 오너도 복사하면 안된다.

07. Component -> CCollider.h

...
class CCollider
{
...
public:
	CCollider();
	//new: 복사생성자가 호출되어도 아이디는 늘어나야해서 복사생성자를 직접 구현
	CCollider(const CCollider& _origin);

	~CCollider();

	friend class CObject;
};

 

07. Component -> CCollider.cpp

...
CCollider::CCollider()
	: m_pOwner(nullptr)
	, m_iID(g_iNextID++)
{
}

//new
CCollider::CCollider(const CCollider& _origin)
	: m_pOwner(nullptr) // 새로 만들어진 콜라이더는 아직 주인이 없어야한다. 부모를 가르키는건 안됨.
	, m_vOffsetPos(_origin.m_vOffsetPos) // 부모꺼 그대로 받아도 된다.
	, m_vScale(_origin.m_vScale) // 부모꺼 그대로 받아도된다.
	, m_iID(g_iNextID++) // 새로운 키값을 받아야한다.
{

}
...

 

디폴트 대입 연산자 제거

콜라이더는 대입연산을 막을 것이다.

07. Component -> CCollider.h

...
class CCollider
{
private:
...
public:
	void finalupdate();
	void render(HDC _dc);

	//new: 대입연산자는 아예 못쓰도록 막아버리자.
	CCollider& operator = (CCollider& _origin) = delete;
...
};

 

충돌 구분

07. Component -> CCollider.h

...
	Vec2 GetScale() { return m_vScale; }

	//new
	UINT GetID() { return m_iID; }

public:
	void finalupdate();
	void render(HDC _dc);

//new
public:
	// 충돌 시점 함수
	void OnCollision(CCollider* _pOther); // 충돌 중인 경우 호출되는 함수
	void OnCollisionEnter(CCollider* _pOther); // 충돌 진입 시
	void OnCollisionExit(CCollider* _pOther); // 충돌 해제 시

	CCollider& operator = (CCollider& _origin) = delete;

...

 

07. Component -> CCollider.cpp

...

//new
void CCollider::OnCollision(CCollider* _pOther)
{
}

//new
void CCollider::OnCollisionEnter(CCollider* _pOther)
{
}

//new
void CCollider::OnCollisionExit(CCollider* _pOther)
{
}

 

03. Manager -> ColliderMgr -> CColliderMgr.h

#pragma once

class CCollider;

//new
union COLLIDER_ID
{
	struct {
		UINT Left_id; // 4byte
		UINT Right_id; // 4byte
	};
	ULONGLONG ID; // 8 byte
};

class CCollisionMgr
{
	SINGLE(CCollisionMgr)
private:
	//new
	map<ULONGLONG, bool> m_mapColInfo; // 충돌체 간의 이전 프레임 충돌 정보

	UINT	m_arrCheck[(UINT)GROUP_TYPE::END];

public:
	void update();
	void CheckGroup(GROUP_TYPE _eLeft, GROUP_TYPE _eRight);
	void Reset() { memset(m_arrCheck, 0, sizeof(UINT) * (UINT)GROUP_TYPE::END); }

private:
	void CollisionGroupUpdate(GROUP_TYPE _eLeft, GROUP_TYPE _eRight);
	bool IsCollision(CCollider* _pLeftCol, CCollider* _pRightCol);
};

 

03. Manager -> ColliderMgr -> CColliderMgr.cpp

...

void CCollisionMgr::CollisionGroupUpdate(GROUP_TYPE _eLeft, GROUP_TYPE _eRight)
{
	CScene* pCurScene = CSceneMgr::GetInst()->GetCurScene();

	const vector<CObject*>& vecLeft = pCurScene->GetGroupObject(_eLeft);
	const vector<CObject*>& vecRight = pCurScene->GetGroupObject(_eRight);
	//new
	map<ULONGLONG, bool>::iterator iter;

	for (size_t i = 0; i < vecLeft.size(); ++i)
	{
		if (nullptr == vecLeft[i]->GetCollider())
		{
			continue;
		}

		for (size_t j = 0; j < vecRight.size(); ++j)
		{
			if (nullptr == vecRight[j]->GetCollider() || vecLeft[i] == vecRight[j])
			{
				continue;
			}

			//new: 충돌체 2개 구현
			CCollider* pLeftCol = vecLeft[i]->GetCollider();
			CCollider* pRightCol = vecRight[j]->GetCollider();

			//new: 두 충돌체 조합 아이디 생성
			COLLIDER_ID ID;
			ID.Left_id = pLeftCol->GetID();
			ID.Right_id = pRightCol->GetID();

			//new
			iter = m_mapColInfo.find(ID.ID);

			//new: 충돌 정보가 미 등록 상태인 경우 등록(충돌하지 않았다 로)
			if (m_mapColInfo.end() == iter)
			{
				// 충돌정보가 없는 경우 false로 인설트
				m_mapColInfo.insert(make_pair(ID.ID, false));
				iter = m_mapColInfo.find(ID.ID);
			}

			//old
			//if (IsCollision(vecLeft[i]->GetCollider(), vecRight[j]->GetCollider()))
			//new
			if (IsCollision(pLeftCol, pRightCol)) // 현재 충돌 중이다.

			{
            
				//new
				if (iter->second)
				{
					// 이전에도 충돌 하고 있었다.
					pLeftCol->OnCollision(pRightCol);
					pRightCol->OnCollision(pLeftCol);
				}
				else
				{
					// 이전에는 충돌하지 않았다.
					pLeftCol->OnCollisionEnter(pRightCol);
					pRightCol->OnCollisionEnter(pLeftCol);
					iter->second = true;
				}
			}
			else // 현재 충돌하고 있지 않다.
			{
				//new
				if (iter->second)
				{
					// 이전에는 충돌하고 있었다.
					pLeftCol->OnCollisionExit(pRightCol);
					pRightCol->OnCollisionExit(pLeftCol);
					iter->second = false;
				}
			}
		}
	}
}
...

 

실제 충돌 로직 구현

07. Component -> CCollider.h

...
public:
	void SetOffsetPos(Vec2 _vPos) { m_vOffsetPos = _vPos; }
	void SetScale(Vec2 _vScale) { m_vScale = _vScale; }

	Vec2 GetOffsetPos() { return m_vOffsetPos; }
	Vec2 GetScale() { return m_vScale; }

	//new
	Vec2 GetFinalPos() { return m_vFinalPos; }

	UINT GetID() { return m_iID; }

...

 

03. Manager -> ColliderMgr -> CCollisionMgr.cpp

...

void CCollisionMgr::CollisionGroupUpdate(GROUP_TYPE _eLeft, GROUP_TYPE _eRight)
{
	...
}

//new
bool CCollisionMgr::IsCollision(CCollider* _pLeftCol, CCollider* _pRightCol)
{
	Vec2 vLeftPos = _pLeftCol->GetFinalPos();
	Vec2 vLeftScale = _pLeftCol->GetScale();

	Vec2 vRightPos = _pRightCol->GetFinalPos();
	Vec2 vRightScale = _pRightCol->GetScale();

	if (abs(vRightPos.x - vLeftPos.x) < (vLeftScale.x + vRightScale.x) / 2.f
		&& abs(vRightPos.y - vLeftPos.y) < (vLeftScale.y + vRightScale.y) / 2.f)
	{
		return true;
	}

	return false;
}
...

 

테스트를 위해 코드를 조금 변경한다.

05. Scene -> Scene_Start -> CScene_Start.cpp

적은 2명만 나오게 수정한다.

...
void CScene_Start::Enter()
{
	...
	AddObject(pObj, GROUP_TYPE::PLAYER);

	//old
	//int iMonCount = 16;
	//new: 충돌테스트
	int iMonCount = 2;

	...
}
...

 

04. Object -> Monster -> CMonster.cpp

몬스터가 못움직이게 수정한다.

...
void CMonster::update()
{
	//new:충돌테스트
	return;

	Vec2 vCurPos = GetPos();

	vCurPos.x += fDT * m_fSpeed * m_iDir;

	float fDist = abs(m_vCenterPos.x - vCurPos.x) - m_fMaxDistance;

	if (0.f < fDist)
	{
		m_iDir *= -1;
		vCurPos.x += fDist * m_iDir;
	}

	SetPos(vCurPos);
}
...

 

코드 변경후 CCollider.cpp 에서 아래 3개의 메소드에 디버그가 잘 걸리는지 테스트를 해보자

void CCollider::OnCollision(CCollider* _pOther)
{
}

void CCollider::OnCollisionEnter(CCollider* _pOther)
{
}

void CCollider::OnCollisionExit(CCollider* _pOther)
{
}

 

충돌하면 충돌 테두리색이 빨간색으로 변하게 수정

07. Component -> CCollider.h

충돌 판정을 담을 변수 선언

...
class CCollider
{
private:
	...

	UINT m_iID;
	//new
	UINT m_iCol;

...

 

07. Component -> CCollider.cpp

#include "pch.h"
#include "CCollider.h"
#include "CCore.h"
#include "CObject.h"
#include "SelectGDI.h"

UINT CCollider::g_iNextID = 0;

CCollider::CCollider()
	: m_pOwner(nullptr)
	, m_iID(g_iNextID++)
	//new
	, m_iCol(0)
{
}

CCollider::CCollider(const CCollider& _origin)
	: m_pOwner(nullptr)
	, m_vOffsetPos(_origin.m_vOffsetPos)
	, m_vScale(_origin.m_vScale)
	, m_iID(g_iNextID++)
{

}

CCollider::~CCollider()
{
}


void CCollider::finalupdate()
{
	Vec2 vObjectPos = m_pOwner->GetPos();
	m_vFinalPos = vObjectPos + m_vOffsetPos;
}

void CCollider::render(HDC _dc)
{
	//old
	//SelectGDI p(_dc, PEN_TYPE::GREEN);
	//new
	PEN_TYPE ePen = PEN_TYPE::GREEN;

	//new
	if (m_iCol)
		ePen = PEN_TYPE::RED;

	//new
	SelectGDI p(_dc, ePen);

	SelectGDI b(_dc, BRUSH_TYPE::HOLLOW);

	Rectangle(_dc
		, (int)(m_vFinalPos.x - m_vScale.x / 2.f)
		, (int)(m_vFinalPos.y - m_vScale.y / 2.f)
		, (int)(m_vFinalPos.x + m_vScale.x / 2.f)
		, (int)(m_vFinalPos.y + m_vScale.y / 2.f)
	);
}

void CCollider::OnCollision(CCollider* _pOther)
{
}

void CCollider::OnCollisionEnter(CCollider* _pOther)
{
	//new
	m_iCol = true;
}

void CCollider::OnCollisionExit(CCollider* _pOther)
{
	//new
	m_iCol = false;
}

 

 

여러객체와도 충돌이 잘 되게 변경

두객체간의 충돌은 지금까지 로직으로 구현이 되었지만 3개의 충돌체에서는 문제가있다.

두객체간 충돌이 일어나고 세번째 객체가 플레이어와 충돌을한다 가정하고 세번째 객체가 충돌에서 빠져나간다면

플레이어의 콜라이더는 초록색으로 변할 것이다. 아직 첫번째 객체와 충돌중인데도 말이다.

 

07. Component -> CCollider.h

...
class CCollider
{
private:
	static UINT g_iNextID;

	CObject* m_pOwner;

	Vec2 m_vOffsetPos;
	Vec2 m_vFinalPos;
	Vec2 m_vScale;

	UINT m_iID;
	//old
	//UINT m_iCol;
	//new
	int m_iCol;

...

 

07. Component -> CCollider.cpp

#include "pch.h"
#include "CCollider.h"
#include "CCore.h"
#include "CObject.h"
#include "SelectGDI.h"

UINT CCollider::g_iNextID = 0;

CCollider::CCollider()
	: m_pOwner(nullptr)
	, m_iID(g_iNextID++)
	, m_iCol(0)
{
}

CCollider::CCollider(const CCollider& _origin)
	: m_pOwner(nullptr)
	, m_vOffsetPos(_origin.m_vOffsetPos)
	, m_vScale(_origin.m_vScale)
	, m_iID(g_iNextID++)
{

}

CCollider::~CCollider()
{
}


void CCollider::finalupdate()
{
	Vec2 vObjectPos = m_pOwner->GetPos();
	m_vFinalPos = vObjectPos + m_vOffsetPos;

	//new: false일때 에러발생하니까 m_iCol이 음수일때 에러가 발생한다.
	assert(0 <= m_iCol);
}

void CCollider::render(HDC _dc)
{
	PEN_TYPE ePen = PEN_TYPE::GREEN;

	// 0만 아니면 다 true이니까 변경안해도 된다.
	if (m_iCol)
		ePen = PEN_TYPE::RED;

	SelectGDI p(_dc, ePen);
	SelectGDI b(_dc, BRUSH_TYPE::HOLLOW);

	Rectangle(_dc
		, (int)(m_vFinalPos.x - m_vScale.x / 2.f)
		, (int)(m_vFinalPos.y - m_vScale.y / 2.f)
		, (int)(m_vFinalPos.x + m_vScale.x / 2.f)
		, (int)(m_vFinalPos.y + m_vScale.y / 2.f)
	);
}

void CCollider::OnCollision(CCollider* _pOther)
{
}

void CCollider::OnCollisionEnter(CCollider* _pOther)
{
	//old
	//m_iCol = true;
	//new
	++m_iCol;
}

void CCollider::OnCollisionExit(CCollider* _pOther)
{
	//old
	//m_iCol = false;
	//new
	--m_iCol;
}
 
반응형