프레임워크
저번 포스팅에서 GetMessage방식의 어플리케이션은 게임에 적합하지 않다는 것을 알아보았다. 이번포스팅에서는 기본 프레임워크를 제작하는 것에 대해 알아보도록하자.
메인루프 변경
while (true)
{
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))//메시지 체크
{
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
else //게임 로직 실행
{
}
}
Win32 및 C++를 사용하는 창 메시지(시작) - Win32 apps
Win32 및 C++를 사용하는 창 메시지(시작)
learn.microsoft.com
미리컴파일된 헤더 설정하기
미리컴파일된 헤더(Precompiled Header) 는 자주변경되지 않는 긴소스를 미리 컴파일하여 컴파일결과를 별도의 파일에 저장하여, 다시 똑같은 헤더를 컴파일시 해당파일을 처음부터 컴파일하지않고 미리컴파일된 헤더 파일을 사용해 컴파일 속도를 월등히 향상시켜준다. 이번 프로젝트에서는 자주 사용될것 같은 헤더파일을 모아서 미리 컴파일하는 식으로 사용할 것이다.
프로젝트에 pch설정
프로젝트속성->C/C++->미리 컴파일된 헤더 사용을 설정한다.
pch cpp 설정
헤더 사용을 설정했다면 해당 헤더를 만들기로 설정해준다.
게임 프레임 워크
앞서 말햇듯이 기본 애플리케이션 구조는 게임에 적합하지 않다. 그렇기에 우리는 게임을 담당하는 클래스를 하나 생성할 필요가 있으며, 이것을 메시지와 상관없이 매프레임마다 반복한다.
//ApplycationDefault.cpp
Game game;
game.Init(g_hwnd);
MSG msg = {};
// 기본 메시지 루프입니다:
while (msg.message!=WM_QUIT)
{
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
else
{
game.Update();
game.Render();
}
}
game클래스
//Game.h
#pragma once
#include<Windows.h>
class Game
{
public:
Game();
~Game();
public:
void Init(HWND hwnd);//초기화
void Update();//업데이트
void Render();//렌더링
private:
HWND _hwnd = {};
HDC _hdc={};
};
게임 프레임워크는 다음과 같이 Init/Update/Render로 되어있다.
이렇게 Game클래스를 생성하였으면 각 기능을 담당하는 매니저를 생성해주어야한다.
Manager Instance생성
싱글톤 패턴을 이용하여 기능의 매니저의 인스턴스를 생성해줄 준비를 한다.
#define DECLARE_SINGLE(classname) \
private: \
classname() { } \ //해당 싱글톤 이외의 메모리 할당을 막기위해
public: \
static classname* GetInstance() \
{ \
static classname s_instance; \
return &s_instance; \
}
#define GET_SINGLE(classname) classname::GetInstance()
InputManager
InputManager는 키보드및 마우스에 대한 입력값을 운영체제를 통해 받아오는 매니저이다.
여기서 핵심적인 내용은 키보드 입력 값에 경우 해당 키값에 변경이 있으면 그 상태를 저장하고 GetButton을 통해 특정 키값의 상태를 받아 올수 있는 것이다. 마우스 입력값의 경우 커서의 좌표를 알려주는 GetCursorPos와 스크린에 대한 마우스의 위치를 현재 클라이언트를 기준점으로 변경해주는 ScreenToClient을 사용한다는 내용이다.
//InputManager.h
#pragma once
enum class KeyType
{
LeftMouse = VK_LBUTTON,
RightMouse = VK_RBUTTON,
Up = VK_UP,
Down = VK_DOWN,
Left = VK_LEFT,
Right = VK_RIGHT,
SpaceBar = VK_SPACE,
W = 'W',
A = 'A',
S = 'S',
D = 'D'
};
enum class KeyState
{
None,
Press,
Down,
Up,
End
};
enum
{
KEY_TYPE_COUNT = static_cast<int32>(UINT8_MAX) + 1,
KEY_STATE_COUNT = static_cast<int32>(KeyState::End)
};
class InputManager
{
DECLARE_SINGLE(InputManager);
public:
void Init(HWND hwnd);
void Update();
// 누르고 있을 때
bool GetButton(KeyType key) { return GetState(key) == KeyState::Press; }
// 맨 처음 눌렀을 때
bool GetButtonDown(KeyType key) { return GetState(key) == KeyState::Down; }
// 맨 처음 눌렀다가 땔 때
bool GetButtonUp(KeyType key) { return GetState(key) == KeyState::Up; }
POINT GetMousePos() { return _mousePos; }
private:
KeyState GetState(KeyType key) { return _states[static_cast<uint8>(key)]; }
private:
HWND _hwnd = 0;
vector<KeyState> _states;
POINT _mousePos;
};
//InputManager.cpp
#include "pch.h"
#include "InputManager.h"
void InputManager::Init(HWND hwnd)
{
_hwnd = hwnd;
_states.resize(KEY_TYPE_COUNT, KeyState::None);
}
void InputManager::Update()
{
BYTE asciiKeys[KEY_TYPE_COUNT] = {};
if (::GetKeyboardState(asciiKeys) == false)
return;
for (uint32 key = 0; key < KEY_TYPE_COUNT; key++)
{
// 키가 눌려 있으면 true
if (asciiKeys[key] & 0x80)
{
KeyState& state = _states[key];
// 이전 프레임에 키를 누른 상태라면 PRESS
if (state == KeyState::Press || state == KeyState::Down)
state = KeyState::Press;
else
state = KeyState::Down;
}
else
{
KeyState& state = _states[key];
// 이전 프레임에 키를 누른 상태라면 UP
if (state == KeyState::Press || state == KeyState::Down)
state = KeyState::Up;
else
state = KeyState::None;
}
}
// Mouse
::GetCursorPos(&_mousePos); // 커서의 좌표를 알아온다
::ScreenToClient(_hwnd, &_mousePos);
}
TimeManager
TimeManager은 시간을 체크하여 이전 프레임과 현재프레임을 비교하여 경과 시간을 추적하는 등의 역할을 하는 매니저이다.
TimeManager에서 주목할만한 기능은 DeltaTime연산과 Fps연산방법 이 두가지가 있다. 사용엔진에서 많이 사용하는 DeltaTime은 현재 CPU클럭에서 이전 CPU클럭을 빼고 CPU 클럭 주파수로 나눈 값이 되는것이다. 그후 이전 클럭을 현재클럭으로 바꿔준다.(DeltaTime=(CurrentCount-PrevCount)/Frequency)
Fps는 함수가 호출된 횟수를 나타내는 _frameCount에서 경과된 시간을 나눈 값이 Fps가 된다.그후 초기화 한다. (Fps=FrameCount/FrameTime)
//TimeManager.h
#pragma once
class TimeManager
{
DECLARE_SINGLE(TimeManager);
public:
void Init();
void Update();
uint32 GetFps() { return _fps; }
float GetDeltaTime() { return _deltaTime; }
private:
uint64 _frequency = 0;
uint64 _prevCount = 0;
float _deltaTime = 0.f;
private:
uint32 _frameCount = 0;
float _frameTime = 0.f;
uint32 _fps = 0;
};
//TimeManager.cpp
void TimeManager::Init()
{
::QueryPerformanceFrequency(reinterpret_cast<LARGE_INTEGER*>(&_frequency));
::QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER*>(&_prevCount)); // CPU 클럭
}
void TimeManager::Update()
{
uint64 currentCount;
::QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER*>(¤tCount));
_deltaTime = (currentCount - _prevCount) / static_cast<float>(_frequency);
_prevCount = currentCount;
_frameCount++;
_frameTime += _deltaTime;
if (_frameTime >= 1.f)
{
_fps = static_cast<uint32>(_frameCount / _frameTime);
_frameTime = 0.f;
_frameCount = 0;
}
}
게임에 적용
//Game.cpp
void Game::Init(HWND hwnd)
{
_hwnd = hwnd;
_hdc = GetDC(hwnd);
GET_SINGLE(TimeManager)->Init();
GET_SINGLE(InputManager)->Init(hwnd);
}
void Game::Update()
{
GET_SINGLE(TimeManager)->Update();
GET_SINGLE(InputManager)->Update();
}
'게임엔진 > 윈도우API' 카테고리의 다른 글
윈도우API)8. 벡터 (0) | 2022.12.18 |
---|---|
윈도우API)7. 삼각함수 (0) | 2022.12.18 |
윈도우API)5. 오브젝트 설계 (0) | 2022.12.17 |
윈도우API)3. Scene과 SceneManager (0) | 2022.12.16 |
윈도우API)1. 기본템플릿 분석 (0) | 2022.12.10 |
댓글