WIN32API FrameWork/한단계씩 직접 구현

05. PeekMessage 함수와 Run() 루프

hyrule 2022. 5. 15. 23:59

https://hyrule.tistory.com/111 

 

*** 공부 방법 ***

1. 코딩을 해야 하는 부분은 첫 부분에 변수나 함수, 메소드에 대한 선언이 코드블럭으로 표시되어 있다. //ex) MakeFunction(); 2. 코드블럭 하단에는 해당 선언에 대한 구현 로직이 작성되어 있다. (ex)

hyrule.tistory.com


지난 1번 글에서, 게임은 왜 GetMessage를 안 쓰고 PeekMessage를 사용하는지 설명했었다.

https://hyrule.tistory.com/109

 

01. 프레임워크에 사용될 WINAPI32 기본 생성 코드 알아보기

[WIN32API] 어렵게 생각할 필요 없이, Windows에서 제공하는 함수의 집합이다. 예를 들어 우리가 게임을 돌리고 있을 때 절전모드에 진입하는걸 막는다던지 하는 것들을 조정하도록 해 주는 것이다. W

hyrule.tistory.com

 

이번 글에서는 일단 만들어두기만 했던 메시지 처리 부분을 수정한다.

 

 


PeekMessage()

GetMessage() 함수가 받던 4개의 인자는 동일하다. 거기에 추가로 한 개의 인자를 더 받는다.

  • 5번 인자: 메시지큐로부터 꺼내온 메시지를 메시지큐에서 지울지 말지 여부
    • 받은 메시지(게임이므로 입력)는 지워준다: PM_REMOVE 를 인자로 전달한다.
  • GetMessage()함수와의 차이점
    • GetMessage() 함수는 메시지큐가 비어있을 경우 메시지가 들어올 때까지 함수 안에서 대기한다.
    • 하지만 PeekMessage() 함수는 메시지큐가 비어있으면 'false'를 반환하며 함수를 빠져나온다.
    • 메시지큐가 비어있으면 false를 반환한다는 점에서 착안
      • if문을 사용: 메시지큐가 비어있을 때 else 문으로 들어가서 게임 로직을 동작시키면 된다.

 

 

 


CGameManager::Logic()

게임로직이 들어갈 함수.

PeekMessage가 false일 때, 이 함수로 들어가서 입력을 받고, 프레임을 렌더링하는 등의 일을 한다.

아래의 함수들은 모두 이 함수 안에 들어간다.

 

  • cf)게임의 흐름을 구성하는 단계
    1. 사용자의 입력
    2. 입력받은 내용에 대해서 데이터 업데이트 + 인공지능의 데이터 업데이트
    3. 업데이트된 데이터를 토대로 충돌을 수행
    4. 출력이 되어야 하는 물체들을 판단
    5. 화면에 출력이 되어야 하는 물체들을 출력

 


CGameManager::Input(float DeltaTime)
  • 입력을 처리해 주는 메소드
  • Input, Update, Render 메소드에 인자로 들어가는 float DeltaTime변수는 나중에 설명

 

CGameManager::Update(float DeltaTime)
  • 입력에 따른 플레이어 및 게임 오브젝트들의 상태를 업데이트해 주는 메소드

 

CGameManager::Collision(float DeltaTime)
  • 게임 오브젝트 간 충돌 처리 메소드

 

CGameManager::Render(float DeltaTime)
  • 위의 정보들을 토대로 최종적으로 프레임을 그려내는 메소드

 


위의 4가지 메소드를 Logic() 함수에 추가했으면,

해당 함수들이 반복문에서 계속 실행되도록 해 주자. 일단 이번 글에서는 함수가 반복 실행되기만 하면 된다.

다음 글에서 출력(Render)을 먼저 해볼 예정이다.

 

 

변경점이 CGameManager 클래스 뿐이므로 CGameManager 클래스의 헤더와 cpp파일의 코드만 업로드.

//Class CGameManager
//GameManager.h

#pragma once

#include "GameInfo.h"
#include "Singleton.h"

class CGameManager
{

private:
	HINSTANCE m_hInst;
	HWND m_hWnd;

