제주 탈출 일지

[3]Keys Down - 2D game programming 본문

윈도우 프로그래밍(DirectX)

[3]Keys Down - 2D game programming

귀건 2020. 9. 10. 19:10
728x90
반응형

이전 character input에서는 단순히 키의 입력만을 받아서 그 입력을 창에 띄워주는 것으로 끝이 났다.

Key Down에서는 현재 키가 눌리고 있는지 아닌지를 확인하는데 중점을 둔다. 그리고 각각의 키는 다른 위치에서 키다운을 표시한다.

 

F키가 눌려있는 화면

 

전체 소스코드이다.

// Programming 2D Games
// Copyright (c) 2011 by:
// Charles Kelly
// Chapter 2 Keyboard State 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
PAINTSTRUCT ps;             // used in WM_PAINT
bool vkKeys[256];           // state of virtual keys, false or true

// Constants
const char CLASS_NAME[]  = "Keyboard";
const char APP_TITLE[]   = "Keys Down";
const int  WINDOW_WIDTH  = 400;  // width of window
const int  WINDOW_HEIGHT = 400;  // height of window

//=============================================================================
// Starting point for a Windows application
//=============================================================================
int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPSTR     lpCmdLine,
                   int       nCmdShow)
{
    MSG     msg;

    // Create the main window
    if (!CreateMainWindow(hInstance, nCmdShow))
        return false;

    for (int i=0; i<256; i++)   // initialize virtual key array
        vkKeys[i] = 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 )
{
    short nVirtKey;                 // virtual-key code 
    const short SHIFTED = (short)0x8000; 
    TEXTMETRIC tm;                  // structure for text metrics 
    DWORD chWidth = 20;             // width of characters
    DWORD chHeight = 20;            // height of characters

    switch( msg )
    {
        case WM_CREATE:
            // get the text metrics
            hdc = GetDC(hwnd);
            GetTextMetrics(hdc, &tm);
            ReleaseDC(hwnd, hdc);
            chWidth = tm.tmAveCharWidth;        // average character width
            chHeight = tm.tmHeight;             // character height
            return 0;

        case WM_DESTROY:
            //tell Windows to kill this program
            PostQuitMessage(0);
            return 0;

        case WM_KEYDOWN:                                // key down
            vkKeys[wParam] = true;
            switch(wParam)
            {
                case VK_SHIFT:                          // shift key
                    nVirtKey = GetKeyState(VK_LSHIFT);  // get state of left shift
                    if (nVirtKey & SHIFTED)             // if left shift
                        vkKeys[VK_LSHIFT] = true;
                    nVirtKey = GetKeyState(VK_RSHIFT);  // get state of right shift
                    if (nVirtKey & SHIFTED)             // if right shift
                        vkKeys[VK_RSHIFT] = true;
                    break;
                case VK_CONTROL:                        // control key
                    nVirtKey = GetKeyState(VK_LCONTROL);
                    if (nVirtKey & SHIFTED)             // if left control
                        vkKeys[VK_LCONTROL] = true;
                    nVirtKey = GetKeyState(VK_RCONTROL);
                    if (nVirtKey & SHIFTED)             // if right control
                        vkKeys[VK_RCONTROL] = true;
                    break;
            }
            InvalidateRect(hwnd, NULL, TRUE);           // force WM_PAINT
            return 0;
            break;

        case WM_KEYUP:                                  // key up
            vkKeys[wParam] = false;
            switch(wParam)
            {
                case VK_SHIFT:                          // shift key
                    nVirtKey = GetKeyState(VK_LSHIFT); 
                    if ((nVirtKey & SHIFTED) == 0)      // if left shift
                        vkKeys[VK_LSHIFT] = false;
                    nVirtKey = GetKeyState(VK_RSHIFT); 
                    if ((nVirtKey & SHIFTED) == 0)      // if right shift
                        vkKeys[VK_RSHIFT] = false;
                    break;
                case VK_CONTROL:                        // control key
                    nVirtKey = GetKeyState(VK_LCONTROL);
                    if ((nVirtKey & SHIFTED) == 0)      // if left control
                        vkKeys[VK_LCONTROL] = false;
                    nVirtKey = GetKeyState(VK_RCONTROL);
                    if ((nVirtKey & SHIFTED) == 0)      // if right control
                        vkKeys[VK_RCONTROL] = false;
                    break;
            }
            InvalidateRect(hwnd, NULL, TRUE);    // force WM_PAINT
            return 0;
            break;

        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
                    return 0;           // non displayable character
                default:                // displayable character
                    ch = (TCHAR) wParam;    // get the character
                    InvalidateRect(hwnd, NULL, TRUE);   // force WM_PAINT
                    return 0;
            }

        case WM_PAINT:
            hdc = BeginPaint(hwnd, &ps);    // get handle to device context
            TextOut(hdc, 0, 0, &ch, 1);     // display the character
            
            // Display the state of vkKeys array
            // Display 'T' if key is down and 'F' is key is up
            for (int r=0; r<16; r++)
            {
                for (int c=0; c<16; c++)
                {
                    if (vkKeys[r*16+c])
                    {
                        SetBkMode(hdc, OPAQUE);         // opaque text background
                        TextOut(hdc,c*chWidth+chWidth*2,r*chHeight+chHeight*2,"T ", 2);
                    } else {
                        SetBkMode(hdc, TRANSPARENT);    // transparent text background
                        TextOut(hdc,c*chWidth+chWidth*2,r*chHeight+chHeight*2,"F ", 2);
                    }
                }
            }

            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(GRAY_BRUSH);    // gray 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;
}

 

