24/03/22
25화. Win32API Collider (1)
충돌 :
충돌 작용없이 게임을 만드려고 하면, 제작할 수 있는 게임의 폭이 엄청 줄어든다.
충돌의 조건이 "충돌하면" 뿐이면 충돌에 대한 상호작용의 결과가 애매해질 수 있다.
때문에 충돌에 디테일한 상태와 조건이 필요하다.
충돌 상태의 종류에는 "Enter(충돌진입) , Stay(충돌유지) , Exit(비충돌)" 3가지가 있다.
설계 :
충돌 이벤트가 필요한 객체가 있고 필요 없는 객체가 있다.
클래스 설계를 할때 확장성 있는 구조를 만들려고 해야한다.
대표적인 확장성 있는 구조로는 "Component 기반 구조"가 있다.
Component는 필요할때 끼워넣을수 있는 부품과도 같은 클래스(객체)다.
final update() :
신경써주지 않아도 내부적으로 돌아가는 update()
엔진 구조상 매 프레임마다 필수적으로 처리되어야하는 일들을 담당한다.
update() 함수처럼 CObject 객체들의 값을 조하진 않는다.
update() 함수에서 작업했던 것들을 확정지어주는 역할을 한다.
대표적인 일 : 충돌체가 Object를 따라가도록 하는 일
CObject::finalupdate() :
Object 클래스의 finalupdate() 는 자식 객체들이 override 하지 못하도록 막아줘야한다.
finalupdate() 함수는 "SceneMgr > Scene > Object > Collider" 클래스 순서로 호출되고
최종적으로 Object에서 Collider의 finalupdate()를 호출해줌으로써 Collider의 위치가 Object의 위치로 이동하게된다.
그런데 Object의 finalupdate() 함수를 가상함수로 만들어 자식객체들이 호출하게 하면 모든 자식 객체들이 finalupdate() 안에서 Collider::finalupdate()를 호출해줘야 할 뿐더러 애초에 finalupdate()는 override 하라고 만든 함수가 아니다.
하지만, 다른 사람이 이러한 원칙을 모르고 그냥 코드만 봐선 알아차리기 힘들 수 있다.
때문에 일부러 finalupdate() 함수를 virtual로 만들고 뒤에 final 키워드를 붙여준다.
final 키워드를 붙여주면 자식객체들이 override를 할 수 없도록 해준다.
// case1
void finalupdate();
// 코드만 봐선 override 하면 안된다는걸 알아차릴 수 없다.
// case2
virtual void finalupdate() final;
// 코드만 봐선 override 하면 안된다는걸 알 순 없어도
// override를 할 수 없다는걸 알 수 있다.
26화. Win32API Collider (2)
충돌체의 위치 :
"Collider를 소유하고 있는 Object의 위치 = Collider의 위치" 라고 생각해도 되긴 하지만, 이렇게 되면 충돌체를 유연하게 사용할 수 없다.
Object의 특정 위치만 충돌 판정을 하고싶은 경우도 생길 수 있다.
때문에 별도의 위치를 따로 가지도록 해줘야한다.
"Offset"은 어떤 기준위치로부터의 상대적인 거리값을 의미한다.
Collider는 소유자(Object)의 Pos를 기준으로 Offset 만큼 이동한 거리에 위치하도록 해준다.
Collider::render() :
충돌체도 눈에 보이는게 있어야 구체적으로 충돌이 진행되는지 시각적으로 확인하기 편하다.
때문에 render() 함수 생성해준다.
디버깅 모드에선 충돌체 출력 해주다가, 릴리즈모드에서 끄면 된다.
또한 Object 에서는 Collider 외에도 추가되는 Component들 중 Render가 필요한 Component가 있을수 있음을 감안해서 "component_render()" 함수를 별도로 만들어준다.
CObject::component_render() :
component_render() 는 함수의 이름처럼 component 들의 렌더링을 담당해주는 함수다.
굳이 render() 함수가 있음에도 따로 만들어준 이유는...
CObject 클래스의 자식객체들 중 render() 함수를 override 해서 사용하는 클래스가 있다.
만약, render_component() 함수를 따로 만들지 않고 render() 함수에 코드로 작성해두었다면, 자식객체들이 render() 함수를 override 하는 순간 자식객체의 렌더링 작업 코드 외에도 부모 클래스의 render() 함수에 있던 component 의 렌더링 코드도 다시 작성해줘야한다.
이러한 번거로움을 피하기 위해 render_component() 함수를 만들어 함수 호출 한번으로 해당 과정을 대체할 수 있도록 해준다.
충돌체 그리기 :
충돌체가 Object의 이미지를 가려버리면 안되기에 테두리만 그려주도록 해야한다.
HOLLOW_BRUSH 를 사용해서 Rectangle 을 그려준다.
GDI 오브젝트 저장 :
자주 사용하는 GDI Object (PEN, BRUSH, etc...) 를 미리 만들어두고
필요할때마다 미리 만들어진 객체를 가져다가 사용하는 방식으로 구현
Core 클래스에서 배열 형태로 관리하도록 한다.
하지만 사용하기엔 여전히 불편한 감이 있다...
SelectGDI 클래스 생성 :
사용하기 번거로운 GDI 객체를 편하게 사용하기 위한 클래스
멤버로 PEN 또는 BRUSH 를 변경할 DC, 변경되고 반환되는 기본 PEN 과 BRUSH 객체를 저장할 멤버변수들로 구성되어 있다.
두개의 생성자에서 각각 PEN, BRUSH 을 받아서 DC의 PEN 과 BRUSH를 변경해준다.
소멸자에서 변경되었던 PEN 과 BRUSH를 원상복구해준다.
사용법 :
결과 :
27화. Win32API Collider (3)
충돌 처리 :
모든 객체들을 서로 충돌 검사하게되면 엄청난 경우의 수가 나온다.
즉, 불필요한 계산을 하게된다.
이를 해결하기 위해 그룹별롤 검사를 진행하도록 설계해준다.
그럼 서로 충돌 처리를 하겠다고 설정한 그룹들끼리만 충돌검사를 진행하게된다.
이를 위해선 생성하는 객체들을 적절한 그룹에 포함시켜줘야한다.
CollisionMgr 클래스 :
오브젝트 그룹 충돌을 관리해주는 매니저 클래
CollisionMgr를 update 하기전에 SceneMgr를 update 해야한다. 해당 순서를 잘 맞춰주도록 하자
현재 32개의 Group을 생성할 수 있으며, 충돌 처리 체크를 할 때 비트값으로 체크를 한다고 가정하면 32개의 비트 즉, 4Byte 정수 32개로 모든 충돌 체크를 관리할 수 있다.
충돌처리를 비트연산으로 계산할때 아래의 이미지를 참고하자
또한 값을 비교할 때 행과 열의 값중 열의 값이 더 커야한다. (열(row) > 행렬(column))
이유 : 대칭 구조이기 때문에 삼각형의 위, 아래 둘중 한 곳의 값만 채워주면 되는데 우리는 윗삼각형을 사용할 예정이기 때문이다.
memset() :
메모리의 내용(값)을 원하는 크기만큼 특정 값으로 세팅하는 함수
memory + setting = memset
void *memset(
void *dest, // 세팅하고자 하는 메모리의 시작주소
int c, // 메모리에 세팅할 값
size_t count // 길이 = 데이터타입의 사이즈 * 개수
);
24/03/23
28화. Win32API Collider (4) / 29화. Win32API Collider (5)
CollisionMgr::CheckGroup() :
충돌 매니저가 충돌 체크를 할 레이어들을 선택해주는 기능인 CheckGroup() 함수를 구현함
내부적으로 들어온 타입의 enum 값이 큰쪽을 "열", 작은 쪽을 "행"으로 보고 연산을 진행한다.
적은 메모리를 차지하기 위해 비트연산으로 진행함
만약 이미 해당 위치의 비트 플래그가 켜져있다면 비트 플래그의 값을 끄고
비트 플래그의 값이 꺼져있다면 켜주는 방식으로 동작한다.
비트 플래그를 켜는 방식 :
"bitwise or (|)" 연산 활용
unsigned char a = 0; // 00000000
a |= (1 << 3); // 00000000 | 00001000 = 000001000
비트 플래그 끄는 방식 :
"bitwise not (~)" 연산 활용
unsigned char a = 0;
a |= (1 << 3);
a &= ~(1 << 3); // 000001000 & ~(000001000)
// = 000001000 & 111110111
// = 00000000
CollisionMgr::CollisionUpdateGroup() :
충돌 매니저의 update() 함수에서 충돌 비교를 하기로한 레이어들끼리 충돌여부 비교를 진행한다.
각각의 Group에 포함된 Object 끼리의 비교는 CollisionUpdateGroup() 함수에 구현되어 있다.
( CollisionUpdateGroup() 함수 파라미터 조건 : _eLeft < _eRight )
SceneMgr::GetObject()로 부터 충돌 체크할 그룹의 객체들을 받아오는데, 이때 참조자 지역변수로 받아야 원본객체들을 받아올 수 있음에 유의하자.
참조자로 반환하는 값은 참조자로 받아줘야 원본을 다룰 수 있게된다.
일반 변수로 받아주면 복사된 값을 받아온다.
주의하도록 하자!
#include <iostream>
using namespace std;
void Add1(int& i)
{
int& temp = i;
temp += 1;
}
void Add2(int& i)
{
int temp = i;
temp += 1;
}
int main()
{
int test = 1;
cout << test << endl; // 1
Add1(test);
cout << test << endl; // 2
Add2(test);
cout << test << endl; // 2
return 0;
}
충돌 연산 :
충돌 연산시 각종 이벤트를 디테일하게 넣기 위해선 이전 프레임에서의충돌 상태를 가지고 있어야한다.
방대한 양의 경우의 수와 데이터를 다루게 되기에 "탐색"에 특화되어 있는 구조체를 사용해야한다.
map, hash table, binary search, etc...
충돌연산 데이터 컨테이너에 사용할 키값 생성 :
충돌연산 데이터를 map 컨테이너를 활용하여 저장할 예정이다.
두 충돌체간의 데이터에 대한 키 값을 생성할때 절대 겹치지 않는 키 값을 생성하는 방법을 모색해야한다.
각각의 객체들이 소유한 Collider 객체 서로 겹칠 수 없는 아이디 값을 부여하고 충돌하는 객체들끼리 값을 합쳐주면 절대 겹치지 않는 두 충돌체만의 키값이 생성된다. -> Collider 클래스에 ID값 멤버로 추가
이 때문에 복사생성자를 직접 정의해줘야한다.
왜냐하면 기본 복사 생성자를 통해 객체가 생성되면 별도의 충돌체임에도 불구하고 동일한 아이디 값을 가지게 되어 구분을 할 수 없게되기 때문이다.
- m_pOwner(nullptr) : 복사되긴 했지만 별개의 충돌체이기에 nullptr 로 초기화해준다.
- m_vFinalPos{} : 최종 위치는 매 프레임마다 새로 계산되기 때문에 굳이 값을 가져올 필요가 없다.
또한, 충돌체는 어디에 대입되거나 할 필요가 없기 때문에 문제가 발생되기 전에 미리 "대입 연산자"를 막아준다.
LARGE_INTEGER :
union 을 사용한 대표적인 타입중 하나
union은 구조체와 유사하긴 하나 한가지 union 만의 특징이 있다.
구조체와 달리 union 은 구조체 내에 가장큰 크기의 메모리를 각 요소들이 공유한다.
LARGE_INTEGER를 다시보면 DWRD, LONG 두 타입을 묶어서 u 라는 구조체 하나를 만들고 QuardPart 라는 LONGLONG타입 변수를 union으로 묶어줬다.
때문에 LARGE_INTEGER 안의 데이터는 u 또는 QuardPart 라고 지칭하여 호출 할 수 있다.
충돌 로직 구체화 :
현재 프레임과 이전 프레임에서의 충돌여부를 모두 확인하여
디테일한 충돌 이벤트(enter, stay, exit, none)를 발생시킬 수 있도록 구현
30화. Win32API Collider (6)
충돌 검사 로직 구현 :
인자로 들어온 두 충돌체의 충돌여부 검사 로직을 구현해야한다.
충돌체의 충돌여부 검사를 위해선 충돌체의 최종 위치를 가져와서 비교해야한다.
즉, finaltick에 의해 충돌체를 소유한 객체의 위치로 충돌체가 이동한 후의 위치값을 가져와 비교한다는거다.
Collider의 형태는 Circle 이지만 구현 로직은 Rect-Rect 충돌검사라 흠이 있긴하지만 간단하게 충돌검사 로직을 구현하였다.
충돌중이면 Collider 색상이 붉은색으로 변한다.
참고로 충돌여부는 Collider 클래스에 충돌 횟수를 담는 멤버변수(m_iColCnt)를 추가하여 "충돌 진입함수 (OnCollisionEnter)"와 "충돌 탈출함수 (OnCollisionExit)"를 호출할때 해당 변수의 값을 ++, -- 해주는 것으로 충돌상태여부를 구분하게 하였다.
Next Note
'Win32API' 카테고리의 다른 글
Win32API_StudyNote_7 (0) | 2024.03.25 |
---|---|
Win32API_StudyNote_6 (0) | 2024.03.24 |
Win32API_StudyNote_4 (0) | 2024.03.21 |
Win32API_StudyNote_3 (0) | 2024.03.19 |
Win32API_StudyNote_2 (0) | 2024.03.17 |