WinAPI Programming
WinAPI_2020_0528_목_Point <-> Circle 충돌처리
HI2
2020. 5. 31. 20:29
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;
}
}
}