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*>(&currentCount)); // 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를 나타낸다.