24/03/24
31화. Win32API Collider 활용 & Event Manger (1)
충돌 이벤트 처리 :
충돌 이벤트에 대한 처리는 Object 마다 다를거다.
때문에 충돌 이벤트에 대한 처리는 충돌체를 가지고 있는 Object가 담당하도록 해줘야한다.
모든 객체의 부모 클래스인 CObject 클래스에 충돌 이벤트 처리 함수를 가상함수로 만들어둔다.
충돌체를 가지고 있는 자식 Object 클래스는 이를 override 하여 이벤트 발생시 수행할 작업을 구현해둔다.
충돌이 발생하면 Collider 클래스는 자신을 소유한 Object 클래스의 충돌 이벤트 함수를 호출해준다.
프레임 단위 동기화 처리 :
한 사건에 대해 동일 프레임 안에선 동일한 처리를 해줘야한다.
동일 프레임 안에서 코드 자체는 순차적으로 처리가 되지만, 결과 값은 동시처리된것 처럼 보여야한다.
한 프레임 안에서 a 라는 객체가 어떤 객체는 존재하는 것으로 인식하고 나머지 다른 객체들은 존재하지 않는걸로 인식하게되면 "댕글링 포인터"와 같은 문제가 발생할 수 있다.
"지연처리"를 통해 프레임 동기화처리를 해준다.
모든 데이터가 충분히 update 된 다음 이벤트들에 대한 정보를 모아서 다음 프레임에 적용시켜준다. 그러면 다음 프레임에서 이전 프레임에 발생한 이벤트들을 모두 동시에 적용받는것처럼 처리된다.
이벤트에 대한 정보를 수집하는 "이벤트 매니저 클래스 (EventMgr)" 를 추가해준다.
EventMgr 클래스 :
생성, 추가, 삭제, 화면전환 과 같은 작업 모두 이벤트 처리를 통해 지연처리를 거쳐야 했다.
때문에 이전에 Player 객체에서 Missile 발사를 할때 Missile 객체를 생성해주던 작업도 EventMgr에의해 지연처리가 되어야 했다.
32화. Win32API Event Manger (2)
이벤트 등록 함수 생성 :
프레임 동기화를 위해서 지연처리 방식으로 모든 이벤트를 처리한다.
이를 위해선 해당 프레임에서 발생한 일에 대해서 이벤트처리를 하는 과정이 있어야한다.
이벤트 등록 과정이 자주 발생하기 때문에, 이벤트 등록을 해주는 함수를 별도로 만들어준다.
전역 함수만 모아두는 func.h 파일 추가
DWORD :
Event 구조체에서 Event 관련 추가 데이터를 저장하는 변수의 타입을 DWORD로 정의 했었다.
이는 unsigned long 타입을 의미하며 4Byte에 해당한다.
만약 해당 변수로 64bit 환경의 주소값을 받아주게 되면 문제가 발생할 수 있다. (타입 변경 필요)
플랫폼의 속성에 따라서 크기가 다르게 정의되는 타입도 있다.
대표적인 예가 "DWORD_PTR" 이다.
DWORD_PTR 은 ULONG_PTR로 정의되어 있고
ULONG_PTR 은 64bit 환경에선 unsigned long long 타입으로 32bit 환경에선 unsigned long 타입으로 정의된다.
func.h/cpp :
전역 함수들을 모아둔 파일
CreateObject(CObject*, GROUP_TYPE)
- lParam : 추가해줄 오브젝트의 주소값
- wParam : 추가될 오브젝트가 소속될 그룹타입
_pObj, _eGroup 는 lParam(DWORD_PTR)과 타입이 맞지 않기에 값을 넘겨주려고 하면 컴파일 에러가 발생한다.
때문에 이를 해결하기위해 DWORD_PTR로 캐스팅하여 값을 넘겨준다.
Player 클래스에서 Missile을 생성해주던 CreateMissile()함수에서 Missile 객체를 생성해주던 코드를 CreateObject() 전역함수를 통해 이벤트 추가하는 방식으로 변경해줌
이벤트 매니저의 처리 시점 :
이벤트 매니저의 처리시점은 렌더링 까지 모두 마친 뒤여야한다.
만약 이벤트 매니저의 처리시점이 렌더링 전이었다면, 해당 프레임의 update 과정에서 추가되지 않아 아무런 연산을 받지 않은 객체가 렌더링 과정에서 화면에 그려지기 때문이다.
그렇기 때문에 렌더링까지 다 마친 후 다음 프레임으로 넘어가기 전에 이벤트 매니저의 작업을 처리해줘야한다.
참고로 이벤트의 발생은 상황에 따라 렌더링 과정중 발생할 수도 있고, 충돌하다가도 발생할 수 있다.
즉, 한 프레임 내에서 발생할 수 있는 이벤트는 업데이트, 렌더링 과정에 상관없이 언제든지 등록될 수 있다.
어차피 모든 이벤트는 모여서 해당 프레임 종료전에 EventMgr::update() 함수에 의해서 한번에 처리된다.
오브젝트 삭제 이벤트 :
비교적 간단했던 생성 이벤트와 달리 삭제 이벤트는 복잡하다.
이벤트 매니저에서 삭제할 객체를 바로 삭제해버리면 다음 프레임에서 삭제된 객체를 참조하던 곳에서 "댕글링"이 발생할 위험이 생긴다.
이러한 문제를 해결하기위해 이벤트 매니저에서 우선 삭제할 객체를 바로 삭제하진 않고 객체의 상태를 Dead 상태로 바꿔준다.
다음 프레임의 update() 과정에서 Dead 상태인 객체를 참조하던 곳에선 해당 객체에 대한 참조를 끊어주고, 해당 작업이 종료되면 이벤트 매니저에서 Dead 상태인 객체를 정말로 삭제처리하면된다.
이러한 과정때문에 객체 삭제과정은 객체가 삭제 요청을 보낸 프레임보다 한프레임 뒤에 삭제 처리가 된다.
N번째 프레임 : 삭제 요청 -> (N+1)번째 프레임 : 삭제 예정 -> (N+2)번째 프레임 : 삭제
삭제 이벤트 구현 :
이벤트 매니저의 이벤트 처리 로직에서는 삭제할 객체의 상태를 Dead 상태로 만들어주고 삭제 예정 객체들을 모아두는 컨테이너(m_vecDead)에 넣어준다.
삭제 예정 객체들의 삭제 과정은 EventMgr 클래스의 update() 전에 진행해준다.
해당 위치에서 삭제해주게 되면 이전 프레임에서 Dead 상태로 바뀐 객체들을 삭제처리해주고 새로운 프레임의 이벤트 처리를 수행해줄 수 있게된다.
33화. Win32API Event Manager (3)
오브젝트 삭제처리 :
Dead 상태로 바뀐 오브젝트는 Scene::update() 와 Scene::render() 를 받을 필요가 없어진다.
때문에 update() 와 render() 함수에 분기처리를 해준다.
render 의 경우 이벤트 처리를 제외하면 마지막 작업에 해당하기에 Dead 상태인 객체를 Scene에서 관리하는 객체들을 보관하고 있는 컨테이너에서 빼는 작업도 해줘야한다. (vector<T>::erase())
finalupdate() 과정의 경우 별도의 분기처리를 해주지 않았는데, 그 이유는 finalupdate() 는 객체의 정보를 갱신하는 작업을 담당하는 함수가 아니기 때문이다.
update() 함수에서 갱신된 객체의 정보를 바탕으로 객체가 가지고 있는 component들의 마무리 작업을 해주는 곳이기에 update() 에서 갱신 작업을 하지 않은 오브젝트의 경우 별다른 작업수행이 되지 않는다.
DT값 고정 :
60프레임 이상만 나오면 게임실행에 큰 불편함이 없다.
때문에 60프레임 이상이 나오면 60프레임으로 고정되도록 해준다.
참고로 _DEBUG 에 대한 정의는 프로젝트 속성을 들어가면 확인할 수 있다.
Dead 상태 오브젝트의 충돌 :
finalupdate() 과정은 Object 의 상태가 Dead 상태여도 정상적으로 수행되도록 놔뒀었다.
그러면 Dead 상태인 Object의 Collider와 충돌하는 경우가 생기는데 이에대한 예외 처리를 "충돌 매니저(CCollisionMgr)"에서 해줘야한다.
▼ 코드
#define LEFT_OBJ vecLeft[i]
#define RIGHT_OBJ vecRight[k]
#define LEFT_COLLIDER vecLeft[i]->GetCollider()
#define RIGHT_COLLIDER vecRight[k]->GetCollider()
void CCollisionMgr::CollisionUpdateGroup(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);
map<ULONGLONG, bool>::iterator iter;
for (size_t i = 0; i < vecLeft.size(); ++i)
{
// Left 그룹의 i번째 객체에 충돌체가 없는 경우
if (nullptr == LEFT_COLLIDER)
continue;
for (size_t k = 0; k < vecRight.size(); ++k)
{
// Right 그룹의 k번째 객체에 충돌체가 없는 경우
if (nullptr == RIGHT_COLLIDER)
continue;
// 자기자신과 충돌 비교를 하려는 경우
if (vecLeft[i] == vecRight[k])
continue;
// 두 충돌체 조합 아이디 생성
COLLIDER_ID ID;
ID.Left_id = LEFT_COLLIDER->GetID();
ID.Right_id = RIGHT_COLLIDER->GetID();
// 두 충돌체의 충돌정보 탐색
iter = m_mapColInfo.find(ID.ID);
// 충돌 정보가 미등록 상태인 경우 등록(충돌하지 않았다로 등록)
if (m_mapColInfo.end() == iter)
{
m_mapColInfo.insert(make_pair(ID.ID, false));
iter = m_mapColInfo.find(ID.ID);
}
bool isPrevCol = iter->second;
// 현시점 충돌 체크
if (IsCollision(LEFT_COLLIDER, RIGHT_COLLIDER))
{
// 이전시점 충돌 체크
if (isPrevCol)
{
// 이전에도 충돌했고 현재도 충돌하고 있다.
if (LEFT_OBJ->IsDead() || RIGHT_OBJ->IsDead())
{
// 둘중 하나가 Dead 상태다 (EXIT)
LEFT_COLLIDER->OnCollisionExit(RIGHT_COLLIDER);
RIGHT_COLLIDER->OnCollisionExit(LEFT_COLLIDER);
iter->second = false;
}
else
{
// 둘다 Dead 상태가 아니다. (STAY)
LEFT_COLLIDER->OnCollision(RIGHT_COLLIDER);
RIGHT_COLLIDER->OnCollision(LEFT_COLLIDER);
}
}
else
{
// 이전에는 충돌하지 않았고 현재는 충돌하고 있다. (ENTER)
// 둘중 하나가 Dead 상태다. 충돌하지 않은것으로 취급
if (!LEFT_OBJ->IsDead() && !RIGHT_OBJ->IsDead())
{
// 둘다 Dead 상태가 아니다.
LEFT_COLLIDER->OnCollisionEnter(RIGHT_COLLIDER);
RIGHT_COLLIDER->OnCollisionEnter(LEFT_COLLIDER);
iter->second = true; // 현재 상태가 다음 프레임에선 이전상태가 된다
}
}
}
else
{
// 이전시점 충돌 체크
if (isPrevCol)
{
// 이전에는 충돌했지만 현재는 충돌하지 않고 있다. (EXIT)
LEFT_COLLIDER->OnCollisionExit(RIGHT_COLLIDER);
RIGHT_COLLIDER->OnCollisionExit(LEFT_COLLIDER);
iter->second = false; // 현재 상태가 다음 프레임에선 이전상태가 된다. 갱신해주자
}
else
{
// 이전에도 충돌하지 않았고 현재도 충돌하지 않고 있다. (NONE)
__noop;
}
}
}
}
}
Next Note
Win32API_StudyNote_7
24/03/25 34화. Win32API Scene (1)
coder-qussong.tistory.com
'Win32API' 카테고리의 다른 글
Win32API_StudyNote_8 (0) | 2024.03.25 |
---|---|
Win32API_StudyNote_7 (0) | 2024.03.25 |
Win32API_StudyNote_5 (0) | 2024.03.22 |
Win32API_StudyNote_4 (0) | 2024.03.21 |
Win32API_StudyNote_3 (0) | 2024.03.19 |