1. TimeManager ; 시간을 관리하는 클래스
1) Defines.h ; 매크로
#pragma once
#define DECLARE_SINGLE(classname) \
private: \
classname() { } \
public: \
static classname* GetInstance() \
{ \
static classname s_instance; \
return &s_instance; \
}
#define GET_SINGLE(classname) classname::GetInstance()
#define SAFE_DELETE(ptr) \
if (ptr) \
{ \
delete ptr; \
ptr = nullptr; \
}
\ (back slash)를 사용하여 같은 줄에 포함됨을 표시할 수 있다. 마지막줄은 생략한다.
DECLARE_SINGLE은 싱글톤을 정의할 때 사용한다.
GET_SINGLE은 싱글톤의 인스턴스를 불러오는 코드이다.
SAFE_DELETE는 포인터를 제거 후 즉시 nullptr로 지정하는 코드이다.
2) TimeManager.h, TimeManager.cpp
#pragma once
class TimeManager
{
DECLARE_SINGLE(TimeManager);
public:
void Init();
void Update();
uint32 GetFps() { return _fps; }
float GetDeltaTime() { return _deltaTime; }
private:
uint32 _frequency = 0;
uint32 _prevCount = 0;
float _deltaTime = 0.f;
uint32 _frameCount = 0;
float _frameTime = 0.f;
uint32 _fps = 0;
};
#include "pch.h"
#include "TimeManager.h"
void TimeManager::Init()
{
// 경과 시간을 ms로 반환
// 정밀도가 조금 떨어짐
/*::GetTickCount64();
::GetTickCount64();*/
::QueryPerformanceFrequency(reinterpret_cast<LARGE_INTEGER*>(&_frequency));
::QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER*>(&_prevCount)); // CPU 클럭
}
void TimeManager::Update()
{
uint64 currentCount;
::QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER*>(¤tCount)); // CPU 클럭
_deltaTime = (currentCount - _prevCount) / static_cast<float>(_frequency);
_prevCount = currentCount;
_frameCount++;
_frameTime += _deltaTime;
if (_frameTime >= 1.f) // 1초가 증가했다면
{
// 초당 프레임 수 = 프레임 수 / 시간
_fps = static_cast<uint32>(_frameCount / _frameTime);
_frameTime = 0.f;
_frameCount = 0;
}
}
CPU의 지금 클럭과 이전 클럭, 시간을 받아와서 프레임을 계산한다.
2. wstring 출력
1) wstring을 출력할 std::format을 사용하기 위한 설정(C++20)
2) wstring으로 fps를 출력하기
2-1) 사전 컴파일 헤더
#include "Types.h"
#include "Enums.h"
#include "Defines.h"
#include <windows.h>
#include <vector>
#include <list>
#include <map>
#include <unordered_map>
#include <string>
#include <algorithm>
#include <format>
pch.h에 format을 include 한다.
2-2) Game.cpp
#include "pch.h"
#include "Game.h"
#include "TimeManager.h"
#include "inputManager.h"
Game::Game()
{
}
Game::~Game()
{
}
void Game::Init(HWND hwnd)
{
_hwnd = hwnd;
_hdc = ::GetDC(hwnd);
GET_SINGLE(TimeManager)->Init();
}
void Game::Update()
{
GET_SINGLE(TimeManager)->Update();
}
void Game::Render()
{
uint32 fps = GET_SINGLE(TimeManager)->GetFps();
float deltaTime = GET_SINGLE(TimeManager)->GetDeltaTime();
wstring str = std::format(L"FPS({0}), DT({1} ms)", fps, static_cast<int32>(deltaTime * 1000));
::TextOut(_hdc, 650, 10, str.c_str(), static_cast<int32>(str.size()));
}
싱글톤 매크로를 사용하여 초기화 시간에 시간도 같이 초기화하고, 업데이트 시간에 시간도 같이 업데이트한다.
GetFps와 GetdeltaTime을 이용하여 fps와 deltaTime을 출력한다.
3. GameCoding.cpp에서 프레임을 제한하기
uint64 prevTick = 0;
//~~
else
{
uint64 now = ::GetTickCount64();
if (now - prevTick >= 10)
{
// 게임 로직 구동
game.Update();
game.Render();
prevTick = now;
}
}
4. InputManager ; 입력을 관리하는 매니저
1) 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;
};
주요 버튼과 키를 표기하기 쉽게 KeyType의 enum class를 만들어 관리하고, 키보드의 누름, 뗌, 눌려있음, 안누름을 관리하기 쉽게 KeyState enum class로 관리한다.
1-0) KeyType 열거형 클래스
키보드와 마우스에서 사용할 버튼을 간단하게 코딩에 이용할 수 있도록 설정한다.
1-1) KeyState 열거형 클래스
키의 눌림 상태(눌림, 눌렀다 뗌, 누르고 있음, 안누름)의 정보를 가지고 있다.
1-2) KEY_TYPE_COUNT, KEY_STATE_COUNT
KEY_TYPE_COUNT는 키보드로 누르는 키의 개수이다. 256으로 설정되어 있다.
KEY_STATE_COUNT는 KeyState의 전체 개수 4를 나타낸다(암시적으로 enum의 첫 값이 0으로 시작함).
1-3) _states 벡터
_states 벡터는 키의 눌림 상태를 벡터로 저장한다.
1-4) GetState 함수
눌림의 상태를 가지는 GetState 함수는 위에서 정의된 W, A, S, D, 마우스 왼쪽, 마우스 오른쪽, 스페이스 바의 눌림 상태를 벡터에서 가져온다.
2) 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++)
{
// 256개로 된 배열에서 내가 누른 키가 있는가?
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;
}
}
::GetCursorPos(&_mousePos);
::ScreenToClient(_hwnd, &_mousePos);
}
2-1) Init
받아온 윈도우 핸들(hwnd)의 값을 매니저 내에서 사용할 예정이므로, 멤버 변수에 저장한다.
_states 벡터를 256개, 초기값은 눌림상태 없음으로 초기화한다.
2-2) Update
(1) asciiKeys
asciiKeys 배열은 256개의 요소로 이루어져있다. 그리고 공백으로 초기화되어 있다.
이 배열은 지금 키보드의 상태를 저장하고 있다.
(2) GetKeyboardState
지금 키보드의 상태를 asciiKeys 배열에 저장한다.
만약 해당 함수가 실패하면 함수를 즉시 종료한다.
(3) for 문
256 만큼의 루프를 돌며 각 키의 눌림 상태를 확인한다.
0x80는 눌림을 표현하는 비트마스크이다.
만약에 asciiKeys의 어떤 요소가 값을 가지고 있고 0x80이라면 해당 키가 눌려져 있는 것이다.
(4) KeyState& state = _states[key];
_states 배열은 0(none), 1(press), 2(down), 3(up)의 4가지 KeyState의 정보를 담고 있다.
키보드의 모든 key의 눌림 상태를 체크하기 위해 state에 눌림 정보를 저장한다.
(5) if 문 로직
if (state == KeyState::Press || state == KeyState::Down)
// 키를 지금 방금 눌렀다면
state = KeyState::Press;
else
// 키를 누르고 있다면
state = KeyState::Down;
현재 키가 이전 프레임에 눌린 상태이다.
키가 방금 눌렸거나 아니면 누르고 있는 상태일 것이다.
키가 방금 눌렸다면 Press 상태로 설정한다. 그리고 키가 계속 눌려져 있다면 Down 상태로 설정한다.
(5-1) else 문 로직
else
{
KeyState& state = _states[key];
// 이전 프레임에 키를 누르고 있었다면 Up
if (state == KeyState::Press || state == KeyState::Down)
// 눌렀다가 땠다면
state == KeyState::Up;
else
// 아예 안누른 경우
state = KeyState::None;
}
else는 현재 키가 이전 프레임에 눌려져 있지 않은 상태이다.
키를 누르지 않은 상태라면 이전에 눌렀다가 떼진 상태이거나 아예 안눌렀을 것이다.
(6) 마우스 동작
::GetCursorPos(&_mousePos);
현재 마우스 커서의 위치를 _mousePos 변수에 저장한다.
::ScreenToClient(_hwnd, &_mousePos);
_hwnd를 기준으로 _mousePos의 좌표를 화면 좌표에서 클라이언트 영역의 좌표로 변환한다.
5. Game.cpp ; 마우스 위치 출력
#include "pch.h"
#include "Game.h"
#include "TimeManager.h"
#include "InputManager.h"
Game::Game()
{
}
Game::~Game()
{
}
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();
}
void Game::Render()
{
uint32 fps = GET_SINGLE(TimeManager)->GetFps();
float deltaTime = GET_SINGLE(TimeManager)->GetDeltaTime();
{ // 마우스 위치 출력
POINT mousePos = GET_SINGLE(InputManager)->GetMousePos();
wstring str = std::format(L"Mouse({0}, {1})", mousePos.x, mousePos.y);
::TextOut(_hdc, 20, 10, str.c_str(), static_cast<int32>(str.size()));
}
{ // FPS, Delta Time 출력
wstring str = std::format(L"FPS({0}), DT({1} ms)", fps, static_cast<int32>(deltaTime * 1000));
::TextOut(_hdc, 650, 10, str.c_str(), static_cast<int32>(str.size()));
}
}
위에서 작성한 InputManager를 include한다.
Init과 Update 영역에서 InputManger를 설정한다.
5-1) format 함수
FPS와 마우스 위치를 원하는 형식으로 표기하기 위해 C++20에서 새로이 등장한 format 함수를 사용한다.
format 함수는 (형태, 변수1, 변수2 ...)의 문법으로 이루어져 있다.
{0}은 변수1, {1}은 변수2를 나타낸다.
'Windows API & 게임 수학' 카테고리의 다른 글
1-6. 오브젝트(Object) 설계 (1) - 플레이어, 투사체, ObjectManager (0) | 2023.10.17 |
---|---|
1-5. 더블 버퍼링 (Double Buffering) (0) | 2023.10.17 |
1-4. 게임을 위한 프레임워크 제작하기(3) - Scene, SceneManager (1) | 2023.10.16 |
1-2. 게임을 위한 프레임워크 제작하기(1) (0) | 2023.10.16 |
1-1. 기본 템플릿 분석 (1) | 2023.10.16 |