	//static 메소드인 WinProc은 같은 static 변수만 처리가능하므로
	static bool m_Loop;


public:
	bool Init(HINSTANCE hInstance);
	int Run();

private:
	void Logic();
	void Input(float DeltaTime);
	void Update(float DeltaTime);
	void Collision(float DeltaTime);
	void Render(float DeltaTime);

	void Register();
	bool Create();

	static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);

	DECLARE_SINGLETON(CGameManager)
};
//Class CGameManager
//GameManager.cpp

#include "GameManager.h"

//아이콘
#include "resource.h"


DEFINITION_SINGLETON(CGameManager)
bool CGameManager::m_Loop = true;

CGameManager::CGameManager()
{
    _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
    //_CrtSetBreakAlloc(141);
}
CGameManager::~CGameManager()
{
}

bool CGameManager::Init(HINSTANCE hInstance)
{
	m_hInst = hInstance;

	// 윈도우클래스 구조체를 만들어주고 등록한다.
	Register();

	// 윈도우 창을 생성하고 보여준다.
	Create();

	return true;
}

int CGameManager::Run()
{
    MSG msg;

    while (m_Loop)
    {
        if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        else 
        {
            Logic();
        }
    }

    return (int)msg.wParam;
}

void CGameManager::Logic()
{
    Input(0.0f);
    Update(0.0f);
    Collision(0.0f);
    Render(0.0f);
}

void CGameManager::Input(float DeltaTime)
{
}

void CGameManager::Update(float DeltaTime)
{
}

void CGameManager::Collision(float DeltaTime)
{
}

void CGameManager::Render(float DeltaTime)
{
}



void CGameManager::Register()
{
    // 레지스터에 등록할 윈도우 클래스 구조체를 만들어준다.
    WNDCLASSEXW wcex;

    wcex.cbSize = sizeof(WNDCLASSEX);

    wcex.style = CS_HREDRAW | CS_VREDRAW;

    // 메세지큐에서 꺼내온 메세지를 인자로 전달하며 호출할 함수의 함수 주소를
    // 등록한다.
    wcex.lpfnWndProc = WndProc;
    wcex.cbClsExtra = 0;
    wcex.cbWndExtra = 0;

    // 윈도우 인스턴스를 등록한다.
    wcex.hInstance = m_hInst;

    // 실행파일에 사용할 아이콘을 등록한다.
    wcex.hIcon = LoadIcon(m_hInst, MAKEINTRESOURCE(IDI_ICON1));

    // 마우스 커서 모양을 결정한다.
    wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);

    // 메뉴를 사용할지 말지를 결정한다.
    wcex.lpszMenuName = nullptr;

    // 등록할 클래스의 이름을 유니코드 문자열로 만들어서 지정한다.
    // TEXT 매크로는 프로젝트 설정이 유니코드로 되어있을 경우 유니코드 문자열로 만들어지고
    // 멀티바이트로 되어있을 경우 멀티바이트 문자열로 만들어지게 된다.
    wcex.lpszClassName = TEXT("GameFramework");

    // 윈도우창 좌상단에 표시할 작은 아이콘을 등록한다.
    wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_ICON1));

    RegisterClassExW(&wcex);
}

bool CGameManager::Create()
{
    m_hWnd = CreateWindowW(TEXT("GameFramework"),
        TEXT("GameFramework"), WS_OVERLAPPEDWINDOW,
        100, 50, 1280, 720, nullptr, nullptr, m_hInst, nullptr);

    if (!m_hWnd)
    {
        return false;
    }

    RECT rc = { 0, 0, 1280, 720 };

    AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, false);

    MoveWindow(m_hWnd, 100, 50, abs(rc.left) + abs(rc.right), abs(rc.top) + abs(rc.bottom), true);

    // 윈도우 창을 보여준다. 1번인자에 들어간 핸들의 윈도우 창을 보여줄지 말지를
    // 결정해준다.
    ShowWindow(m_hWnd, SW_SHOW);

    // 이 함수를 호출하여 클라이언트 영역이 제대로 갱신되었다면 0이 아닌 값을 반환하고
    // 갱신이 실패했을 경우 0을 반환한다.
    UpdateWindow(m_hWnd);

    return true;
}

LRESULT CGameManager::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_DESTROY:
        // 윈도우가 종료될때 들어오는 메세지이다.
        m_Loop = false;
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}