학원/경일게임아카데미

34. 스무번째 수업과제 [원과 사각형의 충돌]

셩잇님 2023. 1. 13. 00:59
반응형

경일게임아카데미 프로그래밍반 28기 20일차 수업과제 (2021. 05. 06)

 

 

 


 

 

 

오늘은 WIN32 API을 활용하여 원과 사각형의 충돌을 제작해봅시다!

과제2 - 원과 사각형의 충돌을 제작해보세요~!
조건.
1. 사각형 하나를 화면 중앙에 위치한다.
2. 사용자는 원을 만들지만 해당 원은 마우스로 움직이게 하세요.
3. 사각형과 원이 충돌하면 화면 중앙 사각형을 색칠시키세요.

 

 

 


 

 

 

playGround.h 소스코드

#pragma once
#include "gameNode.h"

class playGround : public gameNode
{
private:
	// 초기화
	/* 충돌 여부 */
	bool _is_crash;

	/* 가운데 박스 */
	RECT _center_box;

	/* 유저 원 */
	RECT _user_ellipse;
	float _user_ellipse_centerX;
	float _user_ellipse_centerY;
	float _user_ellipse_radius;

	/* 모서리 충돌여부 */
	// LT (LEFT TOP)
	float _disX_1;
	float _disY_1;
	float _hypo_1;

	// RT (RIGHT TOP)
	float _disX_2;
	float _disY_2;
	float _hypo_2;

	// LB (LEFT BOTTOM)
	float _disX_3;
	float _disY_3;
	float _hypo_3;

	// RB (RIGHT BOTTOM)
	float _disX_4;
	float _disY_4;
	float _hypo_4;

public:
	playGround();
	~playGround();

	virtual HRESULT init();
	virtual void release();
	virtual void update();
	virtual void render(HDC hdc);
};

 

 

 


 

 

 

playGround.cpp 소스코드

#include "stdafx.h"
#include "playGround.h"

playGround::playGround()
{
}

playGround::~playGround()
{
}

//초기화는 여기다 하세요 제발
HRESULT playGround::init()
{
	gameNode::init();

	// 초기화
	_is_crash = false;							// 충돌여부
	_center_box = RectMakeCenter(WINSIZEX / 2, WINSIZEY / 2, 100, 100); 	// 사각형 생성

	return S_OK;
}

//메모리 해제는 여기다 하세요 제발
void playGround::release()
{
	gameNode::release();
}

//여기에다 연산하세요 제에발
void playGround::update()
{
	gameNode::update();

	_user_ellipse = RectMakeCenter(_ptMouse.x, _ptMouse.y, 200, 200);			// 사용자 원
	_user_ellipse_centerX = (_user_ellipse.left + _user_ellipse.right) / 2;			// 사용자 원 중점(X)
	_user_ellipse_centerY = (_user_ellipse.top + _user_ellipse.bottom) / 2;			// 사용자 원 중점(Y)
	_user_ellipse_radius = (_user_ellipse.right - _user_ellipse.left) / 2;			// 사용자 원 반지름(R)

	POINT pt;
	pt.x = _user_ellipse_centerX;
	pt.y = _user_ellipse_centerY;

	// LT (LEFT TOP)
	_disX_1 = _center_box.left - _user_ellipse_centerX;
	_disY_1 = _center_box.top - _user_ellipse_centerY;
	_hypo_1 = sqrtf(_disX_1 * _disX_1 + _disY_1 * _disY_1);
	
	// RT (RIGHT TOP)
	_disX_2 = _center_box.right - _user_ellipse_centerX;
	_disY_2 = _center_box.top - _user_ellipse_centerY;
	_hypo_2 = sqrt(_disX_2 * _disX_2 + _disY_2 * _disY_2);
	
	// LB (LEFT BOTTOM)
	_disX_3 = _center_box.left - _user_ellipse_centerX;
	_disY_3 = _center_box.bottom - _user_ellipse_centerY;
	_hypo_3 = sqrt(_disX_3 * _disX_3 + _disY_3 * _disY_3);
	
	// RB (RIGHT BOTTOM)
	_disX_4 = _center_box.right - _user_ellipse_centerX;
	_disY_4 = _center_box.bottom - _user_ellipse_centerY;
	_hypo_4 = sqrt(_disX_4 * _disX_4 + _disY_4 * _disY_4);

	// 참고사이트 : https://m.blog.naver.com/winterwolfs/10165506488
	// 면 처리
	if ((_center_box.left <= pt.x && pt.x <= _center_box.right) ||
		(_center_box.top <= pt.y && pt.y <= _center_box.bottom))
	{
		// 확장된 사각형 생성
		RECT temp;
		temp.left	=	_center_box.left	- _user_ellipse_radius;
		temp.right	=	_center_box.right	+ _user_ellipse_radius;
		temp.top	=	_center_box.top		- _user_ellipse_radius;
		temp.bottom =	_center_box.bottom	+ _user_ellipse_radius;

		if (PtInRect(&temp, pt)) _is_crash = true;
		// 참고사이트 : https://metalkim.tistory.com/323
	}
	else
	{
		// 꼭지점 처리 순 (LT, RT, LB, RB)
		if (_user_ellipse_radius <= _hypo_1 && _user_ellipse_radius <= _hypo_2 &&
			_user_ellipse_radius <= _hypo_3 && _user_ellipse_radius <= _hypo_4)
		{
			_is_crash = false;
		}
		else
		{
			_is_crash = true;
		}
	}
}

