[2]Character input - 2D game programming
이번 포스팅에는 이전 Hello world를 넘어서, character를 받고 그 문자를 윈도우 콘솔창에 띄우는 그런 예제에 대한 소스 코드를 분석해보도록 하겠다. 해당 예제는 이전 포스팅에서 적어놓은 2D Games Programming 사이트에서 다운로드 받을 수 있다.
다음은 소스코드 전체이다. 역시나 소스코드, WinMain, WinProc, CreateMainWindow 순으로 살펴보도록 하겠다.
윈도우 프로그래밍에서는 이 3개의 함수가 기본 템플릿이라고 생각할 수 있을 것이라고 생각된다.
// Programming 2D Games
// Copyright (c) 2011 by:
// Charles Kelly
// Chapter 2 Character Input with Windows API v1.0
// winmain.cpp
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
// Function prototypes
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int);
bool CreateMainWindow(HINSTANCE, int);
LRESULT WINAPI WinProc(HWND, UINT, WPARAM, LPARAM);
// Global variables
HINSTANCE hinst;
HDC hdc; // handle to device context
TCHAR ch = ' '; // character entered
RECT rect; // rectangle structure
PAINTSTRUCT ps; // used in WM_PAINT
// Constants
const char CLASS_NAME[] = "Keyboard";
const char APP_TITLE[] = "Character Input";
const int WINDOW_WIDTH = 400; // width of window
const int WINDOW_HEIGHT = 300; // height of window
//=============================================================================
// Starting point for a Windows application
//=============================================================================
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
MSG msg;
// Create the window
if (!CreateMainWindow(hInstance, nCmdShow))
return false;
// main message loop
int done = 0;
while (!done)
{
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
//look for quit message
if (msg.message == WM_QUIT)
done = 1;
//decode and pass messages on to WinProc
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return msg.wParam;
}
//=============================================================================
// window event callback function
//=============================================================================
LRESULT WINAPI WinProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
switch( msg )
{
case WM_DESTROY:
//tell Windows to kill this program
PostQuitMessage(0);
return 0;
case WM_CHAR: // a character was entered by the keyboard
switch (wParam) // the character is in wParam
{
case 0x08: // backspace
case 0x09: // tab
case 0x0A: // linefeed
case 0x0D: // carriage return
case 0x1B: // escape
MessageBeep((UINT) -1); // beep but do not display
return 0;
default: // displayable character
ch = (TCHAR) wParam; // get the character
InvalidateRect(hwnd, NULL, TRUE); // force WM_PAINT
return 0;
}
case WM_PAINT: // the window needs to be redrawn
hdc = BeginPaint(hwnd, &ps); // get handle to device context
GetClientRect(hwnd, &rect); // get the window rectangle
// Display the character
TextOut(hdc, rect.right/2, rect.bottom/2, &ch, 1);
EndPaint(hwnd, &ps);
return 0;
default:
return DefWindowProc( hwnd, msg, wParam, lParam );
}
}
//=============================================================================
// Create the window
// Returns: false on error
//=============================================================================
bool CreateMainWindow(HINSTANCE hInstance, int nCmdShow)
{
WNDCLASSEX wcx;
HWND hwnd;
// Fill in the window class structure with parameters
// that describe the main window.
wcx.cbSize = sizeof(wcx); // size of structure
wcx.style = CS_HREDRAW | CS_VREDRAW; // redraw if size changes
wcx.lpfnWndProc = WinProc; // points to window procedure
wcx.cbClsExtra = 0; // no extra class memory
wcx.cbWndExtra = 0; // no extra window memory
wcx.hInstance = hInstance; // handle to instance
wcx.hIcon = NULL;
wcx.hCursor = LoadCursor(NULL,IDC_ARROW); // predefined arrow
wcx.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); // black background brush
wcx.lpszMenuName = NULL; // name of menu resource
wcx.lpszClassName = CLASS_NAME; // name of window class
wcx.hIconSm = NULL; // small class icon
// Register the window class.
// RegisterClassEx returns 0 on error.
if (RegisterClassEx(&wcx) == 0) // if error
return false;
// Create window
hwnd = CreateWindow(
CLASS_NAME, // name of the window class
APP_TITLE, // title bar text
WS_OVERLAPPEDWINDOW, // window style
CW_USEDEFAULT, // default horizontal position of window
CW_USEDEFAULT, // default vertical position of window
WINDOW_WIDTH, // width of window
WINDOW_HEIGHT, // height of the window
(HWND) NULL, // no parent window
(HMENU) NULL, // no menu
hInstance, // handle to application instance
(LPVOID) NULL); // no window parameters
// if there was an error creating the window
if (!hwnd)
return false;
// Show the window
ShowWindow(hwnd, nCmdShow);
// Send a WM_PAINT message to the window procedure
UpdateWindow(hwnd);
return true;
}
선언부이다.
// Programming 2D Games
// Copyright (c) 2011 by:
// Charles Kelly
// Chapter 2 Character Input with Windows API v1.0
// winmain.cpp
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
// Function prototypes
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int);
bool CreateMainWindow(HINSTANCE, int);
LRESULT WINAPI WinProc(HWND, UINT, WPARAM, LPARAM);
// Global variables
HINSTANCE hinst;
HDC hdc; // handle to device context
TCHAR ch = ' '; // character entered
RECT rect; // rectangle structure
PAINTSTRUCT ps; // used in WM_PAINT
// Constants
const char CLASS_NAME[] = "Keyboard";
const char APP_TITLE[] = "Character Input";
const int WINDOW_WIDTH = 400; // width of window
const int WINDOW_HEIGHT = 300; // height of window
선언부는 이전과 거의 동일하다.
#define WIN32_LEAN_AND_MEAN은 이전에 설명했듯 데이터의 중복 정의를 방지해준다. windows.h 인클루드 이전에 있어야 한다.
그리고 WinMain, CreateMainWindow, WinProc 3가지의 함수를 미리 선언한다.
전역변수로는 hinst, ch, rect, ps가 선언되어 있다. 이전 Hello world와 비교했을 때, ch, rect, ps가 새롭게 선언되었다.
그렇다면 이 전역변수들의 자료형에 대해서 알아보도록 하자. ^.^b
HINSTANCE형 hinst이다. HINSTANCE는 Instance handle을 구별하기 위해 사용된다. 다시 말하면 윈도우즈 운영체제에서 프로그램을 구별하기 위한 일종의 ID인셈.
다음은 HDC hdc이다. HDC 자료형은 단순하게 말하면 그리기 작업을 할 떄 사용한다고 한다. 아마 H + DC(Device Context)일 것이다. 아직은 정확히 어떻게 동작하는지는 알 수 없다. 함수가 정의된 부분에서 어떻게 사용되고 있는지 알아보겠다.
TCHAR ch이다. TCHAR는 유니코드와 아스키코드의 바이트 크기의 차이때문에 고안된 함수이다. 아래 포스팅에서 굉장히 자세하게 설명 되어있다.!!
https://hwan-shell.tistory.com/134
c/c++ TCHAR 정리.
요즘은 문자를 처리할 때 유니코드를 주로 사용합니다. WBCS라고 하며 모든 문자와 숫자를 2바이트로 처리합니다. 모든 프로그램이 유니코드를 사용하면 좋겠지만.... 실상은 아니죠. 초창기, 문�
hwan-shell.tistory.com
다음 Rect rect이다. 사각형 구조체라고 하는데 함수 정의부에서 어떻게 쓰이는지 살펴보겠다. 아마 그리기를 해야할 사각형 영역에 대한 정보를 담고 있는 구조체라고 판단된다.
마지막 PAINTSTRUCT ps이다.
https://ebebeb111.tistory.com/78
8. PAINTSTRUCT
< PAINTSTRUCT > WM_PAINT의 선두에서는 항상 BeginPaint 함수를 호출한다. 이 함수는 윈도우 핸들과 PAINTSTRUCT 구조체의 포인터를 인수로 취하는데 PAINTSTRUCT 구조체를 잘 활용하면 그리기 속도를 높일 수..
ebebeb111.tistory.com
해당 포스팅에서 PAINTSTRUCT의 구조가 나와 있었다.
다음은 함수 정의부이다. 정의된 함수 중 WinMain은 Hello world에서의 WinMain과 동일하므로 생략한다.
WinProc의 정의부이다.
LRESULT WINAPI WinProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
switch( msg )
{
case WM_DESTROY:
//tell Windows to kill this program
PostQuitMessage(0);
return 0;
case WM_CHAR: // a character was entered by the keyboard
switch (wParam) // the character is in wParam
{
case 0x08: // backspace
case 0x09: // tab
case 0x0A: // linefeed
case 0x0D: // carriage return
case 0x1B: // escape
MessageBeep((UINT) -1); // beep but do not display
return 0;
default: // displayable character
ch = (TCHAR) wParam; // get the character
InvalidateRect(hwnd, NULL, TRUE); // force WM_PAINT
return 0;
}
case WM_PAINT: // the window needs to be redrawn
hdc = BeginPaint(hwnd, &ps); // get handle to device context
GetClientRect(hwnd, &rect); // get the window rectangle
// Display the character
TextOut(hdc, rect.right/2, rect.bottom/2, &ch, 1);
EndPaint(hwnd, &ps);
return 0;
default:
return DefWindowProc( hwnd, msg, wParam, lParam );
}
}
Hello World와의 정의부와 비교해 보겠다.
//=============================================================================
// window event callback function
//=============================================================================
LRESULT WINAPI WinProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
switch( msg )
{
case WM_DESTROY:
//tell Windows to kill this program
PostQuitMessage(0);
return 0;
}
return DefWindowProc( hWnd, msg, wParam, lParam );
}
확연하게 차이가 나는것을 확인할 수 있다.
2개의 case가 추가되었는데, WM_CHAR과 WM_PAINT이다.
WH_CHAR 부터 살펴보겠다. wParam을 switch문의 인자로 받는다. 그리고 wParam의 값 0x08과 같은 케이스에는 아무 반응을 하지 않고 비프음을 울리게 된다.
그게 아닌 일반적인 경우 wParam의 저장되어 있는 char값을 (TCHAR) 형으로 변환하여 ch에 넣는다.
그리고 InvalidateRect()를 호출한다.
http://www.tipssoft.com/bulletin/board.php?bo_table=FAQ&wr_id=632
팁스소프트 > MFC/API 가이드 > [API] InvalidateRect 함수에 대하여...
[API] InvalidateRect 함수에 대하여... 글쓴이 : 관리자 트랙백 주소 : http://www.tipssoft.com/bulletin/tb.php/FAQ/632 팁스소프트에서 제공하는 프로그래밍과 관련된 자료나 정보들을 무단으로 복제하거��
www.tipssoft.com
여기서 아주 잘 설명을 해주었다. 이 함수는 필요한 일부분만을 무효화하여 새롭게 제작하게 된다.
WH_PAINT에 대해서 분석하겠다. 이 케이스는 윈도우 창을 새로 그려야 할 필요가 있을때 사용한다.
먼저 hdc = BeginPaint(hwnd, &ps) 이다. 이 beginpaint함수에서 윈도우 핸들과 PAINTSTRUCT 구조체 변수의 참조자를 넘겨받는다. 그리고 그에대한 반환값을 HDC 형 변수 hdc에 넣는다.
그 후 GetClientRect(hwnd, &rect) 함수를 호출하는데, 윈도우에서의 클라이언트 영역을 반환하게 된다.
Textout(hdc, rect.right/2, rect.bottom/2, &ch, 1) 함수를 호출하게 된다. 핸들러를 받고, XY좌표를 받아 출력할 텍스트와 출력할 텍스트의 길이 통해 한줄씩 출력해주게 된다.
마지막 EndPaint와 BeginPaint는 짝을 이루어 사용된다.
(cf. BeginPaint-EndPaint 이외에 GetDC - ReleaseDC를 사용하는 방법이 존재한다.)
마지막으로 CreateWindowMain 또한 Hello world와 동일한 내용이므로 생략한다.