본문으로 바로가기

PointCircleCollision.zip
0.03MB

 

 

[프로그램 설계]

1. 0 ~ n까지 Circle Object 생성, 각 Object에 ID 부여
2. 화면 왼쪽위 Object 갯수, 찾아야 할 Object ID 표시
3. ID가 일치 하지 않으면 Object 갯수 +5, Object는 랜덤이동
4. 일치하면 Object 삭제하고 다음 ID 랜덤 선정 후 출력

 

[구현 중점]

1. 클래스, 상속을 활용하여 다양한 요구에 빠르게 적응할 수 있는 Framework 설계

2. 점(마우스좌표) <-> 원(오브젝트) 충돌처리

3. 더블버퍼링을 이용하여 부드러운 렌더링 구현

4. 폰트 설정

 

[보완점]

1. 오브젝트 갯수 500개 이상 증가 시 렉 현상

-> Update, Render 부분 최적화

2. UI 영역 배치 및 항목별 폰트 설정 불편

-> 폰트 설정 및 출력된 텍스트의 크기를 계산하는 기능이 포함된 문자 출력 함수 작성

 

 

Framework.h
더보기
#pragma once

#include "targetver.h"
#define WIN32_LEAN_AND_MEAN
#include "Resource.h"

// Windows Header
#include <windows.h>
#include <string>
#include <map>
#include <vector>
#include <ctime>

using namespace std;

// Macro
#define MAX_LOADSTRING 100
// color
#define BLACK	RGB(0,0,0)
#define RED		RGB(255,0,0)
#define GREEN	RGB(0,255,0)
#define BLUE	RGB(0,0,255)
#define CYAN	RGB(0,255,255)
#define MAGENTA	RGB(255,0,255)
#define YELLOW	RGB(255,255,0)
#define WHITE	RGB(255,255,255)


// Framework Header

// Obejct
#include "Object.h"
#include "CircleObject.h"

// Scene
#include "Scene.h"
#include "PointCircleScene.h"
#include "SceneManager.h"

// Core
#include "Core.h"

// extern
extern HINSTANCE hInst;
extern HWND hWnd;
extern HDC hdc;
extern POINT mousePos;
extern RECT bufferRT;
main.cpp
더보기
#include "Framework.h"

// 코어 초기화 함수
void InitCore(HWND hWnd);

// 폰트 설정 함수
void SetFont(HDC& hdc, HFONT& hFont, HFONT& OldFont, LOGFONT& lf);

// My 전역 변수
HINSTANCE hInst;                                // InitInstance()에서 초기화
HWND hWnd;                                      // InitInstance()에서 초기화
HDC hdc;                                        // WM_PAINT 에서 초기화
RECT bufferRT;                                  // WM_PAINT 에서 초기화
POINT mousePos;                                 // WM_MOUSEMOVE 에서 업데이트

// 전역 변수:
WCHAR szTitle[MAX_LOADSTRING];                  // 제목 표시줄 텍스트입니다.
WCHAR szWindowClass[MAX_LOADSTRING];            // 기본 창 클래스 이름입니다.

// 이 코드 모듈에 포함된 함수의 선언을 전달합니다:
ATOM                MyRegisterClass(HINSTANCE hInstance);
BOOL                InitInstance(HINSTANCE, int);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK    About(HWND, UINT, WPARAM, LPARAM);

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    // TODO: 여기에 코드를 입력합니다.

    // 전역 문자열을 초기화합니다.
    LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    LoadStringW(hInstance, IDC_POINTCIRCLECOLLISION, szWindowClass, MAX_LOADSTRING);
    MyRegisterClass(hInstance);

    if (!InitInstance (hInstance, nCmdShow))
        return FALSE;

    HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_POINTCIRCLECOLLISION));

    MSG msg;

    // 기본 메시지 루프입니다:
    while (GetMessage(&msg, nullptr, 0, 0))
    {
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    return (int) msg.wParam;
}

ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEXW wcex;

    wcex.cbSize = sizeof(WNDCLASSEX);

    wcex.style          = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc    = WndProc;
    wcex.cbClsExtra     = 0;
    wcex.cbWndExtra     = 0;
    wcex.hInstance      = hInstance;
    wcex.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_POINTCIRCLECOLLISION));
    wcex.hCursor        = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
    wcex.lpszMenuName   = MAKEINTRESOURCEW(IDC_POINTCIRCLECOLLISION);
    wcex.lpszClassName  = szWindowClass;
    wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

    return RegisterClassExW(&wcex);
}

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   hInst = hInstance; // 인스턴스 핸들을 전역 변수에 저장합니다.

   hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);

   if (!hWnd)
   {
      return FALSE;
   }

   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   return TRUE;
}