//여기에다 그려라 좀! 쫌!
void playGround::render(HDC hdc)
{
	HDC backDC = this->getBackBuffer()->getMemDC();
	PatBlt(backDC, 0, 0, WINSIZEX, WINSIZEY, WHITENESS);
	// 위에 건들지마라
	//================제발 이 사이에 좀 그립시다==========================

	Rectangle(backDC, _center_box); // 사각형 출력
	Ellipse(backDC, _user_ellipse); // 사용자 원 출력

	// 충돌 시 색칠
	if (_is_crash)
	{
		HBRUSH brush1 = CreateSolidBrush(RGB(0, 0, 0));
		HBRUSH oldBrush1 = (HBRUSH)SelectObject(backDC, brush1);
		Rectangle(backDC, _center_box);
		SelectObject(backDC, oldBrush1);
		DeleteObject(brush1);
	}

	//==================================================
	//여기도 건들지마라
	this->getBackBuffer()->render(hdc, 0, 0);
}

 

 

 


 

 

 

어떻게 구현해야 할까?

  1. 화면 중심에 사각형을 만든다
  2. 마우스를 중심으로 유저의 원을 만든다
  3. 충돌 로직을 구현한다
  4. 충돌 시 원의 색을 바꿔준다.

 

로직은?

  1. 화면 중심에 사각형을 만드는 것은 간단하다.
  2. 유저 원을 마우스 x축과 y축인 ptMouse.x, y를 사용하여 생성해준다.
  3. 충돌의 자세한 내용은 밑에서 서술하겠다.
  4. 충돌시 원의 색상을 바꾸는 것은 bool 값을 이용해 충돌일 때에만 색칠한다.

 

충돌을 구현하기에 앞서
선생님께서는 함수를 이용하여 간단하게 구현하셨지만 난 선생님의 함수용 코드를 뜯어서 함수 없이 사용하려고 했다. 왜냐하면 함수용 코드를 뜯어서 함수 없이 사용하면 적어도 함수 내부의 코드가 어떻게 동작하는지 알아야하며, 그 이해를 바탕으로 함수 없이 코드를 작성할 수 있다고 판단했기 때문이다.

충돌

  1. 사각형과 원의 충돌은 원과 원의 충돌보다 조금 더 복잡하다.
  2. 일단 사각형을 원의 반지름 만큼 영역을 확장한다.
    이 때 원의 중심점이 확장된 사각형 안에 있다면 충돌이라고 볼 수 있다.
    단, 예외적으로 대각선일 경우에는 사각형 꼭지점들이 원 안에 있는지 체크해서 확인해야 한다.
  3. 그렇기 떄문에 원과 사각형의 충돌이 원과 원의 충돌보다 조금 더 복잡하다.
  4. 참고 사이트 1. 에서 가져온 이미지를 살펴보자

  1. 먼저 원의 반지름만큼 확장된 사각형을 구한다.
  2. 1에서 구해진 원의 반지름 만큼 확장된 영역에 원의 중심이 오면 충돌로 판단한다.
  3. 예외사항으로 사각형의 꼭지점이 원 안에 오면 충돌로 판단한다.

 

 

 


 

 

 

먼저 우리는 원의 면과 사각형의 면 충돌을 위해 원의 반지름 만큼 확장된 사각형을 구해주어야 한다.

if ((_center_box.left <= pt.x && pt.x <= _center_box.right) || 
    (_center_box.top  <= pt.y && pt.y <= _center_box.bottom))
	{
		// 확장된 사각형 생성
		RECT temp;
		temp.left	=	_center_box.left	- _user_ellipse_radius;
		temp.right	=	_center_box.right	+ _user_ellipse_radius;
		temp.top	=	_center_box.top		- _user_ellipse_radius;
		temp.bottom 	=	_center_box.bottom	+ _user_ellipse_radius;

		if (PtInRect(&temp, pt)) _is_crash = true;
		// 참고사이트 : https://metalkim.tistory.com/323
	}

위에 코드가 원의 면과 사각형의 면 충돌을 위한 부분이다.

if문을 살펴보면 사용자 원의 중점(pt.x, pt.y)이 사각형의 면(L, R, T, B)에 충돌한 부분이고, 그 이후 RECT temp를 이용하여 원의 반지름 만큼 확장된 사각형을 구해준다. 그러면 1번 확장된 사각형을 구해주는 부분 해결! 😎

