Win32 API/기초

Win32 API 기초 : PeekMessage (1)

시플마 2024. 6. 18. 21:52

직접 마우스를 통해 사각형을 그릴 수 있도록 

코드를 작성해 봅시다.

 

이를 위한 각종 전역 변수를 추가해 보죠.

 

117번째 줄에 있는 구조체 tObjInfo는

POINT형 멤버 두 가지를 갖고 있습니다.

 

멤버 g_ptObjPos는 사각형의 중심 좌푯값을 나타냅니다.

맴버 g_ptObjScale은 사각형의 크기를 나타냅니다.

 

 

123번째 줄에 있는 vector형 객체 v_recObj는 

사각형 객체들을 관리하는 vector이죠.

 

 

124번째 줄에 있는 전역 변수 g_ptLT는

사각형의 좌상단 좌푯값을,

 

125번째 줄에 있는 전역 변수 g_ptRB는

사각형의 우하단 좌푯값을 의미합니다.

 

 

128번째 줄에 있는 bool형 변수 bLbtnDown은

현재 마우스 좌클릭이 발생한 상태인지 아닌지를

나타내기 위한 변수입니다.

 

 

 


 

 

 

 

 

WndProc 함수에 

 

WM_LBUTTONDOWN case를 추가하여

마우스 좌클릭이 들어오면 

사각형의 좌상단 좌푯값을 나타내는 g_ptLT의

x 좌푯값에 클릭한 곳의 x 좌푯값을 대입합니다.


사각형의 좌상단 좌푯값을 나타내는 g_ptLT의

y 좌푯값에 클릭한 곳의 y 좌푯값을 대입합니다.

 

 

그 다음 마우스 좌클릭이 발생한 상태임을 

나타내기 위해 bLbtnDown에 true를 대입합니다. 

 

 

 

이번에는 WM_MOUSEMOVE case를 추가합니다.

 

마우스가 조금이라도 움직이면 해당 case의 코드가 작동하죠.

 

좌클릭을 통해 사각형 좌상단의 위치를 결정했다면 

이후에는 마우스를 움직여서 사각형의 우하단 위치를

정하고 좌클릭을 통해 확정하도록 합시다.

 

 

마우스가 움직일 때마다 234번째 줄 코드에 의해 

사각형의 우하단을 의미하는 g_ptRB의 멤버 x에

마우스가 최종적으로 움직인 위치의 x 좌푯값을 대입합니다. 

 

235번재 줄 코드도 마찬가지로 g_ptRB의 멤버 y에

마우스가 최종적으로 움직인 위치의 y 좌푯값을 대입합니다. 

 

 

근데 이러한 동작이 사용자 눈에도 보여야겠죠?

 

236번째 줄에 있는 InvaildateRect 함수를 통해 

마우스가 움직일 때마다 새로운 사각형이 그려지도록 합시다.

 

 

 

WM_LBUTTONUP case를 추가합니다.

 

이는 좌클릭이 발생한 이후 클릭 버튼에서 손을 떼는 순간입니다.

 

사각형 객체인 tRec을 생성하고 

사각형의 중심 좌푯값 중 x 좌푯값에 전역 변수 g_ptLT의 x 좌푯값과

전역 변수 g_ptRB의 x 좌푯값을 더한 후 2로 나눈 값을 대입합니다.

 

즉 좌상단의 x 좌푯값과 우하단의 x 좌푯값을 더하고 2로 나눈 값이

중심 좌푯값의 x 값이 되는 것이죠.

 

 

사각형의 중심 좌푯값 중 y 좌푯값에 전역 변수 g_ptLT의 y 좌푯값과

전역 변수 g_ptRB의 y 좌푯값을 더한 후 2로 나눈 값을 대입합니다.

 

즉 좌상단의 y 좌푯값과 우하단의 y 좌푯값을 더하고 2로 나눈 값이

중심 좌푯값의 y 값이 되는 것이죠.

 

 

사각형의 가로 길이인 g_ptObjScale의 멤버 x에 g_ptRB의 멤버 x 값에서

g_ptLT의 멤버 x 값을 뺀 후 abs 함수를 통해 절댓값으로 표시한 후 대입합니다.

 