Core* core; // Core Pointer 선언

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message)
	{
	case WM_CREATE:
	{
		InitCore(hWnd);
		SetTimer(hWnd, 1, 10, nullptr);
	}
	break;
	case WM_TIMER:
	{
		core->Update();
		InvalidateRect(hWnd, nullptr, false);
	}
	break;
	case WM_MOUSEMOVE:
	{
		mousePos.x = LOWORD(lParam);
		mousePos.y = HIWORD(lParam);
	}
	break;
    case WM_PAINT:
    {
        // 더블버퍼링 시작
        PAINTSTRUCT ps;
        //static HDC hdc;
        static HDC MemDC, tmpDC;
        static HBITMAP BackBit, oldBackBit;
        //static RECT bufferRT;
        hdc = BeginPaint(hWnd, &ps);

        GetClientRect(hWnd, &bufferRT);
        MemDC = CreateCompatibleDC(hdc);
        BackBit = CreateCompatibleBitmap(hdc, bufferRT.right, bufferRT.bottom);
        oldBackBit = (HBITMAP)SelectObject(MemDC, BackBit);
        PatBlt(MemDC, 0, 0, bufferRT.right, bufferRT.bottom, WHITENESS);
        tmpDC = hdc;
        hdc = MemDC;
        MemDC = tmpDC;

        // SetFont
		static HFONT hFont, OldFont;
		static LOGFONT lf;
		SetFont(hdc, hFont, OldFont, lf);

		// Render
		core->Render();

		// 더블버퍼링 끝
		tmpDC = hdc;
		hdc = MemDC;
		MemDC = tmpDC;
		GetClientRect(hWnd, &bufferRT);
		BitBlt(hdc, 0, 0, bufferRT.right, bufferRT.bottom, MemDC, 0, 0, SRCCOPY);
		SelectObject(MemDC, oldBackBit);
		DeleteObject(BackBit);
		DeleteDC(MemDC);
		EndPaint(hWnd, &ps);
	}
	break;
	case WM_COMMAND:
	{
		int wmId = LOWORD(wParam);
		// 메뉴 선택을 구문 분석합니다:
		switch (wmId)
		{
		case IDM_ABOUT:
			DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
			break;
		case IDM_EXIT:
			DestroyWindow(hWnd);
			break;
		default:
			return DefWindowProc(hWnd, message, wParam, lParam);
		}
	}
	break;
	case WM_DESTROY:
	{
		PostQuitMessage(0);
		delete core;
	}
	break;
	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

// 정보 대화 상자의 메시지 처리기입니다.
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
	UNREFERENCED_PARAMETER(lParam);
	switch (message)
	{
	case WM_INITDIALOG:
		return (INT_PTR)TRUE;

	case WM_COMMAND:
		if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
		{
			EndDialog(hDlg, LOWORD(wParam));
			return (INT_PTR)TRUE;
		}
		break;
	}
	return (INT_PTR)FALSE;
}

void InitCore(HWND hWnd)
{
	// Core 초기화
	core = new Core;

	GetClientRect(hWnd, &bufferRT);
	if (!core->Init()) // 초기화 실패시 프로그램 종료
		PostQuitMessage(0);

	core->Run();
}


void SetFont(HDC& hdc, HFONT& hFont, HFONT& OldFont, LOGFONT& lf)
{
	memset(&lf, 0, sizeof(lf));
	lf.lfHeight = 24;
	lf.lfWidth = 0;
	lf.lfEscapement = 0;
	lf.lfOrientation = 0;
	lf.lfWeight = FW_BOLD;
	lf.lfItalic = 0;
	lf.lfUnderline = 0;
	lf.lfStrikeOut = 0;
	lf.lfCharSet = HANGEUL_CHARSET;
	lf.lfOutPrecision = 0;
	lf.lfClipPrecision = 0;
	lf.lfQuality = 0;
	lf.lfPitchAndFamily = VARIABLE_PITCH | FF_ROMAN;
	lstrcpy(lf.lfFaceName, TEXT("Gulim"));
	hFont = CreateFontIndirect(&lf);

	OldFont = (HFONT)SelectObject(hdc, hFont);
	//SelectObject(hdc, OldFont);
	//DeleteObject(hFont);
}
Core
더보기
#pragma once

class Core
{
public:
	Core();
	~Core();

public:
	bool Init();
	void Run();
	void Update();
	void Render();

private:
	SceneManager* m_pSceneManager;
};
/////////////////////////////////////////////////////////
#include "Framework.h"

