제주 탈출 일지

[1]Hello, World - 2D game Programming 본문

윈도우 프로그래밍(DirectX)

[1]Hello, World - 2D game Programming

귀건 2020. 9. 5. 03:42
728x90
반응형

원래 공부에는 게임 제작이 최고라 하였다.

목표는 (내가 자주 하는) 퍼즐앤 드래곤과 던전앤 파이터의 아라드 어드벤처를 합쳐서 하나의 프로토타입 형태의 게임을 만들어보는 것이 목표이다. 두 개를 합치면 재밌을 것 같다는 생각을 많이 했다. 

 

퍼즐앤 드래곤

 

아라드 어드벤처

C++로 짜려고 하니 DirectX라는 어떤 라이브러리를 요구했고 그 제공하는 함수들로 프로그래밍을 진행하는 것 같았다. 버전이 2010년이 마지막으로 더이상 업데이트는 되지 않는것 같다.

 

http://blog.naver.com/PostView.nhn?blogId=j099450&logNo=221450305286

 

비주얼 스튜디오 2017에 DirectX SDK설치하기

몇시간 삽질하다가 겨우 알아내서 기억하기 편하게,또 고생하지 마시라고 올립니다. ​​https://www.micro...

blog.naver.com

설치방법에 대해서 아주 잘 나와있었다.

 

http://www.programming2dgames.com/chapter2.html

 

Programming 2D Games

Keys Down Displays a grid that represents the current key press state of every key on the keyboard. Uses the WM_KEYDOWN and WM_KEYUP messages to determine the state of the keys on a keyboard. Download......(Source included)

www.programming2dgames.com

이 사이트에서 간단하게 2D 게임을 어떻게 프로그래밍하는지에 대한 내용을 소스코드로 제공하고 있었다.

오늘은 그 중 "Hello World" 소스코드를 분석하고자 한다.

 

// Programming 2D Games
// Copyright (c) 2011 by:
// Charles Kelly
// Chapter 2 "Hello World" Windows Style 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 variable
HINSTANCE hinst;

// Constants
const char CLASS_NAME[]  = "WinMain";
const char APP_TITLE[]   = "Hello World";   // title bar text
const int  WINDOW_WIDTH  = 400;             // width of window
const int  WINDOW_HEIGHT = 400;             // height of window

//=============================================================================
// Starting point for a Windows application
// Parameters are:
//   hInstance - handle to the current instance of the application
//   hPrevInstance - always NULL, obsolete parameter, maintained for backwards compatibilty
//   lpCmdLine - pointer to null-terminated string of command line arguments
//   nCmdShow - specifies how the window is to be shown
//=============================================================================
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)
    {
        // PeekMessage is a non-blocking method for checking for Windows messages.
        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;
    }
    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;
}

 

소스코드를 보면 주석이 굉장히 자세하게 나와있는데, 처음이니까 세세하게 한번 분석해보려고 한다.

 

#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 variable
HINSTANCE hinst;

// Constants
const char CLASS_NAME[]  = "WinMain";
const char APP_TITLE[]   = "Hello World";   // title bar text
const int  WINDOW_WIDTH  = 400;             // width of window
const int  WINDOW_HEIGHT = 400;             // height of window

먼저 선언부이다.

WIN32_LEAN_AND_MEAN을 정의했다. #define WIN32_LEAN_AND_MEAN를 #include "windows.h" 문장 이전에 선언해 준다면 수많은 헤더를 인클루드 하면서 중복 인클루드 되는 헤더를 방지하고 데이터의 중복정의를 제거하며 SDK 프로그래밍에 필요없는 정의들을 건너뛴다. 그래서 프로그램을 날씬하게 만들어 준다.

출처: https://javawoo.tistory.com/42 []

windows.h는 윈도우 응용 프로그램을 만들기 위한 다양한 함수 및 매크로가 포함되어 있다라고 한다. -위키-

 

 

 

함수로는  WinMain(), CreateMainWindow(), WinProc() 3가지를 사용한다.

 

- WinMain()은 window프로그램의 시작점으로 main과 비슷한 역할이다.

 

winmain과 int 사이에는 WINAPI라는 매크로가 있는데, 그에 대해 정리한 글이다. 

https://vallista.tistory.com/entry/Windows-API-int-WINAPI-WinMain-HINSTANCE-hInstance-HINSTANCE-hPrevInstance-LPSTR-lpCmdLine-int-nCmdShow-%EC%9D%98-%EC%9D%98%EB%AF%B8

 

Windows API :: int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) 의 의미

윈도우즈 API에서 "int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)" 와 같은 메인 함수를 발견할 수 있다. 이러한 함수가 무슨 역할을 하는지 궁금했는데, 이..

vallista.tistory.com

