🕹️자체엔진/DirectX 12 번역강좌

[C/C++] Directx12 강좌 - 02. 윈도우 생성

Mawile 2021. 11. 5.
728x90

번역자의 말

이번에 Directx12를 공부하기 위해서 번역강좌 올립니다.

Directx12 설치부분은 추후에 업로드하겠습니다.

참고한 원본사이트는 DirectX 12 Braynzar Soft Tutorials - 02. Creating a Window 입니다.

영어번역이 어색한 부분은 댓글로 지적 부탁드립니다.

 

Pinterest

 

 

새 선언

우리는 구조체에 저장되는 HWND라는 윈도우 핸들이 필요합니다. 우리는 글로벌 윈도우 핸들을 만들어서 hwnd라고 부릅시다. 창을 만들려면 WNDCLASSEX 구조체를 작성해야 합니다. 다음 줄은 클래스의 이름인 문자열입니다. 이건 여러분이 원하는 대로 불러도 됩니다. 그 다음에는 전체 화면 모드가 아닐 때 창의 제목 표시줄에 있는 텍스트가 창의 제목입니다.

더보기

New Declarations

We need a window handle, which is stored in a structure called **HWND**. We create a global window handle and call it hwnd. To create a window, we need to fill out a WNDCLASSEX structure. The next line is a string which is the name of the class. You can call it whatever you want. After that is the title of our window, the text in the title bar of the window when it's not in full screen mode.

// 윈도우 핸들
HWND hwnd = NULL;

// 윈도우의 이름 (제목이 아닙니다.)
// 번역자: 윈도우의 클래스이름입니다.
LPCTSTR WindowName = L"MawileApp";

// 윈도우의 제목
LPCTSTR WindowTitle = L"Mawile's App";

 

다음으로 우리는 윈도우창의 너비와 높이를 알아야 합니다. 전체 화면을 "참"으로 설정하면 너비와 높이가 모니터의 너비와 높이로 변경됩니다.

더보기

Next we have the width and height of our window. If we set FullScreen to "true", the width and height will change to the width and height of the monitor.

 

// 윈도우창의 너비와 높이
int Width = 800;
int Height = 600;

// 윈도우창을 풀스크린으로 할지 정할것인가?
bool FullScreen = false;

 

이제 몇 가지 함수가 있습니다. 첫 번째 함수는 창을 만들기 위해 호출할 함수입니다. 그 후에 우리의 메인 루프를 가지게 됩니다. 메인 루프는 우리 프로그램의 핵심입니다. 여기서 윈도우 메시지를 확인하고, 게임 로직(논리)을(를) 업데이트하며, 프레임을 렌더링합니다.

더보기

Now we have a couple functions. The first function is the function we will call to create our window. After that we have our main loop. The main loop is the heart of our program. It is where we will check for windows messages, update our game logic, and render our frame.

 

// 윈도우를 생성한다
bool InitializeWindow(HINSTANCE hInstance,
    int ShowWnd,
    int width, int height,
    bool fullscreen);

// 메인 애플리케이션 루프
void mainloop();

 

stdafx.h 파일에 있는 마지막 선언은 윈도우 메시지에 대한 WndProc이라는 콜백 함수입니다.

더보기

The last declaration we have in our stdafx.h file is the callback function for windows messages, called WndProc.

 

// 윈도우 메세지 콜백 함수
LRESULT CALLBACK WndProc(HWND hWnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam);

 

WinMain 함수가 Initialize함수, InitializeWindow함수, mainloop함수를 호출하도록 업데이트되었습니다.

더보기

Our WinMain function has been updated to call the InitializeWindow function and mainloop function.

 

int WINAPI WinMain(HINSTANCE hInstance,    // 메인 윈도우 함수
    HINSTANCE hPrevInstance,
    LPSTR lpCmdLine,
    int nShowCmd)
{
    // 윈도우를 생성한다
    if (!InitializeWindow(hInstance, nShowCmd, Width, Height, FullScreen))
    {
        MessageBoxW(0, L"윈도우창의 초기화가 실패했습니다.",
            L"에러", MB_OK);
        return 0;
    }

    // 메인 애플리케이션 루프 시작
    mainloop();

    return 0;
}

 

이제 InitializeWindow함수를 작성해야합니다. 이 함수에서 가장 먼저 할 일은 전체 화면(풀 스크린)을 원하는지 확인하는 것입니다. 만약 그렇다면, 우리는 모니터의 폭과 길이를 구하고 전체 폭과 길이를 거기에 맞춰야 합니다.

더보기

Now we have our InitializeWindow function. In this function, the first thing we will do is check if we want fullscreen. If we do, we get the monitors width and length and set the global width and length to that.

 

bool InitializeWindow(HINSTANCE hInstance,
    int ShowWnd,
    int width, int height,
    bool fullscreen)
{
if (fullscreen)
{
	HMONITOR hmon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
	MONITORINFO mi = { sizeof(mi) };
    GetMonitorInfo(hmon, &mi);
    
    width = mi.rcMonitor.right - mi.rcMonitor.left;
    height = mi.rcMonitor.bottom - mi.rcMonitor.top;
}

 

다음으로 우리는 WNDCLASSEX 구조체를 작성한다. 이 구조체는 우리의 윈도우에 대한 정보가 작성될 것입니다.

더보기

Next we fill out a WNDCLASSEX structure. This structure will describe our window.

 

WNDCLASSEX wc;

wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = NULL;
wc.cbWndExtra = NULL;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 2);
wc.lpszMenuName = NULL;
wc.lpszClassName = WindowName;
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);

 

