일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- Render Target
- 게임 프로그래밍
- Frustum Culling
- Direct3D12
- 입방체 매핑
- 노멀 맵핑
- C++
- DirectX12
- gitscm
- DirectX
- 조명 처리
- Deferred Rendering
- 직교 투영
- gitlab
- 장치 초기화
- 동적 색인화
- FrameResource
- 디퍼드 렌더링
- 네트워크
- 네트워크 게임 프로그래밍
- Dynamic Indexing
- 절두체 컬링
- light
- direct3d
- 게임 클래스
- 게임 디자인 패턴
- 큐브 매핑
- effective C++
- InputManager
- TCP/IP
- Today
- Total
코승호딩의 메모장
[InputManager와 GameTimer] 본문
이번 글에서는 키 입력을 쉽게 받기 위한 InputManager 클래스와 시간 관리를 위한 GameTimer를 생성하도록 한다. 이 두 클래스는 엔진에서 한 개만 존재해야 하기 때문에 싱글턴 패턴을 이용하여 간편하게 관리하도록 한다.
#define DECLARE_SINGLE(type) \
private: \
type() {} \
~type() {} \
public:
static type* GetInstance() \
{ \
static type instance; \
return &instance; \
} \
#define GET_SINGLE(type) type::GetInstance()
ex) GET_SINGLE(Timer)->GetDeltaTime();
우선 싱글턴 패턴을 위해 #define을 이용하여 클래스에서 쉽게 사용하도록 한다. 그리고 다른 파일에서 싱글턴 객체들을 불러와서 사용하기 위해 GET_SINGLE을 추가하여 내부적으로 가지고 있는 자신의 Instance를 반환하도록 한다.
InputManager
enum class KEY_STATE
{
NONE,
TAP,
PRESSED,
AWAY
};
struct KeyInfo {
KEY_STATE mState{};
bool mIsPrevPushed{};
};
다음은 키가 가지고 있어야 할 정보들이다. TAP은 이전에 눌린 적이 없는 상태일 때 눌린 것이고 PRESSED는 이전에 눌린 것이 아직도 눌려지고 있는 상태이다. AWAY는 이전에 눌려 있었지만 현재는 안 눌려 있는 상태이다.
int keyList[] =
{
VK_ESCAPE, VK_F1, VK_F2, VK_F3, ...
};
keyList는 int 배열로 관리되며 각자 다른 값을 가지고 있기 때문에 이 키 리스트를 통해 mKeys의 키 값으로 사용한다.
class InputManager {
DECLARE_SINGLE(InputManager)
public:
void Init(const WindowInfo& info);
void Update();
public:
KEY_STATE GetKeyState(int key) { return mKeys[key].mState; }
private:
WindowInfo _info;
std::unordered_map<int, KeyInfo> mKeys;
};
InputManager 클래스는 내부적으로 키에 대한 정보를 unoredered_map을 통해서 가지고 있다. mKeys의 키 값은 키 리스트 즉 어떠한 키보드의 키인지를 가지고 있고 밸류 값으로는 KeyInfo 즉, 현재 키보드의 상태가 어떤 상태인지를 가지고 있다.
void InputManager::Init(const WindowInfo& info)
{
_info = info;
for (int key : keyList) {
mKeys[key] = KeyInfo{ KEY_STATE::NONE, false };
}
}
Init에서 모든 키를 NONE과 false로 초기화 해준다.
Update에서는 for (auto& keyInfo : mKeys) 을 돌며 mKeys에 저장된 키 값에 대한 내용들이 변화가 있는지를 계속 확인한다. 그리고 각 State에 따라 변화가 있다면 변경해 주는 것이다.
#define KEY_NONE(key) GET_SINGLE(InputManager)->GetKeyState(key) == KEY_STATE::NONE
#define KEY_TAP(key) GET_SINGLE(InputManager)->GetKeyState(key) == KEY_STATE::TAP
#define KEY_PRESSED(key) GET_SINGLE(InputManager)->GetKeyState(key) == KEY_STATE::PRESSED
#define KEY_AWAY(key) GET_SINGLE(InputManager)->GetKeyState(key) == KEY_STATE::AWAY
ex) if (KEY_PRESSED('W')) { move(y + 1) };
마지막으로 편리하게 키 값을 얻어올 수 있는 매크로 명령을 정의한다.
GameTimer
Timer에 대한 세부 변수들은 아래 포스팅에서 확인할 수 있다.
[시간 측정]
컴퓨터의 성능은 하드웨어마다 다르기 때문에 게임의 물체들을 경과 시간에 기초하여 적절히 갱신하기 위해서는 애니메이션의 프레임들을 렌더링할 때는 프레임들 사이에 시간이 얼마나 흘렀
suengho2257.tistory.com
class Timer
{
DECLARE_SINGLE(Timer)
public:
void Init(const WindowInfo info);
void Update();
void Reset();
void Start();
void Stop();
public:
float TotalTime() const;
float DeltaTime() const;
float GetFps() const;
private:
void CalculateFrameStats();
private:
//... value
};
위와 같이 Timer 또한 싱글턴으로 생성하고 나머지는 위 포스팅에서 확인한다. 주목할 점은 CalculateFrameStats 함수를 사용하여 프레임을 계산하고 프레임을 응용 프로그램 시작 시 상단 캡션 바에 표시해 주는 것이다.
void Timer::CalculateFrameStats()
{
static int frameCnt = 0;
static float timeElapsed = 0.0f;
frameCnt++;
if ((TotalTime() - timeElapsed) >= 1.0f)
{
mFps = (float)frameCnt;
mSpf = 1000.0f / mFps;
wstring fpsStr = to_wstring(mFps);
wstring mspfStr = to_wstring(mSpf);
wstring wndCaption = L"DirectX Game";
wstring windowText = wndCaption + L" fps: " + fpsStr + L" mspf: " + mspfStr;
SetWindowText(mInfo.hwnd, windowText.c_str());
// Reset for next average.
frameCnt = 0;
timeElapsed += 1.0f;
}
}
이 함수를 업데이트할 때마다 호출하게 되면 계속 캡션에 fps와 mspf의 정보를 확인할 수 있을 것이다. 다만 주의할 점은 Timer을 생성하여 시작할 때, Init 함수에 Reset을 넣어줘야 한다. 그렇지 않으면 BaseTime 값이 0이 되어버리기 때문에 위 함수의 TotalTime() 부분이 엄청 높은 숫자가 되어 계속 1, 1000의 값만 표시가 될 것이다.
void Engine::Init(const WindowInfo& info)
{
GET_SINGLE(InputManager)->Init(info);
GET_SINGLE(Timer)->Init(info);
}
void Engine::Update()
{
GET_SINGLE(InputManager)->Update();
GET_SINGLE(Timer)->Update();
}
이제 엔진 코드의 초기화와 업데이트 부분에서 싱글턴 인스턴스를 얻어와서 각각 객체들을 초기화해주고 업데이트해준다.
void Game::Update()
{
gEngine->Update();
gEngine->RenderBegin();
{
static ObjectConstants o;
if (KEY_PRESSED('W'))
o.offset.y += 1.f * GET_SINGLE(Timer)->DeltaTime();
if (KEY_PRESSED('D'))
o.offset.x += 1.f * GET_SINGLE(Timer)->DeltaTime();
if (KEY_PRESSED('S'))
o.offset.y -= 1.f * GET_SINGLE(Timer)->DeltaTime();
if (KEY_PRESSED('A'))
o.offset.x -= 1.f * GET_SINGLE(Timer)->DeltaTime();
mesh->SetObjectConstant(o);
mesh->Render();
}
gEngine->RenderEnd();
}
이제 mesh 객체의 ObjectConstatns 정보에 각 키마다의 값을 넘겨주어 더하거나 빼주고 상수 버퍼에 넘겨주면 객체가 이동이 잘 될 것이다. 다음은 객체를 오른쪽으로 몇 초간 이동한 모습을 보여준다. 이제 정확한 시간에 따라 객체를 움직일 수 있다.
이번 코드를 작성하면서 싱글턴은 벌집안에 든 꿀과 같다는 생각이 들었다. 이렇게 사용하기 편리하고 구현하기 쉬운 장점이 있지만 싱글턴을 남발하다 보면 화를 면치 못할 것이기 때문이다. 그러나 InputManager와 같이 편리하게 사용할 수 있다면 위에서 작성한 코드처럼 편리하며 개발자가 아닌 사람들도 어느 정도 이해를 할 수 있을 정도이다. 또한 상수 버퍼를 자유롭고 유동적으로 사용할 수 있는 능력도 기를 수 있었다.
'DirectX12 > DirectX12 응용' 카테고리의 다른 글
[Camera] (1) | 2023.10.09 |
---|---|
[SceneManager] (1) | 2023.10.08 |
[Component] (0) | 2023.10.07 |
[Material] (0) | 2023.10.07 |
[Texture Mapping] (0) | 2023.10.07 |
[Mesh] (1) | 2023.10.07 |
[ConstantBuffer와 FrameResource] (0) | 2023.10.07 |