이후 ptInRect를 이용하여 충돌여부를 판단하여 2번 또한 간단하게 해결한다. 🤗

 

마지막으로 3을 해결 하기 위해선 모서리에 대한 정보를 알아야한다.
위에서 if문으로 면처리를 해주었기 떄문에 else문을 사용하여 면이 아닐 때에 조건을 주면 된다.

else
{
	// 꼭지점 처리 순 (LT, RT, LB, RB)
	if (_user_ellipse_radius <= _hypo_1 && _user_ellipse_radius <= _hypo_2 &&
	    _user_ellipse_radius <= _hypo_3 && _user_ellipse_radius <= _hypo_4)
	{
		_is_crash = false;
	}
	else
	{
		_is_crash = true;
	}
}

그래서 else문 안에 또 다른 if문을 주어 꼭지점 처리를 하였다.

각각의 _hypo_1 ~ _hypo_4가 각각 의미하는 것은 왼쪽 위 꼭지점, 오른쪽 위 꼭지점, 왼쪽 하단 꼭지점, 오른쪽 하단 꼭지점을 의미한다.

if 문에서 순차적으로 등장하는 _hypo는 아래와 같다.

_disX_1 = _center_box.left - _user_ellipse_centerX;
_disY_1 = _center_box.top - _user_ellipse_centerY;
_hypo_1 = sqrtf(_disX_1 * _disX_1 + _disY_1 * _disY_1);

등등..

먼저 원과 사각형의 disX, Y(distance, 거리)를 구해준다. 왜? 충돌했는지 확인하기 위해! 😴
disX, Y는 중점 사각형의 부위(L, R, T, B)에서 해당하는 유저의 원 중점(X, Y)을 빼서 구해준다.

이제 원과 원의 충돌에서 마찬가지로 위에서 구한 값을 이용해 피타고라스의 정리를 사용하여 hypo 값을 구해준다!

이제 다시 충돌 여부 여부인 if문으로 돌아가보자!

if (_user_ellipse_radius <= _hypo_1 && _user_ellipse_radius <= _hypo_2 &&
    _user_ellipse_radius <= _hypo_3 && _user_ellipse_radius <= _hypo_4)
	{
		_is_crash = false;
	}
	else
	{
		_is_crash = true;
	}

hypo1 ~ 4가 유저의 반지름 보다 클 경우 충돌이 되지 않았다 처리한다. (사진을 보고 참고하자.) 그리고 그 외일 때 충돌이라고 했다.

왜냐하면 충돌일 때를 구현하는 것보다 그것이 아닐때를 구현하는게 더 쉽고 간편하기 때문이다.

이렇게 되면 3번 꼭지점 충돌도 구현 완료이다! ✌

 

 

 


 

 

 

결과화면

 

 

 


 

 

 

개인적으로 해보고 싶은 심화학습
구현하는 것 조차 너무 힘들었다. 딱히 개별적으로 심화학습을 하고 싶지 않다.....🤦‍♂️

 

아쉬운점
꼭지점 처리를 구현하면서 if문 안에 4개의 조건을 같이 넣지 않고 개별적으로 넣었더니 특정 꼭지점에서만 구현이 되고 나머지는 정상적으로 동작 되지 않아 '왜 안될까?'하고 스트레스를 정말 엄~청 받았다.


답답한 마음에 동기생 형님에게 질문을 해봤지만 코드를 보고 난 후 disX, Y, hypo를 구하는 공식에는 문제가 없다고 하셨었다. 이후 집에 가기전에 "혹시 if문안에 조건들이 개별적으로 있어서 그런게 아닐까?" 한마디에 조건들을 묶어서 처리해주었더니 정상 작동하였다. 이후 형님에게 감사하다고 인사를 몇번이나 한지 모르겠다.


이렇게 간단한 문제를 해결조차 못하고 있는 내 자신이 너무 한심했지만, 처음이니까 모르는 내 자신을 격려하고 한심할 겨를도 없이 밀린 과제들을 해야겠다고 다시금 생각했다. ✍

 

 


 

 

 

참고 사이트 1. : https://m.blog.naver.com/winterwolfs/10165506488

 

[API] 충돌 처리

충돌 - Intersect (교차)     -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-...

blog.naver.com

참고 사이트 2. : https://metalkim.tistory.com/323

 

 

 

반응형

'학원 > 경일게임아카데미' 카테고리의 다른 글

36. 스물한번째 수업 - 2  (0) 2023.01.13
35. 스물한번째 수업  (0) 2023.01.13
33. 스무번째 수업과제 [원과 원의 충돌]  (0) 2023.01.11
32. 스무번째 수업  (0) 2023.01.10
31. 열아홉번째 수업  (0) 2023.01.09