이전 단계인 "5. Engine 구성하기" 에서는 Engine 클래스가 WindowContainer 클래스를 상속받아 창을 초기화하고 메시지를 처리했습니다.
이번 단계에서는 Keyboard 이벤트를 처리하려고 합니다.
KeyboardEvent.h
#pragma once
class KeyboardEvent
{
public:
/* 키보드 이벤트 타입 */
enum EventType
{
Press,
Release,
Invalid
};
KeyboardEvent();
KeyboardEvent(const EventType type, const unsigned char key);
bool IsPress() const;
bool IsRelease() const;
bool IsValid() const;
unsigned char GetKeyCode() const;
private:
EventType type;
unsigned char key;
};
KeyboardEvent.cpp
#include "KeyboardEvent.h"
/* 키보드 이벤트 타입과 키 */
// KeyboardClass의 keyBuffer에 값이 없는 경우
KeyboardEvent::KeyboardEvent()
:
type(EventType::Invalid),
key(0u)
{
}
// KeyboardClass의 keyBuffer에 값이 있는 경우
KeyboardEvent::KeyboardEvent(const EventType type, const unsigned char key)
:
type(type),
key(key)
{
}
bool KeyboardEvent::IsPress() const
{
return this->type == EventType::Press;
}
bool KeyboardEvent::IsRelease() const
{
return this->type == EventType::Release;
}
bool KeyboardEvent::IsValid() const
{
return this->type != EventType::Invalid;
}
unsigned char KeyboardEvent::GetKeyCode() const
{
return this->key;
}
KeyboardClass.h
#pragma once
#include "KeyboardEvent.h"
#include <queue>
using namespace std;
class KeyboardClass
{
public:
KeyboardClass();
bool KeyIsPressed(const unsigned char keycode);
bool KeyBufferIsEmpty();
bool CharBufferIsEmpty();
KeyboardEvent ReadKey();
unsigned char ReadChar();
void OnKeyPressed(const unsigned char key);
void OnKeyReleased(const unsigned char key);
void OnChar(const unsigned char key);
void EnableAutoRepeatKeys();
void DisableAutoRepeatKeys();
void EnableAutoRepeatChars();
void DisableAutoRepeatChars();
bool IsKeysAutoRepeat();
bool IsCharsAutoRepeat();
private:
bool autoRepeatKeys = false;
bool autoRepeatChars = false;
bool keyStates[256]; // 키가 눌렸을 때의 상태 저장
// 키를 저장하는 queue
queue<KeyboardEvent> keyBuffer;
queue<unsigned char> charBuffer;
};
KeyboardClass.cpp
#include "KeyboardClass.h"
/* 모든 키 상태 초기화 */
KeyboardClass::KeyboardClass()
{
for (int i = 0; i < 256; i++)
this->keyStates[i] = false;
}
/* 키가 눌리면, 키 상태를 가져옴 */
bool KeyboardClass::KeyIsPressed(const unsigned char keycode)
{
return this->keyStates[keycode];
}
bool KeyboardClass::KeyBufferIsEmpty()
{
return this->keyBuffer.empty();
}
bool KeyboardClass::CharBufferIsEmpty()
{
return this->charBuffer.empty();
}
KeyboardEvent KeyboardClass::ReadKey()
{
// 키가 없는 경우
if (this->keyBuffer.empty())
{
return KeyboardEvent(); // 빈 KeyboardEvent 리턴 -> Invalid
}
// 키가 있는 경우
else
{
KeyboardEvent e = this->keyBuffer.front(); // queue의 front에서 값을 가져옴
this->keyBuffer.pop(); // front 값을 삭제
return e;
}
}
unsigned char KeyboardClass::ReadChar()
{
// 키가 없는 경우
if (this->charBuffer.empty())
{
return 0u; // NULL char
}
// 키가 있는 경우
else
{
unsigned char e = this->charBuffer.front(); // queue의 front에서 값을 가져옴
this->charBuffer.pop(); // front 값을 삭제
return e;
}
}
/* 키가 눌린 경우 */
void KeyboardClass::OnKeyPressed(const unsigned char key)
{
this->keyStates[key] = true;
this->keyBuffer.push(KeyboardEvent(KeyboardEvent::EventType::Press, key));
}
/* 키가 떼진 경우 */
void KeyboardClass::OnKeyReleased(const unsigned char key)
{
this->keyStates[key] = false;
this->keyBuffer.push(KeyboardEvent(KeyboardEvent::EventType::Release, key));
}
void KeyboardClass::OnChar(const unsigned char key)
{
this->charBuffer.push(key);
}
void KeyboardClass::EnableAutoRepeatKeys()
{
this->autoRepeatKeys = true;
}
void KeyboardClass::DisableAutoRepeatKeys()
{
this->autoRepeatKeys = false;
}
void KeyboardClass::EnableAutoRepeatChars()
{
this->autoRepeatChars = true;
}
void KeyboardClass::DisableAutoRepeatChars()
{
this->autoRepeatChars = false;
}
bool KeyboardClass::IsKeysAutoRepeat()
{
return this->autoRepeatKeys;
}
bool KeyboardClass::IsCharsAutoRepeat()
{
return this->autoRepeatChars;
}
WindowContainer.h
#pragma once
#include "RenderWindow.h"
#include "Keyboard/KeyboardClass.h"
class WindowContainer
{
public:
LRESULT WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
protected:
RenderWindow render_window;
KeyboardClass keyboard; // 추가
private:
};
- KeyboardClass 타입의 keyboard 변수 선언
WindowContainer.cpp
#include "WindowContainer.h"
LRESULT WindowContainer::WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
/* 키가 눌렸을 때 */
case WM_KEYDOWN:
{
unsigned char keycode = static_cast<unsigned char>(wParam);
/* 자동 반복이 true면 키 이벤트가 들어올 때마다 키 저장 */
if (keyboard.IsKeysAutoRepeat())
{
keyboard.OnKeyPressed(keycode);
}
/* 자동 반복이 false면 키 이벤트가 지속적으로 들어와도 키를 한 번 저장 */
else
{
const bool wasPressed = lParam & 0x40000000;
if (!wasPressed)
{
keyboard.OnKeyPressed(keycode);
}
}
return 0;
}
/* 키가 떼졌을 때 */
case WM_KEYUP:
{
unsigned char keycode = static_cast<unsigned char>(wParam);
keyboard.OnKeyReleased(keycode);
return 0;
}
/* 문자 키를 입력했을 때 발생할 수 있는 메시지 */
case WM_CHAR:
{
unsigned char ch = static_cast<unsigned char>(wParam);
/* 자동 반복이 true면 키 이벤트가 들어올 때마다 키 저장 */
if (keyboard.IsCharsAutoRepeat())
{
keyboard.OnChar(ch);
}
/* 자동 반복이 false면 키 이벤트가 지속적으로 들어와도 키를 한 번 저장 */
else
{
// 31번째 비트가 이전 문자를 저장함
// -> 10...00 (0이 30개)를 16진수로 변경하면 0x40000000이 됨
const bool wasPressed = lParam & 0x40000000;
if (!wasPressed)
{
keyboard.OnChar(ch);
}
}
return 0;
}
default:
/* 처리되지 않은 모든 메시지의 디폴트 처리를 수행 */
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}
- WM_KEYDOWN : 키가 눌렸을 때
- 자동 반복인 경우, 이벤트가 들어올 때마다 키를 저장합니다.
- 자동 반복이 아닌 경우, 이벤트가 지속적으로 들어와도 키를 한 번만 저장합니다.
- WM_KEYUP : 키가 떼졌을 때, 현재 키를 저장합니다.
- WM_CHAR : 문자 키를 입력받았을 때, 키가 눌렸을 때와 동일합니다.
Engine.h
#pragma once
#include "WindowContainer.h"
/* WindowContainer 클래스 상속 */
class Engine : WindowContainer
{
public:
bool Initialize(HINSTANCE hInstance, string window_title, string window_class, int width, int height); // 창 초기화
bool ProcessMessages(); // 메시지 처리
void Update(); // 추가
};
Update 함수 추가
Engine.cpp
#include "Engine.h"
/* 동일 */
void Engine::Update()
{
while (!keyboard.CharBufferIsEmpty())
{
unsigned char ch = keyboard.ReadChar();
}
while (!keyboard.KeyBufferIsEmpty())
{
KeyboardEvent kbe = keyboard.ReadKey();
unsigned char keycode = kbe.GetKeyCode();
}
/* Debug 창을 통해 키 이벤트 및 키 출력 */
/*
while (!keyboard.CharBufferIsEmpty()) {
unsigned char ch = keyboard.ReadChar();
string outmsg = "Char : ";
outmsg += ch;
outmsg += "\n";
OutputDebugStringA(outmsg.c_str());
}
while (!keyboard.KeyBufferIsEmpty()) {
KeyboardEvent kbe = keyboard.ReadKey();
unsigned char keycode = kbe.GetKeyCode();
string outmsg = "";
if (kbe.IsPress()) {
outmsg += "Key Press : ";
}
else if (kbe.IsRelease()) {
outmsg += "Key Release : ";
}
outmsg += keycode;
outmsg += "\n";
OutputDebugStringA(outmsg.c_str());
}
*/
}
- 키 이벤트에 의해 들어온 키를 업데이트 해줍니다.
- 주석으로 처리된 부분은 키 이벤트가 발생했을 때, Debug 출력창을 통해 확인할 수 있습니다.
#include "Engine.h"
// 추가한 라이브러리를 가져옴
#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "DirectXTK.lib")
int APIENTRY wWinMain(_In_ HINSTANCE hInstance, // 인스턴스에 대한 핸들
_In_opt_ HINSTANCE hPrevInstance, // 사용 X
_In_ LPWSTR lpCmdLine, // 커맨드 라인을 유니코드 문자열로 포함
_In_ int cCmdShow) // 기본 응용 프로그램 창이 최소화, 최대화 또는 정상적으로 표시되는지 여부를 나타내는 플래그
{
/* 에러 창 띄우기
HRESULT hr = E_INVALIDARG; // 올바르지 않은 매개 변수
ErrorLogger::Log(hr, "FAILURE");
*/
Engine engine;
engine.Initialize(hInstance, "Title", "MyWindowClass", 800, 600);
while (engine.ProcessMessages() == true)
{
engine.Update(); // 추가
}
return 0;
}