이 구조체를 작성한 후, 우리는 RegisterClassEx 라는 함수를 호출하는 클래스를 등록해야 합니다.

더보기

After we fill out this structure, we must register the class calling the RegisterClassEx function

 

if (!RegisterClassEx(&wc))
{
	MessageBox(NULL, L"클래스 가입 에러", L"에러", MB_OK | MB_ICONERROR);
	return false;
}

 

이제 CreateWindowEX 라는 함수를 호출하여 방금 등록한 클래스로 윈도우창을 만듭니다.

더보기

Now we create our window with the class we just registered by calling the CreateWindowEX function.

 

hwnd = CreateWindowEx(NULL,
            WindowName,
            WindowTitle,
            WS_OVERLAPPEDWINDOW,
            CW_USEDEFAULT, CW_USEDEFAULT,
            width, height,
            NULL,
            NULL,
            hInstance,
            NULL);

 

윈도우창의 핸들을 가져오지 못한 경우 false를 반환하고 오류 메시지를 표시합니다.

더보기

If we were unsuccessful in getting a window handle, we will return false and show an error message.

 

if (!hwnd)
{
	MessageBox(NULL, L"윈도우 생성 에러", L"에러", MB_OK | MB_ICONERROR);
	return false;
}

 

만약 전체 화면(풀 스크린)을 원한다면 창 스타일을 제거해야 합니다. 단일 모니터에서만 작업하는 경우 윈도우 모드에서 디버깅하는 것이 좋습니다. 윈도우가 전체 화면일 때 단일 모니터를 사용하여 전체 화면 응용 프로그램을 디버깅하는 것은 중단점이나 예외에 도달하는 경우 큰 문제가 될 수 있습니다.

더보기

If we want full screen, we will need to remove the window style. If you are working on only a single monitor, you i suggest you debug in windowed mode. debugging fullscreen applications using a single monitor can be a huge bitch if you hit a breakpoint or an exception while the window is fullscreen.

 

if (fullscreen)
{
	SetWindowLong(hwnd, GWL_STYLE, 0);
}

 

마지막으로 윈도우창을 표시하고 윈도우창을 업데이트한 다음, "참"을 반환합니다.

더보기

Finally we show the window and update the window, then return true.

 

	ShowWindow(hwnd, ShowWnd);
	UpdateWindow(hwnd);
    
	return true;
}

 

다음으로 메인 루프인 "mainloop()"가 있습니다. MSG 객체의 메모리를 0으로 초기화합니다. 이 MSG 객체는 PeekMessage 함수가 받는 모든 윈도우 메시지를 보유합니다. 만약 우리가 메시지를 찾으면, 메시지를 번역(translate)하고 발송(dispatch)합니다. 이건 단지 우리 프로그램이 응답을 멈춘다고 생각하지 않기 위한 것입니다. 메시지를 찾지 못하면 우리의 게임 코드를 실행하고 루프를 계속합니다.

더보기

Next we have our main loop, "mainloop()". We zero out the memory for a MSG object. this MSG object will hold any windows message that the PeekMessage function gets. If we find a message, we translate the message then dispatch it. This is just so windows doesn't think our program stopped responding. If we did not find a message, we run our game code and continue the loop.

 

void mainloop() {
    MSG msg;
    ZeroMemory(&msg, sizeof(MSG));
    
    while (true)
    {
        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            if (msg.message == WM_QUIT)
                break;
    
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        else {
            // 게임 코드를 입력하세요
        }
    }
}

 

마지막으로 윈도우 콜백함수입니다. 윈도우 클래스를 만들 때 이 함수를 제공해야 합니다. 이렇게 하면 키를 누르거나 마우스를 움직이거나 창 크기 조정과 같은 윈도우에서 보내는 윈도우 메시지를 얻을 수 있습니다. 현재로서는 Esc 키와 창 오른쪽 상단에 있는 x 버튼을 눌렀는지 여부만 확인하고 있습니다.

더보기

The last thing is our windows procedure callback. We had to give this function to the windows class when we created it. This is so we can get windows messages that are sent from Windows, such as key presses, mouse movements, or window resizing. For now, all we are checking for are the escape key, and if the x button on the top right of the window has been pressed.

 

LRESULT CALLBACK WndProc(HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam)
{
    switch (msg)
    {

    case WM_KEYDOWN:
        if (wParam == VK_ESCAPE) {
            if (MessageBox(0, L"종료하시겠습니까?",
                L"진짜로 종료하게요?", MB_YESNO | MB_ICONQUESTION) == IDYES)
                DestroyWindow(hwnd);
        }
        return 0;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hwnd,
        msg,
        wParam,
        lParam);
}

 

번역자의 말: 이거는 원문의 프로젝트 다운로드 링크랍니다.

나중에 시간이 된다면 저가 깃허브로 최신번역본으로 새로 링크 올리겠습니다.

BzTut02.rar (1.02 mb)

 

 


 

728x90

댓글