[DirectX 12] #3-1. Input과 Timer
들어가며
게임에서는 키보드나 마우스의 입력과 시간에 따른 계산이 핵심이다. 이번 시간에는 카메라를 이동하거나 객체를 움직이기 위한 Input 처리, 그리고 프레임마다 시간값을 추적하는 Timer를 구현해 볼 것이다.
Input과 Timer는 싱글톤으로 구현하거나 엔진에서 객체로 관리할 수 있는데, 이번 수업에서는 Engine 클래스에서 객체로 관리하는 방식을 사용한다.
Input 클래스
키보드 입력을 감지하여 W
, A
, S
, D
등을 눌러 객체를 이동할 수 있도록 해보자. 특정 키가 눌림, 떨어짐, 유지 상태인지 추적한다.
- Key 정의
- Press: 누르고 있는 상태
- Down: 처음 눌렀을 때
- Up: 눌렀다 뗐을 때
enum class KEY_TYPE
{
UP = VK_UP, DOWN = VK_DOWN, LEFT = VK_LEFT, RIGHT = VK_RIGHT,
W = 'W', A = 'A', S = 'S', D = 'D',
};
enum class KEY_STATE
{
NONE, PRESS, DOWN, UP, END
};
enum
{
KEY_TYPE_COUNT = static_cast<int32>(UINT8_MAX + 1),
KEY_STATE_COUNT = static_cast<int32>(KEY_STATE::END),
};
Input::Init()
_hwnd = hwnd;
_states.resize(KEY_TYPE_COUNT, KEY_STATE::NONE);
Input::Update()
- 게임 창 포커스 상태에서만 키 입력을 받도록 한다. (핸들 확인)
::GetKeyboardState()
로 256개의 키보드 정보를 한 번에 가져와 확인한다.
HWND hwnd = ::GetActiveWindow();
if (_hwnd != hwnd)
{
for (uint32 key = 0; key < KEY_TYPE_COUNT; key++)
_states[key] = KEY_STATE::NONE;
return;
}
BYTE asciiKeys[KEY_TYPE_COUNT] = {};
if (::GetKeyboardState(asciiKeys) == false)
return;
for (uint32 key = 0; key < KEY_TYPE_COUNT; key++)
{
// 키가 눌려 있으면 true
if (asciiKeys[key] & 0x80)
{
KEY_STATE& state = _states[key];
// 이전 프레임에 키를 누른 상태라면 PRESS
if (state == KEY_STATE::PRESS || state == KEY_STATE::DOWN)
state = KEY_STATE::PRESS;
else
state = KEY_STATE::DOWN;
}
else
{
KEY_STATE& state = _states[key];
// 이전 프레임에 키를 누른 상태라면 UP
if (state == KEY_STATE::PRESS || state == KEY_STATE::DOWN)
state = KEY_STATE::UP;
else
state = KEY_STATE::NONE;
}
}
- 키 상태 확인 함수
// 누르고 있을 때
bool GetButton(KEY_TYPE key) { return GetState(key) == KEY_STATE::PRESS; }
// 맨 처음 눌렀을 때
bool GetButtonDown(KEY_TYPE key) { return GetState(key) == KEY_STATE::DOWN; }
// 맨 처음 눌렀다 뗐을 때
bool GetButtonUp(KEY_TYPE key) { return GetState(key) == KEY_STATE::UP; }
Timer 클래스
매 프레임마다 deltaTime(이전 프레임 대비 경과 시간)을 측정하고, 1초마다 FPS(Frame Per Second)를 계산한다.
- 멤버 변수
uint64 _frequency; // 성능 카운터 빈도
uint64 _prevCount; // 이전 프레임의 카운터
float _deltaTime;
uint32 _frameCount;
float _frameTime;
uint32 _fps;
Timer::Init()
::QueryPerformanceFrequency(reinterpret_cast<LARGE_INTEGER*>(&_frequency));
::QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER*>(&_prevCount)); // CPU 클럭
Timer::Update()
- deltaTime
QueryPerformanceCounter()
는 CPU 클럭 기반의 정밀한 시간 측정을 한다._frequency
는 1초 동안 카운트 수이고, 초기화시QueryPerformanceCounter()
로 받아온다.- 두 카운터의 차이를 빈도로 나누면 경과 시간(초)이 된다.
- fps(초당 프레임 수)
_frameCount
는 1초 동안 누적된 프레임 수이다._frameTime
은 deltaTime을 누적해 1초가 될 때까지 시간을 계속 더한다.- 누적된 시간이 1초 이상 되면
fps
값을 갱신하고 초기화한다.
- deltaTime
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;
}
Engine 클래스
- 멤버 변수 및 함수 추가
shared_ptr<Input> _input = make_shared<Input>();
shared_ptr<Timer> _timer = make_shared<Timer>();
shared_ptr<Input> GetInput() { return _input; }
shared_ptr<Timer> GetTimer() { return _timer; }
Engine::Update()
_input->Update();
_timer->Update();
Engine::ShowFps()
- 화면에 FPS를 출력한다.
uint32 fps = _timer->GetFps();
WCHAR text[100] = L"";
wsprintf(text, L"FPS : %d", fps);
SetWindowText(_window.hwnd, text);
EnginePch.h
에 전역 사용을 위한 매크로 추가
#define INPUT GEngine->GetInput()
#define DELTATIME GEngine->GetTimer()->GetDeltaTime()
Game 클래스
키보드 입력으로 오브젝트를 이동시켜보자.
Game::update()
- 키보드 입력에 따라
속도 * 시간
값으로 오브젝트가 움직이도록 한다.
- 키보드 입력에 따라
static Transform t{};
if (INPUT->GetButton(KEY_TYPE::W))
t.offset.y += 1.f * DELTATIME;
if (INPUT->GetButton(KEY_TYPE::S))
t.offset.y -= 1.f * DELTATIME;
if (INPUT->GetButton(KEY_TYPE::A))
t.offset.x -= 1.f * DELTATIME;
if (INPUT->GetButton(KEY_TYPE::D))
t.offset.x += 1.f * DELTATIME;
mesh->SetTransform(t);
mesh->Render();
- 결과
- 키보드 입력에 따라 흰둥이가 잘 움직인다.
마치며
이번 시간에는 Input과 Timer 클래스를 구현해보았다. 드디어 게임 세계 속 객체들을 움직일 수 있게 됐다. 앞으로도 파이팅 ㅎㅎ
Leave a comment