우하단의 x 좌푯값에서 좌상단의 x 좌푯값을 빼면

사각형의 가로 길이가 나오기 때문이죠.

 

 

사각형의 세로 길이인 g_ptObjScale의 멤버 y에 g_ptRB의 멤버 y 값에서

g_ptLT의 멤버 y 값을 뺀 후 abs 함수를 통해 절댓값으로 표시한 후 대입합니다.

 

우하단의 y 좌푯값에서 좌상단의 y 좌푯값을 빼면

사각형의 세로 길이가 나오기 때문이죠.

 

절댓값으로 표시한 후 대입하는 이유는 g_ptRB의 멤버의 값들이 

반드시 g_ptLT의 맴버의 값들보다 크다는 보장이 없습니다.

 

왜냐하면 좌클릭을 통해 사각형의 좌상단 위치를 정한 후

마우스를 그보다 왼쪽으로 이동시켜 좌클릭하여 

사각형의 우하단 위치를 정하면 g_ptRB의 좌푯값이 더 작겠죠?

 

이 경우 g_ptRB에서 g_ptLT를 빼면 음수가 나옵니다.

 

이를 대비하기 위해 abs 함수를 통해 연산 결과를

절댓값으로 표시하여 대입하는 것이죠.

 

 

그런 다음 250번째 줄에서

push_back 함수를 통해 vector 끝에 

WM_LBUTTONUP case를 통해 만들어진 사각형을 추가합니다.

 

 

그리고 버튼에서 손을 뗀 상태이므로

bLbtnDown의 값을 false로 설정해 줍니다.

 

 


 

 

 

이제 위에서 작성한 코드가 

화면에서 작동하도록 해야겠죠.

 

WM_PAINT case를 보시면 

172번째 줄에 if 문이 있습니다. 

 

bLbtnDown이 true이면, 즉 좌클릭이 발생한 경우

사각형을 그리기 시작하죠.

 

 

그 다음 마우스를 움직여 WM_MOUSEMOVE case의

코드가 실행되어 우하단 좌푯값을 정할 겁니다. 

 

그리고 버튼에서 손을 떼면 사각형이 만들어지고 

vedtor형 객체 v_recObj에 만들어진 사각형이 추가될 겁니다.

 

 

178번째 for 문을 통해 vedtor형 객체 v_recObj에 저장된

모든 사각형이 화면에 출력될 겁니다.

 

좌상단 좌표 중 x 값은, 사각형 중심 x 좌푯값에서

사각형의 가로 길이를 2로 나눈 값을 빼주면 나오겠죠.

 

좌상단 좌표 중 y 값은, 사각형 중심 y 좌푯값에서

사각형의 세로 길이를 2로 나눈 값을 빼주면 나오겠죠.


우하단 좌표 중 x 값은, 사각형 중심 x 좌푯값에서

사각형의 가로 길이를 2로 나눈 값을 더하면 나오겠죠.

 

우하단 좌표 중 y 값은, 사각형 중심 y 좌푯값에서

사각형의 세로 길이를 2로 나눈 값을 더하면 나오겠죠.

 

이 for 문으로 인해 사각형을 새로 그릴 때마다

계속 누적되며 화면에 출력될 겁니다.

 

 

 


 

 

 

그런데 사각형을 그리면 그릴수록 마우스를 움직이기만 해도

화면이 심하게 깜빡거립니다.

 

이유는 마우스를 움직이면 InvaildateRect 함수를 통해

무효화 영역을 발생시켜 화면을 다시 그리도록 하였죠?

 

그로 인해서 마우스를 조금이라도 움직이면

화면에 출력된 모든 사각형이 다시 그려지게 됩니다.

 

이때 빠르게 그려지긴 하지만

사람 눈에는 결국 깜빡이는 것처럼 보이게 되는 것이죠.

 

 

이를 해결하려면 픽셀 데이터를 두 개로 관리하여야 합니다.

 

쉽게 예를 들자면, 화가가 그림 그리는 과정을 보여주지 않고

손님에게는 완성된 그림만을 보여주는 것이죠.

 

마찬가지로 사각형이 그려지는 장면은 보여주지 않고