Core::Core()
	:m_pSceneManager(nullptr)
{
}

Core::~Core()
{
	delete m_pSceneManager;
}

bool Core::Init()
{
	srand((unsigned)time(nullptr));

	m_pSceneManager = new SceneManager;
	if (!m_pSceneManager->Init())
		return false;

	return true;
}

void Core::Run()
{
	m_pSceneManager->Run();
}

void Core::Update()
{
	m_pSceneManager->Update();
}

void Core::Render()
{
	m_pSceneManager->Render();
} 
SceneManager
더보기
#pragma once

class SceneManager
{
public:
	SceneManager();
	~SceneManager();

public:
	friend class Core;

public:
	bool Init();
	void Run();
	void Update();
	void Render();

private:
	Scene* m_pCurrentScene;

public:
	Scene* GetCurrentScene();
};
//////////////////////////////////////////////////////////////////////////////////////////
#include "Framework.h"

SceneManager::SceneManager()
	:m_pCurrentScene(nullptr)
{
}

SceneManager::~SceneManager()
{
	delete m_pCurrentScene;
}

bool SceneManager::Init()
{
	m_pCurrentScene = new PointCircleScene;
	if (!m_pCurrentScene->Init())
		return false;

	return true;
}

void SceneManager::Run()
{
	m_pCurrentScene->Run();
}

void SceneManager::Update()
{
	m_pCurrentScene->Update();
}

void SceneManager::Render()
{
	m_pCurrentScene->Render();
}

Scene* SceneManager::GetCurrentScene()
{
	return m_pCurrentScene;
}
Scene
더보기
#pragma once

class Scene
{
public:
	Scene() = default;
	virtual ~Scene() = default;

public:
	friend class SceneManager;

public:
	virtual bool Init() = 0;
	virtual void Run() = 0;
	virtual void Update() = 0;
	virtual void Render() = 0;
 }; 
PointCircleScene
더보기
#pragma once

class PointCircleScene : public Scene
{
public:
	PointCircleScene();
	virtual ~PointCircleScene();

public:
	// Scene을(를) 통해 상속됨
	virtual bool Init() override;
	virtual void Run() override;
	virtual void Update() override;
	virtual void Render() override;

private:
	vector<Object*> m_vecObjects;
	int m_nCurrentID;
	int m_nCount;
	int m_nElapsedTime;
	bool isPlaying;
	bool isClear;

private:
	POINT m_tPos_LT;
	POINT m_tPos_RT;
	POINT m_tPos_LB;
	POINT m_tPos_RB;
	POINT m_tPos_Center;

public:
	void RemoveObject(int nID);
	void ShowUI();
	void AddCurrentID() { m_nCurrentID++; }
	void NextRandomID();
	bool IsCurrentID(int nID) { return nID == m_nCurrentID; }
	void AddCircle();
	void CheckClear();
};
///////////////////////////////////////////////////////////////////////////////
#include "Framework.h"

PointCircleScene::PointCircleScene()
	:m_nCurrentID(0), m_nCount(0), m_vecObjects(NULL), m_nElapsedTime(0), isPlaying(false), isClear(false)
{
}

PointCircleScene::~PointCircleScene()
{
}

bool PointCircleScene::Init()
{
	isPlaying = true;

	m_tPos_LT = { bufferRT.left + 10, bufferRT.top + 10 };
	m_tPos_RT = { bufferRT.right - 10, bufferRT.top + 10 };
	m_tPos_LB = { bufferRT.left + 10, bufferRT.bottom - 10 };
	m_tPos_RB = { bufferRT.right - 10, bufferRT.bottom - 10 };
	m_tPos_Center = { bufferRT.right / 2, bufferRT.bottom / 2 };

	return true;
}

void PointCircleScene::Run()
{
	POINT pos{};
	for (int i = 0; i < 5; i++)
	{
		CircleObject* pCircleObject = new CircleObject;
		double dRadius = 30.f;
		pos.x = (rand() % (bufferRT.right - 2*(LONG)dRadius)) + (LONG)dRadius;
		pos.y = (rand() % (bufferRT.bottom - 2*(LONG)dRadius)) + (LONG)dRadius;
		pCircleObject->SetPos(pos);
		pCircleObject->SetRadius(dRadius);
		pCircleObject->SetID(i);
		pCircleObject->SetScene(this);
		m_vecObjects.push_back(pCircleObject);
		m_nCount++;
	}
}