인수들은 4가지가 존재한다. 다음과 같다.

 - hInstance : 프로그램의 인스턴스 핸들 //실제로 메모리에 올라온 프로그램의 실체를 의미, 프로그램마다 고유하다.

시스템 프로그래밍에서 공부했던 리눅스의 inode?(정확한 명칭이 기억이 안난다.) 와 비슷한 의미를 가진다고 생각이 든다.
 - hPrevInstance : 바로 앞에 실행된 현재 프로그램의 인스턴스 핸들, WIN32에서는 항상 NULL, 호환성을 위해 존재
 - lpCmdLine : 명령행, 도스의 argv인수에 해당
 - nCmdShow : 프로그램이 실행될 형태, 최소화, 보통모양이 전달

출처: https://hoidu.tistory.com/entry/Windows-Api-정복-정리-WinMain-윈도우-클래스 [Du의 재미있는 프로그래밍 세상]

 

winmain만 봤는데도 쉽지 않다.. 다음 createmainwindow이다.

 

- bool CreateMainWindow(HINSTANCE, int);

함수이름에서 알 수 있듯이 메인 Window화면을 생성해주는 함수이다. 그를 위해서는 프로그램의 고유 인스턴스 핸들을 알아야 하고(HINSTANCE), int는 윈도우 및 컨트롤의 표시/숨김 및 최대화/최소화 상태에 대한 flag이다.(nCmdShow 인수)

 

- LRESULT WINAPI WinProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )

winProc은 window procedure라는 뜻이다. 윈도우에서 메세지 전송을 통해서 프로그램과 소통하는데, 이러한 메세지들은 사용자 작업과 윈도우 명령을 알려주는데 사용된다. 다양한 메세지들이 존재하는데, 그 중 응답할 메세지를 정하여 WinProc 내에 코드를 배치한다.

 

 

이제 WinMain의 내용을 한번 보도록 하겠다.

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)
    {
        // PeekMessage is a non-blocking method for checking for Windows messages.
        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문에서는 CreateMainWindow를 호출하여 1이 오게 되면 false를 리턴하고 있다. 윈도우즈 창이 제대로 생성되지 못한 모양. 그 후 while문으로 무한 루프를 호출한다. 메세지를 Peek(몰래 엿보다)하는데 PM_REMOVE가 있는지 확인하고(아마 PeekMessage는 bool타입 리턴을 해줄 것이다.), 있다면 mes.message가 WM_QUIT일 경우 반복을 끝내게 된다. (done을 1로 만듬.)

PeekMessage와 GetMessage가 CPU 유휴 상태에 대해서 중요한 issue가 존재하는 것 같은데, 이 소스코드는 맛보기이므로 다음에,..,.,.,

 

PeekMessage -> TranslateMessage(&msg) -> DispatchMessage(&msg);

GetMessage가 메시지를 읽고(여기서는 PeekMessage) TranslateMessage가 문자 메시지로 변환을 하며 DispatchMessage가 메시지를 윈도우 프로시저로 보내진다.

출처: https://kjin2sm.tistory.com/167 [K_JIN2SM]

이렇게 프로그램( done이 1으로 바뀌게 되면)이 종료되면, msg. Wparam을 반환한다.

wparam은 전달된 메시지에 대한 부가적인 정보를 가진다. 어떤 의미를 가지는가는 메시지별로 다르다. 32비트값이다.

 

다음은 WinProc의 정의부이다.

//=============================================================================
// 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 );
}

 

 

winproc의 모든 인수는 메세지와 관련된 정보들이다.(메세지 구조체의 4가지 정보이다. hwnd는 메세지를 받은 윈도우의 핸들이다.) 이 msg를 받아서 WM_DESTROY라면 윈도우즈에게 이 프로그램을 종료하라고 전달한다.(PostQuitMessage(0))

 

DefWindowProc은 WinProc에서 처리하지 않은 나머지 메세지를 처리한다.

 

 

마지막으로 bool CreateMainWindow(HINSTANCE, int)이다.

 

//=============================================================================
// 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;
}

 

해당 함수는 윈도우 프로그램창을 생성해주기 때문에 wndclassex를 정의하는데, 굉장히 많은 멤버들을 초기화하는 것을 확인할 수 있었다. 윈도우 클래스를 등록하고 클래스의 이름등등으로 CreateWindow함수를 호출하여 윈도우를 생성한다.

 

이렇게 Hello, World 제목을 가진 윈도우 프로그램 창 하나를 띄우는 과정에 대해서 세세하게 살펴보았다.

윈도우 프로그램 만드는 것 자체가 C나 C++에서의 느낌과는 전혀 다른 생소한 느낌이었고, 익숙해지려면 많이 보는 수 밖에는 없을 듯 하다. 좋은 경험이었다.

 

Hello, World!

728x90
반응형
Comments