점점 소스코드가 길어지지만, 구조는 동일하다. WinMain, CreateMainWindow, WinProc 3개의 함수로 구성되어 있다.


먼저 선언부이다.

#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
PAINTSTRUCT ps;             // used in WM_PAINT
bool vkKeys[256];           // state of virtual keys, false or true

// Constants
const char CLASS_NAME[]  = "Keyboard";
const char APP_TITLE[]   = "Keys Down";
const int  WINDOW_WIDTH  = 400;  // width of window
const int  WINDOW_HEIGHT = 400;  // height of window

이전 Character input 예제와 다른것은 bool vkKeys[256];이다 이 vkKey에서 키가 눌려있는지 아닌지에 대한 상태를 표시한다. 이외의 다른 내용은 이전 예제와 동일하다.


그 다음 WinMain이다.

int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPSTR     lpCmdLine,
                   int       nCmdShow)
{
    MSG     msg;

    // Create the main window
    if (!CreateMainWindow(hInstance, nCmdShow))
        return false;

    for (int i=0; i<256; i++)   // initialize virtual key array
        vkKeys[i] = 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;
}

 

if문과 while문 사이의 for문이 새롭게 추가되었다. 아까 선언부에서의 vkKeys를 false로 초기화 해주는 작업을 해주고 있다. 그 이외에는 동일하다


다음 이벤트를 담당하는 WinProc함수이다. switch문의 case가 길어진 것을 확인할 수 있다.

switch문의 case는 WM_CREATE, WM_DESTROY, WM_KEYDOWN, WM_KEYUP, WM_CHAR, WM_PAINT가 있다.

이 case들 중  DESTROY case를 제외한 나머지를 살펴보도록 한다.

먼저 선언된 변수들 부터 살펴보겠다. short 형 nVirtKey, 상수변수로 SHIFTED가 선언되었다. 그리고 textmetric 구조체 tm 변수가 선언되었고, DWORD chWidth 및 chHeight 변수가 선언되었다. 

    - TEXTMETRIC은 폰트와 관련된 데이터를 담고 있는 구조체이다.

    - DWORD는 4바이트(32bit) 자료형이다. 32비트 프로세서가 일을 처리할 때 사용하는 기본단위이다.

    - 16비트는 WORD이다.