void PointCircleScene::Update()
{
	//for (auto i : m_vecObjects)
	//	i->Update();
	if (isPlaying)
	{
		m_nElapsedTime++;
		for (int i = 0; i < m_vecObjects.size(); i++)
			m_vecObjects[i]->Update();
	}
}

void PointCircleScene::Render()
{
	ShowUI();
	if (isPlaying)
	{
		for (int i = 0; i < m_vecObjects.size(); i++)
			m_vecObjects[i]->Render();
	}
}

void PointCircleScene::RemoveObject(int nID)
{
	int i{};
	for (auto iter : m_vecObjects)
	{
		if (iter->GetID() == nID)
		{
			delete iter;
			m_vecObjects.erase(m_vecObjects.begin() + i);
			NextRandomID();
			CheckClear();
			return;
		}
		++i;
	}
}



void PointCircleScene::ShowUI()
{
	wstring tmp = L"갯수 :";
	TextOutW(hdc, m_tPos_LT.x, m_tPos_LT.y, tmp.c_str(), tmp.length());
	tmp = to_wstring(m_vecObjects.size());
	TextOutW(hdc, m_tPos_LT.x + 80, m_tPos_LT.y, tmp.c_str(), tmp.length());
	tmp = L"찾을 ID :";
	TextOutW(hdc, m_tPos_LT.x + 160, m_tPos_LT.y, tmp.c_str(), tmp.length());
	tmp = to_wstring(m_nCurrentID);
	TextOutW(hdc, m_tPos_LT.x + 280, m_tPos_LT.y, tmp.c_str(), tmp.length());
	tmp = L"경과시간 :";
	TextOutW(hdc, m_tPos_RT.x - 160, m_tPos_RT.y, tmp.c_str(), tmp.length());
	tmp = to_wstring(m_nElapsedTime/60);
	TextOutW(hdc, m_tPos_RT.x - 40, m_tPos_RT.y, tmp.c_str(), tmp.length());
	if (isClear)
	{
		tmp = L"Clear!";
		TextOutW(hdc, m_tPos_Center.x, m_tPos_Center.y, tmp.c_str(), tmp.length());
	}
}

void PointCircleScene::NextRandomID()
{
	int nCount = m_vecObjects.size();
	if (nCount == 0)
		return;
	int nIdx = rand() % nCount;
	m_nCurrentID = (m_vecObjects[nIdx])->GetID();
}

void PointCircleScene::AddCircle()
{
	POINT pos{};
	for (int i = 0; i < 5; i++)
	{
		CircleObject* pCircleObject = new CircleObject;
		double dRadius = 30.f;
		pos.x = rand() % (bufferRT.right - (LONG)dRadius);
		pos.y = rand() % (bufferRT.bottom - (LONG)dRadius);
		pCircleObject->SetPos(pos);
		pCircleObject->SetRadius(dRadius);
		pCircleObject->SetID(m_nCount);
		pCircleObject->SetScene(this);
		m_vecObjects.push_back(pCircleObject);
		m_nCount++;
	}
}

void PointCircleScene::CheckClear()
{
	int nCount = m_vecObjects.size();
	if (nCount == 0)
	{
		isPlaying = false;
		isClear = true;
	}
}
Object
더보기
#pragma once

class Object
{
public:
	virtual ~Object() = default;

public:
	virtual void Update() = 0;
	virtual void Render() = 0;

protected:
	POINT m_tPos;
	int m_nID;

public:
	POINT GetPos() const { return m_tPos; }
	void SetPos(const POINT& pos) { m_tPos = pos; };

	int	GetID() const { return m_nID; }
	void SetID(int nID) { m_nID = nID; }
};
CircleObject
더보기
#pragma once

class Scene;

class CircleObject : public Object
{
public:
	CircleObject();
	virtual ~CircleObject();

public:
	virtual void Update() override;
	virtual void Render() override;

private:
	double m_dRadius;
	int m_nTimer;
	int m_nDir;
	Scene* m_pScene;
	bool isClick;
	double m_dSpeed;
	int m_nColor;
	HBRUSH m_brush;

public:
	double GetRadius() const { return m_dRadius; }
	void SetRadius(double dRad) { m_dRadius = dRad; }
	void SetScene(Scene* pScene) { m_pScene = pScene; }
	void SetSpeed(double dSpeed) { m_dSpeed = dSpeed; }

	void RenderID();
	bool Collide();
	void Destroy();
	void Move();
	void Jump();
	void CheckMouseInput();
	void MouseUp();
	void MouseDown();
	void SetBrush();
};
////////////////////////////////////////////////////////////////////////////////
#include "Framework.h"

