*** 공부 방법 ***
1. 코딩을 해야 하는 부분은 첫 부분에 변수나 함수, 메소드에 대한 선언이 코드블럭으로 표시되어 있다. //ex) MakeFunction(); 2. 코드블럭 하단에는 해당 선언에 대한 구현 로직이 작성되어 있다. 처
hyrule.tistory.com
[ 사전 지식 ]
#include <algorithm>
std::sort(m_vector.begin(), m.vector.end(), CClass::SortY());
- algorithm에 포함되어 있는 정렬 알고리즘 함수, sort()
-- 리스트는 자료구조 클래스 자체에 sort 기능이 내장되어 있었지만, vector의 경우 sort 기능이 없다.
- 원하는 구간의 시작, 끝 지점 iterator()과, 정렬 기준이 될 bool함수를 넣어주면 해당 값을 기준으로 정렬된다.
class CCollisionManager
< CollisionPointToCircle() >
< CollisionPointToBox() >
- 마우스의 충돌을 구현하기 위해서는 점과의 충돌을 구현하면 된다.
-- 점과의 충돌의 경우, Hit Point는 무조건 점의 위치가 된다.
- 점-원 충돌 판정 로직의 경우, 이전에 원-사각형 충돌에서 이미 한번 구현했다.
--> 사각형의 각 꼭짓점과 원의 중심점 사이의 거리를 구해서 반지름 이내면 충돌
- 점-사각형 충돌 판정 로직의 경우도, 이미 사각형-사각형 충돌에서 비슷하게 구현했다.
--> 사각형의 윗변보다 점의 y축 위치가 작으면 절대 충돌 아님, 아랫변보다 점의 y축 위치가 크면 절대 충돌 아님 등...
class CCollider;
< CallMouseCollisionBegin(const Vector2& Point) >
< CallMouseCollisionEnd(const Vector2& Point) >
template <typename T>
void SetCollisionBeginMouse(T* Obj, void (T::* Func)(const Vector2&, CCollider*))
template <typename T>
void SetCollisionEndMouse(T* Obj, void (T::* Func)(const Vector2&, CCollider*))
< m_MouseCollisionBegin >
< m_MouseCollisionEnd >
- 게임오브젝트가 마우스와 충돌했을 때와 일반 게임오브젝트와 충돌했을때 호출될 함수가 달라져야 할 수도 있다.
- 마우스의 충돌 시 호출될 함수 포인터를 설정해주는 메소드.
- 충돌체 하나와 Vector2를 인자로 받는다.
class CColliderBox;
class CColliderCircle;
< CollisionPoint(const Vector2& Point) >
- 마우스 충돌을 확인하기 위한 메소드.
- 각자의 충돌체 타입에 맞는 충돌 판정 메소드를 호출한다.
class CSceneCollider;
< class CCollider m_MouseCollision >
- 마우스의 충돌은 별도로 관리한다.
- 이 변수에 마우스와 충돌이 일어난 충돌체 정보를 저장해 놓는다.
< SortY() >
- 마우스는 충돌 시, 어떤 물체와 충돌될지를 결정해야 한다.
예를 들어 몬스터를 클릭 시 공격 명령이 들어가야 하는데, 땅을 클릭한 것으로 인식되어 그냥 땅 위치로 걸어가는 등의 현상을 막아야 하기 때문이다.
-- 게임을 렌더링할 때, 순서를 y축 오름차순으로 했었다(Y Sorting)
-- 반대로 마우스는 충돌할 때, y축 내림차순으로 해 주어야 한다. (화면의 가장 앞에 나와있는 오브젝트가 클릭되어야 하므로
- 정렬에 필요한 메소드는 -> 전역 함수 또는 정적 함수로 선언했을 때만 사용 가능하다.
- 게임오브젝트를 렌더링할때는 '발 밑' 기준으로 소팅을 했었다.
- 마찬가지로 충돌체도 충돌 부분의 최하단을 기준으로 소팅을 해주어야 한다.
-> 이 데이터는 각 충돌체에서 미리 저장을 해 놓는다.
- 내가 만든 충돌 처리 로직이 약간 다르기 때문에(2차원 배열 사용) Sort를 사용할 수 없다...(정확히 말해서 쓸 수는 있지만 매 배열마다 Sort 과정을 반복해야 된다.)
-- 일일히 순회 돌면서 Bottom 값이 가장 높은 인덱스를 저장하고, 마지막에 처리해야 한다.
class CCollider;
< float m_Bottom >
< float GetBottom() >
class CColliderBox;
class CColliderCircle;
< PostUpdate() >
- 각 충돌체에서 위의 좌표값을 구해서 저장해 놓는다.
flag.h
< enum class ECollisionChannel >
- Mouse 충돌 채널을 하나 추가한다.
class CCollisionManager
< Init() >
- 추가된 마우스 채널과 각 채널들과의 상호작용을 추가해준다.
class CInput
< m_MouseProfile >
- 자신의 프로필을 들고있을 변수.
< Init() >
- 초기화 때 프로필을 등록해준다.
class CSceneCollision;
< CollisionMouse() >
- Src는 무조건 마우스이고, Dest는 충돌체이다.
1. 미리 마우스의 좌표값을 받아 놓는다.
y소팅을 할 수 없으므로 일일히 ybottom 값을 기록한 후 마지막에 처리를 해야한다.
혹시나 이차원 배열 전체가 비어있거나, 충돌이 아예 없었을 수도 있으므로, NotEmpty 변수를 만들어 미연에 방지한다.
//Src = 무조건 마우스위치
//미리 좌표값을 받아놓는다.
Vector2 MousePos = CInput::GetInst()->GetMouseWorldPos();
//y소팅을 위해 순회를 돌 때는 인덱스만 기록해두고
//순회가 끝나면 좌표를 사용해 충돌 처리를 완료한다.
int iIndex = 0;
int kIndex = 0;
//만에하나 배열이 전체 다 비어있을 수도 있으므로
//에러 방지용
bool NotEmpty = false;
2. CollisionUpdate() 에서는 Src와 Dest 모두 달랐으므로 한번에 두 배열씩 순회를 돌았지만
CollisionUpdateMouse()에서는 Src가 마우스로 고정되어있으므로, 한번에 한 배열씩만 순회를 둘아주면 된다.
//일단 i만큼 순회를 돌아야 한다.
for (int i = 0; i < (int)ECollisionChannel::Max; ++i)
{
//비교할 채널의 충돌체가 비어있으면 스킵한다.
if (m_vecCollider[i].empty())
continue;
size_t size_i = m_vecCollider[i].size();
for (size_t k = 0; k < size_i; ++k)
{
3. 채널끼리 충돌이 일어나는지 비교한다. 만약 둘 중 한 채널이라도 충돌 설정을 해 놓았으면 충돌이 발생한다.
충돌이 아닐 경우 해당 배열 전체가 같은 채널이므로 빠져나와준다.
ECollisionChannel SrcProfile = ECollisionChannel::Mouse;
ECollisionChannel DestProfile = m_vecCollider[i][k]->GetProfile();
//둘중 한 채널이라도 상대방 채널과 충돌 판정이 없다면
//두 채널은 관계없는 충돌 판정 라인이므로 break를 통해 스킵
if (m_CollManager->GetProfile(SrcProfile)->Interaction[(int)DestProfile] ||
m_CollManager->GetProfile(DestProfile)->Interaction[(int)SrcProfile])
{
break;
}
4. 충돌일 경우, Bottom 값이 여태까지 가장 높았는지만 기록하고, 만약 가장 높으면 Index만 기록하고 빠져나온다.
배열이 비었는지를 확인하는 NotEmpty 변수를 바꿔 준다.
충돌 판정은 반복문을 빠져나와서 한다.
충돌이 아닐 경우 전 프레임까지 충돌이었다면 충돌 아님 상태로 바꾸고, EndFunction을 호출한다.
CCollider* Dest = m_vecCollider[i][k];
//충돌 여부를 확인해서 충돌이면
if (Dest->CollisionPoint(MousePos))
{
//처음 여기에 진입했으면 에러 방지를 위해 인덱스를 바꿔주고
//에러 방지용 변수를 false로 바꿔준다.
if (iIndex + kIndex == 0)
{
iIndex = (int)i;
kIndex = (int)k;
NotEmpty = true;
}
//통과했으면, Y-Sorting을 위해 index를 비교한다.
//새로 들어온 i,k값의 Bottom값이 기존의 Bottom값보다 클 경우
//저장한 인덱스 번호를 변경한다.
if (m_vecCollider[i][k]->GetBottom() >= m_vecCollider[iIndex][kIndex]->GetBottom())
{
iIndex = (int)i;
kIndex = (int)k;
}
}
//만약 충돌 상태가 아닌데(else)
// 자신의 충돌중 리스트 안에 상대 오브젝트가 있을 경우(+ if)
//-> 충돌중이었는데 다시 떨어졌다는 의미이다!
else if (Dest->CheckMouseColliding())
{
Dest->SetMouseColliding(false);
Dest->CallCollisionEndMouse(MousePos);
}
}
}
5. iIndex와 kIndex에는 여태까지 충돌판정이 났던 충돌체 중 가장 밑에 있던 충돌체의 인덱스 번호가 기록되어 있을것이다.
해당 인덱스와의 충돌 처리를 진행한다.
//만약 충돌된 충돌체가 하나라도 있었을 경우
if (NotEmpty)
{
//저장된 인덱스 = 최종 충돌체
CCollider* Dest = m_vecCollider[iIndex][kIndex];
//근데 만약 첫 충돌이면
//->Src의 충돌중인 충돌체 리스트에 Dest가 없으면
if (!Dest->CheckMouseColliding())
{
Dest->SetMouseColliding(true);
//마우스가 충돌한 대상의 충돌 호출 함수에 좌표를 넣어 호출한다.
Dest->CallCollisionBeginMouse(MousePos);
}
}
6. 로직이 모두 끝났으면 다음 프레임에 다시 사용하기 위해 배열을 비워준다.
//계산이 끝났으면 배열을 비워준다.
for (int i = 0; i < (int)ECollisionChannel::Max; ++i)
{
m_vecCollider[i].clear();
}
* 전체 코드는 다음과 같다.
void CSceneCollision::CollisionUpdate()
{
//일단 i만큼 순회를 돌아야 한다.
for (int i = 0; i < (int)ECollisionChannel::Max; ++i)
{
//비교할 채널의 충돌체가 비어있으면 스킵한다.
if (m_vecCollider[i].empty())
continue;
for (int j = i + 1; j < (int)ECollisionChannel::Max; ++j)
{
//비교 대상 채널의 충돌체가 비어있으면 스킵한다.
if (m_vecCollider[j].empty())
continue;
size_t size_i = m_vecCollider[i].size();
for (size_t k = 0; k < size_i; ++k)
{
//다음 반복문도 빠져나오기 위한 변수
//첫번쨰 원소끼리 전혀 관계없는 충돌체이면
//다음 원소들도 각각 같은 충돌 그룹이기 때문에
//전혀 관련없으므로 반복문 전체 중단
bool noCollision = false;
size_t size_j = m_vecCollider[j].size();
for (size_t l = 0; l < size_j; ++l)
{
ECollisionChannel SrcProfile = m_vecCollider[i][k]->GetProfile();
ECollisionChannel DestProfile = m_vecCollider[j][l]->GetProfile();
//둘중 한 채널이라도 상대방 채널과 충돌 판정이 없다면
//두 채널은 관계없는 충돌 판정 라인이므로 break를 통해 스킵
if (m_CollManager->GetProfile(SrcProfile)->Interaction[(int)DestProfile] ||
m_CollManager->GetProfile(DestProfile)->Interaction[(int)SrcProfile] )
{
noCollision = true;
break;
}
//통과했으면 충돌 처리를 시작한다.
CCollider* Src = m_vecCollider[i][k];
CCollider* Dest = m_vecCollider[j][l];
//충돌 여부를 확인해서 충돌이면
if (Src->Collision(Dest))
{
//근데 만약 첫 충돌이면
//->Src의 충돌중인 충돌체 리스트에 Dest가 없으면
if (!Src->CheckCollisionList(Dest))
{
//서로의 리스트에 서로를 삽입한다.
Src->AddCollisionList(Dest);
Dest->AddCollisionList(Src);
//그리고 각자의 충돌 호출 함수에 상대를 넣어 호출한다.
Src->CallCollisionBegin(Dest);
Dest->CallCollisionBegin(Src);
}
}
//만약 충돌 상태가 아닌데 자신의 충돌중 리스트 안에
//상대 오브젝트가 있을 경우
//-> 충돌중이었는데 다시 떨어졌다는 의미이다!
else if (Src->CheckCollisionList(Dest))
{
//서로에게 자신을 리스트에서 지우도록 요청
Src->DeleteCollisionList(Dest);
Dest->DeleteCollisionList(Src);
//그리고 충돌 종료 함수를 호출
Src->CallCollisionEnd(Dest);
Dest->CallCollisionEnd(Src);
}
}
//마찬가지로 상관없는 라인이므로 여기도 빠져나와준다.
if (noCollision)
break;
}
}
}
//마우스와도 충돌확인을 해 봐야 하므로 여기서 배열을 지우지 않는다.
}
void CSceneCollision::CollisionUpdateMouse()
{
//Src = 무조건 마우스위치
//미리 좌표값을 받아놓는다.
Vector2 MousePos = CInput::GetInst()->GetMouseWorldPos();
//y소팅을 위해 순회를 돌 때는 인덱스만 기록해두고
//순회가 끝나면 좌표를 사용해 충돌 처리를 완료한다.
int iIndex = 0;
int kIndex = 0;
//만에하나 배열이 전체 다 비어있을 수도 있으므로
//에러 방지용
bool NotEmpty = false;
//일단 i만큼 순회를 돌아야 한다.
for (int i = 0; i < (int)ECollisionChannel::Max; ++i)
{
//비교할 채널의 충돌체가 비어있으면 스킵한다.
if (m_vecCollider[i].empty())
continue;
size_t size_i = m_vecCollider[i].size();
for (size_t k = 0; k < size_i; ++k)
{
ECollisionChannel SrcProfile = ECollisionChannel::Mouse;
ECollisionChannel DestProfile = m_vecCollider[i][k]->GetProfile();
//둘중 한 채널이라도 상대방 채널과 충돌 판정이 없다면
//두 채널은 관계없는 충돌 판정 라인이므로 break를 통해 스킵
if (m_CollManager->GetProfile(SrcProfile)->Interaction[(int)DestProfile] ||
m_CollManager->GetProfile(DestProfile)->Interaction[(int)SrcProfile])
{
break;
}
CCollider* Dest = m_vecCollider[i][k];
//충돌 여부를 확인해서 충돌이면
if (Dest->CollisionPoint(MousePos))
{
//처음 여기에 진입했으면 에러 방지를 위해 인덱스를 바꿔주고
//에러 방지용 변수를 false로 바꿔준다.
if (iIndex + kIndex == 0)
{
iIndex = (int)i;
kIndex = (int)k;
NotEmpty = true;
}
//통과했으면, Y-Sorting을 위해 index를 비교한다.
//새로 들어온 i,k값의 Bottom값이 기존의 Bottom값보다 클 경우
//저장한 인덱스 번호를 변경한다.
if (m_vecCollider[i][k]->GetBottom() >= m_vecCollider[iIndex][kIndex]->GetBottom())
{
iIndex = (int)i;
kIndex = (int)k;
}
}
//만약 충돌 상태가 아닌데(else)
// 자신의 충돌중 리스트 안에 상대 오브젝트가 있을 경우(+ if)
//-> 충돌중이었는데 다시 떨어졌다는 의미이다!
else if (Dest->CheckMouseColliding())
{
Dest->SetMouseColliding(false);
Dest->CallCollisionEndMouse(MousePos);
}
}
}
//만약 충돌된 충돌체가 하나라도 있었을 경우
if (NotEmpty)
{
//저장된 인덱스 = 최종 충돌체
CCollider* Dest = m_vecCollider[iIndex][kIndex];
//근데 만약 첫 충돌이면
//->Src의 충돌중인 충돌체 리스트에 Dest가 없으면
if (!Dest->CheckMouseColliding())
{
Dest->SetMouseColliding(true);
//마우스가 충돌한 대상의 충돌 호출 함수에 좌표를 넣어 호출한다.
Dest->CallCollisionBeginMouse(MousePos);
}
}
//계산이 끝났으면 배열을 비워준다.
for (int i = 0; i < (int)ECollisionChannel::Max; ++i)
{
m_vecCollider[i].clear();
}
}
class CColliderBox;
class CColliderCircle;
< Render() >
- 마우스와 충돌했을 때도 테두리를 빨간색으로 전환되게 해준다.
- 마우스 충돌은 따로 충돌체를 생성하지 않아도 자동으로 계산된다. 채널만 설정해놓으면 된다.
- 마우스 채널이 다른 모든 채널과 전체 충돌하도록 설정해 놓고, 충돌 시 히트 이펙트가 나오도록 한뒤 실행하면,
'WIN32API FrameWork > 한단계씩 직접 구현' 카테고리의 다른 글
56. 위젯 (0) | 2022.06.06 |
---|---|
55. 사운드 (0) | 2022.06.03 |
53. 마우스 (0) | 2022.06.03 |
52. 박스-원 충돌 (0) | 2022.06.02 |
51. 원형 충돌 (0) | 2022.06.02 |