LRESULT WINAPI WinProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
    short nVirtKey;                 // virtual-key code 
    const short SHIFTED = (short)0x8000; 
    TEXTMETRIC tm;                  // structure for text metrics 
    DWORD chWidth = 20;             // width of characters
    DWORD chHeight = 20;            // height of characters

    switch( msg )
    {
        case WM_CREATE:
            // get the text metrics
            hdc = GetDC(hwnd);
            GetTextMetrics(hdc, &tm);
            ReleaseDC(hwnd, hdc);
            chWidth = tm.tmAveCharWidth;        // average character width
            chHeight = tm.tmHeight;             // character height
            return 0;

        case WM_DESTROY:
            //tell Windows to kill this program
            PostQuitMessage(0);
            return 0;

        case WM_KEYDOWN:                                // key down
            vkKeys[wParam] = true;
            switch(wParam)
            {
                case VK_SHIFT:                          // shift key
                    nVirtKey = GetKeyState(VK_LSHIFT);  // get state of left shift
                    if (nVirtKey & SHIFTED)             // if left shift
                        vkKeys[VK_LSHIFT] = true;
                    nVirtKey = GetKeyState(VK_RSHIFT);  // get state of right shift
                    if (nVirtKey & SHIFTED)             // if right shift
                        vkKeys[VK_RSHIFT] = true;
                    break;
                case VK_CONTROL:                        // control key
                    nVirtKey = GetKeyState(VK_LCONTROL);
                    if (nVirtKey & SHIFTED)             // if left control
                        vkKeys[VK_LCONTROL] = true;
                    nVirtKey = GetKeyState(VK_RCONTROL);
                    if (nVirtKey & SHIFTED)             // if right control
                        vkKeys[VK_RCONTROL] = true;
                    break;
            }
            InvalidateRect(hwnd, NULL, TRUE);           // force WM_PAINT
            return 0;
            break;

        case WM_KEYUP:                                  // key up
            vkKeys[wParam] = false;
            switch(wParam)
            {
                case VK_SHIFT:                          // shift key
                    nVirtKey = GetKeyState(VK_LSHIFT); 
                    if ((nVirtKey & SHIFTED) == 0)      // if left shift
                        vkKeys[VK_LSHIFT] = false;
                    nVirtKey = GetKeyState(VK_RSHIFT); 
                    if ((nVirtKey & SHIFTED) == 0)      // if right shift
                        vkKeys[VK_RSHIFT] = false;
                    break;
                case VK_CONTROL:                        // control key
                    nVirtKey = GetKeyState(VK_LCONTROL);
                    if ((nVirtKey & SHIFTED) == 0)      // if left control
                        vkKeys[VK_LCONTROL] = false;
                    nVirtKey = GetKeyState(VK_RCONTROL);
                    if ((nVirtKey & SHIFTED) == 0)      // if right control
                        vkKeys[VK_RCONTROL] = false;
                    break;
            }
            InvalidateRect(hwnd, NULL, TRUE);    // force WM_PAINT
            return 0;
            break;

        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
                    return 0;           // non displayable character
                default:                // displayable character
                    ch = (TCHAR) wParam;    // get the character
                    InvalidateRect(hwnd, NULL, TRUE);   // force WM_PAINT
                    return 0;
            }

        case WM_PAINT:
            hdc = BeginPaint(hwnd, &ps);    // get handle to device context
            TextOut(hdc, 0, 0, &ch, 1);     // display the character
            
            // Display the state of vkKeys array
            // Display 'T' if key is down and 'F' is key is up
            for (int r=0; r<16; r++)
            {
                for (int c=0; c<16; c++)
                {
                    if (vkKeys[r*16+c])
                    {
                        SetBkMode(hdc, OPAQUE);         // opaque text background
                        TextOut(hdc,c*chWidth+chWidth*2,r*chHeight+chHeight*2,"T ", 2);
                    } else {
                        SetBkMode(hdc, TRANSPARENT);    // transparent text background
                        TextOut(hdc,c*chWidth+chWidth*2,r*chHeight+chHeight*2,"F ", 2);
                    }
                }
            }

            EndPaint(hwnd, &ps);
            return 0;

        default:
            return DefWindowProc( hwnd, msg, wParam, lParam );
    }
}

 

먼저 WM_CREATE이다.

 case WM_CREATE:
            // get the text metrics
            hdc = GetDC(hwnd);
            GetTextMetrics(hdc, &tm);
            ReleaseDC(hwnd, hdc);
            chWidth = tm.tmAveCharWidth;        // average character width
            chHeight = tm.tmHeight;             // character height
            return 0;

여기서 GETDC ~ ReleaseDC로 이루어지는데, 이 DC(Device Context)는 윈도우즈에서 그리기 작업을 할 때 필요하다. 

https://m.blog.naver.com/PostView.nhn?blogId=tipsware&logNo=220987761947&proxyReferer=https:%2F%2Fwww.google.com%2F

 

GetDC와 ReleaseDC

1. 먼저 봐야 할 내용들이 글은 아래에 링크한 내용을 먼저 읽어야만 이해할 수 있습니다.Bitmap과 GDI...

blog.naver.com

(참고자료)

 

getTextMetrics는 핸들의 textmetrix값을 tm에 반환해주게 된다.

그리고 chWidth에 평균 캐릭터의 폭을 넣어주고, chHeight에 문자 높이를 넣어주게 된다. 

이렇게 되면 윈도우 창이 생성 되었을때, 필요한 정보들을 얻을 수 있다.

 

WM_KEYDOWN이다.

 case WM_KEYDOWN:                                // key down
            vkKeys[wParam] = true;
            switch(wParam)
            {
                case VK_SHIFT:                          // shift key
                    nVirtKey = GetKeyState(VK_LSHIFT);  // get state of left shift
                    if (nVirtKey & SHIFTED)             // if left shift
                        vkKeys[VK_LSHIFT] = true;
                    nVirtKey = GetKeyState(VK_RSHIFT);  // get state of right shift
                    if (nVirtKey & SHIFTED)             // if right shift
                        vkKeys[VK_RSHIFT] = true;
                    break;
                case VK_CONTROL:                        // control key
                    nVirtKey = GetKeyState(VK_LCONTROL);
                    if (nVirtKey & SHIFTED)             // if left control
                        vkKeys[VK_LCONTROL] = true;
                    nVirtKey = GetKeyState(VK_RCONTROL);
                    if (nVirtKey & SHIFTED)             // if right control
                        vkKeys[VK_RCONTROL] = true;
                    break;
            }
            InvalidateRect(hwnd, NULL, TRUE);           // force WM_PAINT
            return 0;
            break;