CircleObject::CircleObject()
	:m_dRadius(30.f), m_nTimer(1), m_nDir(0), m_pScene(nullptr), m_dSpeed(0.f), m_nColor(0)
{
	SetPos(POINT{ 0, 0 });
	SetID(0);
	m_nDir = rand() % 2;
	m_dSpeed = (rand() % 5) + 1.f;
	m_nColor = rand() % 7;
	SetBrush();
}

CircleObject::~CircleObject()
{
}

void CircleObject::Render()
{
	SelectObject(hdc, m_brush);
	Ellipse(hdc, m_tPos.x - m_dRadius, m_tPos.y - m_dRadius, m_tPos.x + m_dRadius, m_tPos.y + m_dRadius);
	RenderID();
}

void CircleObject::RenderID()
{
	wstring tmp = to_wstring(m_nID);
	TextOutW(hdc, m_tPos.x, m_tPos.y, tmp.c_str(), tmp.length());
}

bool CircleObject::Collide()
{
	LONG x = mousePos.x - m_tPos.x;
	LONG y = mousePos.y - m_tPos.y;
	double dist = sqrt((double)(x * x + y * y));

	if (dist <= m_dRadius)
		return true;

	return false;
}

void CircleObject::Destroy()
{
	dynamic_cast<PointCircleScene*>(m_pScene)->RemoveObject(m_nID);
}

void CircleObject::Update()
{
	CheckMouseInput();
	Move();
}

void CircleObject::Jump()
{
	m_tPos.x = rand() % bufferRT.right;
	m_tPos.y = rand() % bufferRT.bottom;
}

void CircleObject::CheckMouseInput()
{
	if (GetAsyncKeyState(VK_LBUTTON) & 0x8000)
	{
		if (!isClick)
		{
			MouseDown();
			isClick = true;
		}
	}
	else
	{
		if (isClick)
		{
			MouseUp();
			isClick = false;
		}
	}
}

void CircleObject::MouseUp()
{
	if (Collide())
	{
		if (dynamic_cast<PointCircleScene*>(m_pScene)->IsCurrentID(m_nID) == true)
		{
			Destroy();
		}
		else
		{
			dynamic_cast<PointCircleScene*>(m_pScene)->AddCircle();
			Jump();
		}
	}
}

void CircleObject::MouseDown()
{
}

void CircleObject::SetBrush()
{
	switch (m_nColor)
	{
	case 0:
	{
		m_brush = CreateSolidBrush(RED);
	}
	break;
	case 1:
	{
		m_brush = CreateSolidBrush(GREEN);
	}
	break;
	case 2:
	{
		m_brush = CreateSolidBrush(BLUE);
	}
	break;
	case 3:
	{
		m_brush = CreateSolidBrush(CYAN);
	}
	break;
	case 4:
	{
		m_brush = CreateSolidBrush(MAGENTA);
	}
	break;
	case 5:
	{
		m_brush = CreateSolidBrush(YELLOW);
	}
	break;
	case 6:
	{
		m_brush = CreateSolidBrush(BLACK);
	}
	break;
	default:
		break;
	}
}

void CircleObject::Move()
{
	--m_nTimer;
	if (m_nTimer == 0)
	{
		m_nTimer = 1;
		switch (m_nDir)
		{
		case 0: // 우측으로 이동
		{
			if (m_tPos.x >= (bufferRT.right - (LONG)m_dRadius))
			{
				m_tPos.x = (bufferRT.right - (LONG)m_dRadius);
				m_nDir += 2;
				m_nDir %= 4;
			}
			m_tPos.x += m_dSpeed;
		}
		break;
		case 1: // 하단으로 이동
		{
			if (m_tPos.y >= (bufferRT.bottom - (LONG)m_dRadius))
			{
				m_tPos.y = (bufferRT.bottom - (LONG)m_dRadius);
				m_nDir += 2;
				m_nDir %= 4;
			}
			m_tPos.y += m_dSpeed;
		}
		break;
		case 2: // 좌측으로 이동
		{
			if (m_tPos.x <= (LONG)m_dRadius)
			{
				m_tPos.x = (LONG)m_dRadius;
				m_nDir += 2;
				m_nDir %= 4;
			}
			m_tPos.x -= m_dSpeed;
		}
		break;
		case 3: // 상단으로 이동
		{
			if (m_tPos.y <= (LONG)m_dRadius)
			{
				m_tPos.y = (LONG)m_dRadius;
				m_nDir += 2;
				m_nDir %= 4;
			}
			m_tPos.y -= m_dSpeed;
		}
		break;
		default:
			break;
		}
	}
}