0305 화면에 도형 그리기/ 마우스,키보드로 입출력받아 움직이기
작성 코드
실습 1: 자동차 그려서 a,d키 입력받아 움직이기
실습 2: 사각형 10개를 만들고 드래그앤 드롭 기능 넣기
사각형이 특정범위를 벗어나면 별로 변하게 하기
실습 3: destroyer라는 사각형 객체를 만들어서 지우개처럼 나머지 사각형들을 지우기
실습 4: 큰 사각형 2개 , 작은 사각형 1개를 만들고 큰사각형에만 제어권이 있다 , 큰사각형을 벗어날 수없고
충돌시 다른 큰 사각형으로 소유권이 넘어가는 작은 사각형 만들기
/*
WinAPI
: Windows Application Programming Interface
운영체제가 응용프로그램 개발을 위해 제공하는 함수의 집합
핸들(Handle) : 윈도우 각 객체를 구분하는 인덱스 번호(정수)
메시지(Message) : 윈도우에서 발생하는 이벤트를 운영체제가
윈도우에 전달하는 데이터 모음
*/
#include <Windows.h>
#include <iostream>
HINSTANCE g_hInstance; // 프로그램 인스턴스 핸들
HWND g_hWnd; // 윈도우 핸들
LPCWSTR g_lpszClassName = (LPCWSTR)TEXT("윈도우 API 사용하기");
enum class ShapeType : uint8_t //unsigned char 8비트(1바이트) 0 ~ 255 , int32_t: 32비트(4바이트) int
{
None,
Nemo,
Star
};
struct BeingStar
{
POINT pt; // POINT는 (x, y) 형태의 좌표 값을 저장하는 구조체, x와 y는 각각 LONG 타입(32비트 정수)
ShapeType type;
int size;
};
/*
1. 자동차 그리기 (함수화)
2. a, d키로 움직이기
3. 네모 or 동그라미를 그리고 마우스로 드래그 & 드랍 구현
4. 별이 되어라!
바닥에 네모 10개를 그린다.
마우스 드래그를 통해서 위로 이동
특정 높이 이상 위치에 드랍을 하면
네모 -> 별 모양으로 바뀐다.
5. 제어권이 있는 상자 구현
5_1. 큰 상자 두개, 작 은 상자 한개가 존재
5_2. 큰 상자 안에는 작은 상자가 들어 있다.(제어권이 있는 상자)
5_3. 작은 상자는 큰 상자를 벗어날 수 없다.
5_4. 큰 상자끼리 부딪히면 작은 상자가 상대 큰 상자에게 넘어간다. (제어권이 이동)
*/
#pragma region 함수선언
void RenderStar(HDC hdc, int posX, int posY);
void RenderRect(HDC hdc, int x, int y, int width, int height);
void RenderRectAtCenter(HDC hdc, int centerX, int centerY, int width, int height);
void RenderEllipse(HDC hdc, int x, int y, int width, int height);
void RenderEllipseAtCenter(HDC hdc, int centerX, int centerY, int width, int height);
void RenderCar(HDC hdc, int posX, int posY);
bool PointInRect(POINT ptMouse, RECT rc); // ptInRect
bool RectInRect(RECT rc1, RECT rc2);
void UpdateRect(RECT& rc, POINT pt);
LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam);
#pragma endregion
#pragma region WinMain함수
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpszCmdParam, int nCmdShow) //WinMain()에서 윈도우 생성 및 메시지 루프 실행
{
g_hInstance = hInstance;
// 윈도우를 생성하기 위한 데이터 셋팅
WNDCLASS wndClass;
wndClass.cbClsExtra = 0;
wndClass.cbWndExtra = 0;
wndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wndClass.hCursor = LoadCursor(g_hInstance, IDC_ARROW);
wndClass.hIcon = LoadIcon(g_hInstance, IDI_APPLICATION);
wndClass.hInstance = g_hInstance;
wndClass.lpfnWndProc = WndProc; // 함수의 이름은 메모리주소이다.
wndClass.lpszClassName = g_lpszClassName;
wndClass.lpszMenuName = NULL;
wndClass.style = CS_HREDRAW | CS_VREDRAW; // | : 비트연산자
RegisterClass(&wndClass); //윈도우 클래스 등록
g_hWnd = CreateWindow(g_lpszClassName, g_lpszClassName,
WS_OVERLAPPEDWINDOW, 50, 50, 1080, 720,
NULL, NULL, g_hInstance, NULL); //윈도우 생성
if (ShowWindow(g_hWnd, nCmdShow)) //윈도우 표시
{
}
else
{
}
MSG message;
while (GetMessage(&message, 0, 0, 0)) //메시지 처리 (사용자의 입력을 감지하는 부분)
{
TranslateMessage(&message);
DispatchMessage(&message);
}
return message.wParam;
} // 여기까지 윈도우를 등록하고 생성한 후 메시지 루프를 실행하여 입력을 처리하는 부분
#pragma endregion
#pragma region 별찍기,자동차,Destroyer
void RenderStar(HDC hdc, int posX, int posY)
{
MoveToEx(hdc, posX + 60, posY + 20, NULL);
LineTo(hdc, posX - 60, posY + 20);
LineTo(hdc, posX + 40, posY - 60);
LineTo(hdc, posX, posY + 60);
LineTo(hdc, posX - 40, posY - 60);
LineTo(hdc, posX + 60, posY + 20);
}
// 좌상단 기준
void RenderRect(HDC hdc, int x, int y, int width, int height)
{
Rectangle(hdc, x, y, x + width, y + height);
}
// 가운데 기준
void RenderRectAtCenter(HDC hdc, int centerX, int centerY, int width, int height)
{
Rectangle(hdc, centerX - (width / 2), centerY - (height / 2),
centerX + (width / 2), centerY + (height / 2));
}
// 좌상단 기준
void RenderEllipse(HDC hdc, int x, int y, int width, int height)
{
Ellipse(hdc, x, y, x + width, y + height);
}
// 가운데 기준
void RenderEllipseAtCenter(HDC hdc, int centerX, int centerY, int width, int height)
{
Ellipse(hdc, centerX - (width / 2), centerY - (height / 2),
centerX + (width / 2), centerY + (height / 2));
}
void RenderCar(HDC hdc, int posX, int posY)
{
// 자동차 상단
RenderRectAtCenter(hdc, posX, posY - (60 + 50), 220, 100);
// 자동차 하단
RenderRectAtCenter(hdc, posX, posY, 350, 120);
// 바퀴 왼쪽
RenderEllipseAtCenter(hdc, posX - 80, posY + (60), 80, 80);
// 바퀴 오른쪽
RenderEllipseAtCenter(hdc, posX + 80, posY + (60), 80, 80);
}
bool PointInRect(POINT ptMouse, RECT rc) // 마우스좌표가 사각형 안에 있는지 확인
{
if (ptMouse.x < rc.left || ptMouse.x > rc.right
|| ptMouse.y < rc.top || ptMouse.y > rc.bottom)
{
return false;
}
return true;
}
bool RectInRect(RECT rc1, RECT rc2) //두 사각형이 겹치는지 확인
{
if (rc1.right < rc2.left || rc1.left > rc2.right
|| rc1.top > rc2.bottom || rc1.bottom < rc2.top)
{
return false;
}
return true;
}
void UpdateRect(RECT& rc, POINT pt) //사각형을 특정좌표로 이동
{
int width = rc.right - rc.left;
int height = rc.bottom - rc.top;
rc.left = pt.x - (width / 2);
rc.right = rc.left + width;
rc.top = pt.y - (height / 2);
rc.bottom = rc.top + height;
}
#pragma endregion
//WndProc()에서 윈도우 메시지 처리(WM_PAINT, WM_LBUTTONDOWN, WM_MOUSEMOVE 등)
LRESULT WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
// static 변수 : 함수 내에서 선언 , 데이터 영역에 메모리가 할당 (전역변수와 같음)
// 프로그램 종료 시에 메모리 해제 , 해당 함수에서만 접근이 가능하다.
static int mousePosX = 0, mousePosY = 0;
static int posX = 0, posY = 0;
static RECT rc { 10, 10, 110, 110 };
static POINT pt;
wchar_t szText[128];
static BeingStar stars[10];
static int selectedIndex = -1;
static bool isGrap = false;
static RECT destroyer{ 50, 50, 150, 150 };
static RECT bigBox1 = { 100, 100, 300, 300 }; // 큰박스1
static RECT bigBox2 = { 400, 100, 600, 300 }; // 큰박스2
static RECT smallBox = { 150, 150, 200, 200 }; // 작은 박스
// 큰박스1 조작
static RECT* controlledBox = &bigBox1;
switch (iMessage)
{
case WM_CREATE: //윈도우가 처음생성될때 한번만 실행 (초기화 작업)
//초기 네모 10개 생성
for (int i = 0; i < 10; i++)
{
stars[i].type = ShapeType::Nemo;
stars[i].pt = POINT{ 50 + i * (100 + 20), 600 };
stars[i].size = 100;
}
break;
case WM_KEYDOWN:
{
int moveX = 0, moveY = 0;
switch (wParam)
{
case 'W': case 'w':
moveY = -10;
break;
case 'A': case 'a':
moveX = -10;
break;
case 'S': case 's':
moveY = 10;
break;
case 'D': case 'd':
moveX = 10;
break;
}
// 큰 박스 이동
OffsetRect(controlledBox, moveX, moveY);
// 작은 박스의 원래 크기 저장
int smallBoxWidth = smallBox.right - smallBox.left;
int smallBoxHeight = smallBox.bottom - smallBox.top;
// 작은 박스를 큰 박스 안으로 제한
if (smallBox.left < controlledBox->left) smallBox.left = controlledBox->left;
if (smallBox.right > controlledBox->right) smallBox.left = controlledBox->right - smallBoxWidth;
if (smallBox.top < controlledBox->top) smallBox.top = controlledBox->top;
if (smallBox.bottom > controlledBox->bottom) smallBox.top = controlledBox->bottom - smallBoxHeight;
// 크기 유지
smallBox.right = smallBox.left + smallBoxWidth;
smallBox.bottom = smallBox.top + smallBoxHeight;
// 충돌 감지 후 박스 소유권 변경 및 작은 박스 이동
RECT tempRect;
if (controlledBox == &bigBox1 && IntersectRect(&tempRect, &bigBox1, &bigBox2))
{
controlledBox = &bigBox2; // 소유권 변경
// 작은 박스를 bigBox2의 중앙으로 이동
int newSmallBoxX = (bigBox2.left + bigBox2.right) / 2 - (smallBoxWidth / 2);
int newSmallBoxY = (bigBox2.top + bigBox2.bottom) / 2 - (smallBoxHeight / 2);
OffsetRect(&smallBox, newSmallBoxX - smallBox.left, newSmallBoxY - smallBox.top);
}
else if (controlledBox == &bigBox2 && IntersectRect(&tempRect, &bigBox1, &bigBox2))
{
controlledBox = &bigBox1; // 소유권 변경
// 작은 박스를 bigBox1의 중앙으로 이동
int newSmallBoxX = (bigBox1.left + bigBox1.right) / 2 - (smallBoxWidth / 2);
int newSmallBoxY = (bigBox1.top + bigBox1.bottom) / 2 - (smallBoxHeight / 2);
OffsetRect(&smallBox, newSmallBoxX - smallBox.left, newSmallBoxY - smallBox.top);
}
// 강제 화면 갱신하여 충돌 감지 즉시 반영
InvalidateRect(g_hWnd, NULL, TRUE);
UpdateWindow(g_hWnd);
break;
}
case WM_LBUTTONDOWN: //마우스 왼쪽버튼 눌림
mousePosX = LOWORD(lParam); //x좌표추출
mousePosY = HIWORD(lParam); //y좌표추출
pt.x = mousePosX;
pt.y = mousePosY;
for (int i = 0; i < 10; i++)
{
UpdateRect(rc, stars[i].pt);
if (PointInRect(pt, rc))
{
selectedIndex = i;
break;
}
}
isGrap = PointInRect(pt, destroyer);
InvalidateRect(g_hWnd, NULL, true); //화면 갱신
break;
case WM_LBUTTONUP: //마우스 왼쪽버튼 뗌
selectedIndex = -1;
isGrap = false;
//posX = LOWORD(lParam);
//posY = HIWORD(lParam);
break;
case WM_MOUSEMOVE:
mousePosX = LOWORD(lParam);
mousePosY = HIWORD(lParam);
/*if (selectedIndex != -1)
{
stars[selectedIndex].pt.x = mousePosX;
stars[selectedIndex].pt.y = mousePosY;
}
if (isGrap)
{
UpdateRect(destroyer, POINT{ mousePosX, mousePosY });
for (int i = 0; i < 10; i++)
{
if (stars[i].type == ShapeType::None) continue;
UpdateRect(rc, stars[i].pt);
if (RectInRect(destroyer, rc))
{
stars[i].type = ShapeType::None;
}
}
}
InvalidateRect(g_hWnd, NULL, true);*/
break;
case WM_PAINT: //윈도우를 다시 그릴때마다 호출(그래픽을 화면에 출력)
hdc = BeginPaint(g_hWnd, &ps);
// 그리기 위한 로직
TextOut(hdc, 20, 20, TEXT("Hello, World!"), strlen("Hello, World!"));
wsprintf(szText/* 문자열 공간 */, TEXT("Mouse X : %d, Y : %d")/* 형태 */, mousePosX, mousePosY);
TextOut(hdc, 20, 60, szText, wcslen(szText)); //wcslen은 와이드 문자열(유니코드 문자열)을 다룸
//출력할 문자열의 길이를 정확히 전달하기 위해 사용
// 큰 박스 1 그리기
Rectangle(hdc, bigBox1.left, bigBox1.top, bigBox1.right, bigBox1.bottom);
// 큰 박스 2 그리기
Rectangle(hdc, bigBox2.left, bigBox2.top, bigBox2.right, bigBox2.bottom);
// 작은 박스 그리기
Rectangle(hdc, smallBox.left, smallBox.top, smallBox.right, smallBox.bottom);
#pragma region 별이되어라
/*for (int i = 0; i < 10; i++)
{
if (stars[i].type == ShapeType::None)
{
continue;
}
else if (stars[i].type == ShapeType::Nemo)
{
RenderRectAtCenter(hdc, stars[i].pt.x, stars[i].pt.y,
stars[i].size, stars[i].size);
}
else
{
RenderStar(hdc, stars[i].pt.x, stars[i].pt.y);
}
}
Rectangle(hdc, destroyer.left, destroyer.top, destroyer.right, destroyer.bottom);*/
#pragma endregion
EndPaint(g_hWnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0); //메시지 루프 종료하여 프로그램을 완전히 종료
break;
}
return DefWindowProc(hWnd, iMessage, wParam, lParam);
}
알게된점
1. 함수의 원형은 함수의 메모리 주소
2. 윈도우api 는 크게 WinMain 과 WndProc 함수로 동작한다.
3. 핸들은 각 윈도우 객체를 구분하는 인덱스고 메시지는 윈도우에서 발생하는 이벤트들을 운영체제가 윈도우에 전달하는 데이터 모음이다.
4. WinMain에서 윈도우 생성, 메시지 루프를 실행한다.
5. WndProc에서 화면에 그림을 그리거나 종료하거나, 초기화를 하거나, 키, 마우스 입력을 받는 메시지를 처리한다.
6. WM_LBUTTON에서 UP, DOWN이 있는데 보통 UP은 버튼을 눌렀을때 DOWN은 버튼을 뗀것을 감지한다.
7. 메시지를 처리하고 수행할때마다 화면을 갱신해줘야 보인다.
8. static변수는 전역변수처럼 데이터영역에 메모리가 할당되고 프로그램종료시 해제된다.
9.uint8_t 은 unsigned char 8비트(1바이트) 0 ~ 255
int32_t 은 int 32비트(4바이트) -2,147,483,648 ~ 2,147,483,647