키가 눌렸을때, 전달된 메세지의 값인 wParam 인덱스의 vkKey를 true로 초기화 한다. 그리고 스위치문에서 ctrl이나 shift가 눌렸는지를 판단한다. 그리고 쉬프트가 윈쪽 쉬프트인지 오른쪽 쉬프트인지 확인하게 된다. 그 확인을 (GetKeyState(VK_LSHIFT or VK_RSHIFT))로 하게 된다.


WM_KEYUP 케이스이다.

 case WM_KEYUP:                                  // key up
            vkKeys[wParam] = false;
            switch(wParam)
            {
                case VK_SHIFT:                          // shift key
                    nVirtKey = GetKeyState(VK_LSHIFT); 
                    if ((nVirtKey & SHIFTED) == 0)      // if left shift
                        vkKeys[VK_LSHIFT] = false;
                    nVirtKey = GetKeyState(VK_RSHIFT); 
                    if ((nVirtKey & SHIFTED) == 0)      // if right shift
                        vkKeys[VK_RSHIFT] = false;
                    break;
                case VK_CONTROL:                        // control key
                    nVirtKey = GetKeyState(VK_LCONTROL);
                    if ((nVirtKey & SHIFTED) == 0)      // if left control
                        vkKeys[VK_LCONTROL] = false;
                    nVirtKey = GetKeyState(VK_RCONTROL);
                    if ((nVirtKey & SHIFTED) == 0)      // if right control
                        vkKeys[VK_RCONTROL] = false;
                    break;
            }
            InvalidateRect(hwnd, NULL, TRUE);    // force WM_PAINT
            return 0;
            break;

사실 이 WM_KEYUP 함수의 대체적인 동작은 WM_KEYDOWN과 거의 동일하다. 마지막 InvalidateRect()함수를 호출시키는 것만 다른데, 초기화할 영역을 NULL 인자로 호출하고 있다. 이 함수를 호출하면 WM_PAINT가 확인하고 화면을 다시 그려줄 수 있도록 플래그를 표시해 놓는다. 메세지를 윈도우즈가 모두 처리하고 난 후 WM_PAINT가 플래그를 확인하여 작업을 수행해주게 된다.

 

WM_CHAR이다.

 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
                    return 0;           // non displayable character
                default:                // displayable character
                    ch = (TCHAR) wParam;    // get the character
                    InvalidateRect(hwnd, NULL, TRUE);   // force WM_PAINT
                    return 0;
            }

이 케이스는 키보드에서 입력을 했을 때 수행된다. 키보드의 값을 case문으로 처리하는데 backspace나 tab같은 특수한 키들을 제외하고, 일반적인 문자의 경우 ch에 담겨진다.

실제 수행결과에 backspace를 누르면 9번째 칸(0부터 시작하므로)이 True로 변하는 것을 확인할 수 있다.

 

WM_PAINT이다.

        case WM_PAINT:
            hdc = BeginPaint(hwnd, &ps);    // get handle to device context
            TextOut(hdc, 0, 0, &ch, 1);     // display the character
            
            // Display the state of vkKeys array
            // Display 'T' if key is down and 'F' is key is up
            for (int r=0; r<16; r++)
            {
                for (int c=0; c<16; c++)
                {
                    if (vkKeys[r*16+c])
                    {
                        SetBkMode(hdc, OPAQUE);         // opaque text background
                        TextOut(hdc,c*chWidth+chWidth*2,r*chHeight+chHeight*2,"T ", 2);
                    } else {
                        SetBkMode(hdc, TRANSPARENT);    // transparent text background
                        TextOut(hdc,c*chWidth+chWidth*2,r*chHeight+chHeight*2,"F ", 2);
                    }
                }
            }

            EndPaint(hwnd, &ps);
            return 0;

처음 TextOut함수는 가장 왼쪽 상단에 키를 일반적인 문자를 누르면 표시되는 네모 박스를 표현해준다.

이 부분

그 후에 16*16 배열의 구성을 반복문으로 출력하는데, SetBKMode는 문자의 배경을 투명하게 할 것인지 불투명하게 할 것인지 선택하는 것이다.(opaque가 불투명이다.) 그리고 chWidth를 통해서 위치를 계산하고 T거나 F를 출력해주게 된다.

그 후 작업이 끝나면 EndPaint를 호출하게 된다.

 

CreateWindow는 동일하므로 생략.

728x90
반응형
Comments