Deeper Learning

[WinAPI] Collider 구현 (1) - Collider Component 본문

Game Development/WinAPI

[WinAPI] Collider 구현 (1) - Collider Component

Dlaiml 2024. 6. 9. 21:15

게임 개발에서 Collider는 핵심적인 역할을 한다. 물리적 충돌을 감지하고 그 결과를 처리하는 데 사용되는 컴포넌트로 오브젝트의 상호작용을 담당한다. 캐릭터가 바닥을 딛고 서있을지, 벽에 부딪힐지 같은 눈에 보이는 판정뿐만 아니라 카메라에 보여야 하는지, 적의 공격을 맞게 되는지 또한 Collider의 충돌 판정에 의해 결정된다.

WinAPI로 게임을 본격적으로 만들기 전에 거의 모든 게임 장르에서 사용되는 필수 요소인 Collider의 구현에 대해 다뤄보려 한다.

 

충돌O / 충돌X

 

Collider.h

 

우선 헤더파일은 아래와 같다.

#pragma once

class CObject;

class CCollider
{
private:

	static UINT g_iNextID;

	CObject* m_pOwner;
	Vec2 m_vOffsetPos;
	Vec2 m_vCollPos;
	Vec2 m_vScale;

	UINT m_iID;
	int m_iColl;

	bool m_bActive;

public:
	// ...
	// Getter & Setter
    // ...

	void PostUpdate();
	void Render(HDC _hdc);

	void OnCollision(CCollider* _pOther);
	void OnCollisionBegin(CCollider* _pOther);
	void OnCollisionEnd(CCollider* _pOther);

	// ...
};

 

먼저 Object 클래스를 상속받지 않는 이유는 Collider를 Component Pattern으로 구현하였기 때문이다.

개념상 상위(실제 상속여부가 아닌) 클래스에 기능을 끼워 넣는 디자인 패턴인 Component Pattern은 Decoupling을 통해 다이아몬드 상속 등 복잡한 문제를 회피할 수 있으며 코드의 유연성을 크게 증가시켜 준다. 독립적으로 특정 기능의 코드가 모여있기 때문에 재사용성 또한 크게 강화된다.

 

Collider는 모양, 크기, 위치를 멤버로 가지고 있어야 하며 충돌이 일어나는 시점에 따라 로직을 수행할 수 있도록 해야 한다.

우선 멤버를 하나씩 살펴보자.

  • m_pOwner: Collider를 소유한 Object를 가리키는 포인터
  • m_vOffsetPos: 소유 Object의 위치에서 얼마나 떨어져 있는지 나타내는 2차원 벡터
  • m_vCollPos: Offset 조정 등 여러 로직을 수행한 후 최종 Collider 위치
  • m_vScale: Collider 크기
  • m_iID: 충돌 판정을 위한 고유 ID (충돌 판정은 다음 포스팅에 이어 다루겠습니다)
  • m_iColl: 충돌 횟수 (충돌 판정 관련 멤버)
  • m_bActive: 충돌 활성화 상태  

 

Object에 Collider Component 부착 -> 매 Tick에 Collider를 소유한 Object 끼리의 충돌 판정 -> 충돌 상태에 따른 Collider의 콜백 함수 호출(Begin, Ongoing, End) ->  함수가 호출된 Collider를 소유한 Object에서 로직 수행 (ex.투사체에 맞아 체력 감소) 

 

간략하게 Collider 부착부터 충돌 로직 처리까지를 요약하면 위와 같은데, 이번 포스팅은 Collider 클래스 내부의 함수의 구현에 대해 집중해 보자.

 

충돌 처리 및 충돌 상태 관리는 Singleton 패턴으로 구현한 매니저 클래스가 따로 관리를 하고 있어 다음 포스팅에서 이어 소개하겠다.

 

(Manager Class 소개 포스팅)

 

[WinAPI] PeekMessage, Core Class 설계 (슈팅게임)

Win32API를 활용해서 간단한 슈팅게임을 만드려고 한다.  1. GetMessage -> PeekMessage2. Core Class 소개 및 전체 클래스 설계 요약 GetMessage -> PeekMessage 이전 포스팅에서 설명하였던 것처럼 메시지 루프에

dlaiml.tistory.com

 

 

Collider.cpp

UINT CCollider::g_iNextID = 0;

void CCollider::PostUpdate()
{
	Vec2 vOwnerPos = m_pOwner->GetPos();
	m_vCollPos = vOwnerPos + m_vOffsetPos;
	assert(m_iColl >= 0);
}

void CCollider::Render(HDC _hdc)
{
 	// ...
    
    
	Vec2 vRenderPos = CCamera::GetInstance()->ScreenPosToRenderPos(m_vCollPos);

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

void CCollider::OnCollision(CCollider* _pOther)
{
	m_pOwner->OnCollision(_pOther);
}

void CCollider::OnCollisionBegin(CCollider* _pOther)
{
	++m_iColl;
	m_pOwner->OnCollisionBegin(_pOther);
}

void CCollider::OnCollisionEnd(CCollider* _pOther)
{
	--m_iColl;
	m_pOwner->OnCollisionEnd(_pOther);
}

 

PostUpdate 함수는 개발 중인 WinAPI 설계에서 Update 이후 시점에 호출되는 함수이다. Update 시점에 Object의 위치가 업데이트되면 PostUpdate에서 Collider는 Object의 위치를 받아 Offset에 따라 자신의 위치를 조정하도록 구현하였다.

 

디버깅을 위해 Render 함수에 크기에 맞는 사각형을 그리도록 구현하였다. 다양한 모양의 Collider(캡슐, 구)를 디버깅하기 위해서는 Collider를 상속받아 하위 클래스를 만들고 Render 함수를 Override 하여야 한다.

 

나머지 충돌 시점과 관련된 세 함수는 (OnCollisionBegin, OnCollision, OnCollisionEnd) Owner인 Object의 함수를 각각 호출하고 있다. 충돌한 상대 Collider를 매개변수로 받기 때문에 충돌한 Object의 특정이 가능하다.

 

Object 클래스에서는 하위 클래스의 특성에 따라 충돌에 따른 처리를 각각 구현할 수 있도록 가상함수로 세팅하였다. 

// CObject.h
virtual void OnCollision(CCollider* _pOther) {};
virtual void OnCollisionBegin(CCollider* _pOther) {};
virtual void OnCollisionEnd(CCollider* _pOther) {};

 

 

예시로 Object를 상속받았고 체력, 애니메이션, Collider, AI 등 기능이 추가된 Enemy 클래스의 코드를 보자.

OnCollisionBegin(충돌 시작시점) 함수에 Player 클래스에서 생성한 Projectile(PlayerBullet)과 충돌할 경우 체력이 줄어들고 체력이 0 이하로 내려갈 경우 Object가 삭제되는 기능을 아래와 같이 구현하였다.

void CEnemy::OnCollisionBegin(CCollider* _pOther)
{
	CObject* pOtherObj = _pOther->GetOwner();

	if (pOtherObj->GetName() == L"PlayerBullet")
	{
		m_tInfo.fHP -= 1;
		if (m_tInfo.fHP <= 0)
		{
			DeleteObject(this);
		}
	}
}

 

 

코드는 [1]의 유튜브 영상을 예시로 참고하였다.

Reference

[0] https://learn.microsoft.com/ko-kr/windows/win32/api/

[1] https://www.youtube.com/playlist?list=PL4SIC1d_ab-ZLg4TvAO5R4nqlJTyJXsPK

Comments