모든 사각형이 그려진 후에 그 장면만 화면에 출력하면 

사용자가 깜빡이는 것을 보지 않을 수 있습니다.

 

 

이것 말고도 위에서 작성한 프로그램의 문제점은 더 있습니다.

 

바로 메시지가 없으면 프로그램이 동작하지 않는다는 점입니다.

 

만약 게임을 만든다고 가정해 봅시다.

 

내 캐릭터가 움직이지 않아도 인공지능을 가진

몬스터나 NPC는 움직이게 하고 싶습니다.

 

그러나 위 프로그램에서는 사용자가 키 입력이나 마우스 입력 등을

통해 메시지를 발생시키지 않으면 아무런 동작도 하지 않기 때문에,

몬스터나 NPC의 위치가 바뀌면, 다시 바뀐 위치에 몬스터나 NPC가

그려져야 하는데 다시 그려지는 동작 자체가 발생하지 않는다는 것이죠.

 

 

이 문제를 임시로 해결하는 방법이 있는데 메시지를 강제로 주는 겁니다.

 

main 함수에서 

 

46번째 줄에 SetTimer라는 함수가 추가되었습니다.

 

첫 번째 인자로 타이머를 달아 줄 윈도우를 넘깁니다. 

 

기존 윈도우는 InitInstance 함수에서만 유효한 지역 변수이기 때문에

main 함수에서 해당 윈도우에 타이머를 달아 주려고 하면

윈도우를 인식하지 못해 오류가 발생합니다.

 

그래서 전역 변수 g_hWnd를 만들어 아래 코드처럼

 

g_hWnd에 생성된 윈도우를 저장하도록 합시다.

 

이후 SetTimer 함수에 해당 윈도우를 인자로 넘기는 것이죠.

 

 

SetTimer 함수의 두 번째 인자를 통해 타이머 ID를 설정하는 겁니다.

0번으로 설정하도록 하죠.

 

세 번째 인자는 SetTimer 함수의 발생 지연 시간입니다. 

최대한 빠르게 타이머가 작동하도록 0으로 합시다.

 

네 번째 인자는 타이머가 발생할 때마다 같이 호출할 

함수의 주솟값인데 호출할 함수가 없으니 nullptr를 넘기도록 하죠.

 

 

타이머도 윈도우에 부착된 Kernel Object이므로

제거를 해 줘야 합니다. 그래서 프로그램이

종료되기 전에 58번째 줄에 있는 KillTimer 함수를 통해

타이머를 제거하도록 하죠.

 

제거할 타이머가 부착된 윈도우와

제거할 타이머의 ID를 인자로 넘기면 됩니다.

 

 

그 다음 WndProc 함수에 

 

WM_TIMER case를 추가하면

타이머가 발생할 때마다

해당 case 안의 코드가 실행될 겁니다.

 

 

타이머의 지연 시간은 0이므로 WM_TIMER case 안에

실행될 코드가 얼마나 있느냐에 프로그램 성능이 크게 

달라질 겁니다. 매 순간 타이머가 작동하기 때문이죠.

 

그렇다고 지연 시간을 1000,

즉 1초(1000ms)로 주어도 문제입니다.

 

1초에 적어도 30개의 장면은 출력해 줘야 쾌적한 게임 환경으로

느껴질텐데, 1초 동안 몬스터나 NPC가 지워지고 새로운 위치에 

그려지는 동작이 다라면 상당히 답답하게 느껴지겠죠?

 

 

이렇게 윈도우에서 제공하는 메시지 큐에 메시지를 넣고

그것을 꺼낸 후 처리해야 하는 처리기 함수로 보내고 

그 함수에서 처리하는 과정을 매 프레임마다 진행해야 하는

윈도우 메시지 처리 기반 방식으로 게임을 만들기에는

매우 비효율적이고 느립니다.

 

그래서 main 함수에서

 

49번째 줄에 있는 while 문의 구조를 바꾸어 줄 필요가 있죠. 

 

 

 


강의 출처 : https://www.youtube.com/watch?v=YlKf7OkHndI&list=PL4SIC1d_ab-ZLg4TvAO5R4nqlJTyJXsPK&index=5