WIN32API FrameWork/한단계씩 직접 구현

54. 마우스 충돌 - 픽셀 충돌

2022. 6. 3. 21:51

http://hyrule.tistory.com/111 

 

*** 공부 방법 ***

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() > 

- 마우스와 충돌했을 때도 테두리를 빨간색으로 전환되게 해준다.

 

 

 


- 마우스 충돌은 따로 충돌체를 생성하지 않아도 자동으로 계산된다. 채널만 설정해놓으면 된다.

- 마우스 채널이 다른 모든 채널과 전체 충돌하도록 설정해 놓고, 충돌 시 히트 이펙트가 나오도록 한뒤 실행하면,

잘 작동한다.
GameFrameworkStepbyStep_54_MouseCollide.zip
0.32MB

저작자표시 (새창열림)

'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
'WIN32API FrameWork/한단계씩 직접 구현' 카테고리의 다른 글
  • 56. 위젯
  • 55. 사운드
  • 53. 마우스
  • 52. 박스-원 충돌
hyrule
hyrule
hyrule
C++ 프로그래밍 공부
hyrule
전체
오늘
어제
  • 분류 전체보기 (205)
    • C++기초 (50)
    • WIN32API FrameWork (109)
      • 한단계씩 직접 구현 (82)
      • 원본 (15)
      • 코드별 설명 개별저장(검색용) (12)
    • 자습 (21)
    • C++ TIPS (11)
    • 연습 노트 (3)
    • ETC (6)
    • DX2D StarCraft 모작 (1)

블로그 메뉴

  • 홈
  • 방명록

공지사항

인기 글

태그

  • Windows 11
  • Tistory
  • C++
  • notion
  • 스타크래프트
  • hello

최근 댓글

최근 글

hELLO · Designed By 정상우.
hyrule
54. 마우스 충돌 - 픽셀 충돌
상단으로

티스토리툴바

개인정보

  • 티스토리 홈
  • 포럼
  • 로그인

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.