[DirectX 11] 6. Keyboard Input (키보드 입력)

psj98·2022년 11월 10일
0
post-thumbnail
post-custom-banner

개요

이전 단계인 "5. Engine 구성하기" 에서는 Engine 클래스가 WindowContainer 클래스를 상속받아 창을 초기화하고 메시지를 처리했습니다.

이번 단계에서는 Keyboard 이벤트를 처리하려고 합니다.


KeyboardEvent

  • 키보드 이벤트 타입을 Enum으로 선언합니다.
  • 키보드 이벤트에 따라 type과 key를 지정합니다.

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

  • 모든 키의 상태를 초기화합니다.
  • 키 상태에 따라 keyBuffer에 키를 저장합니다.
  • 키 자동 반복 상태를 저장합니다.

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

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

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 출력창을 통해 확인할 수 있습니다.

main.cpp

#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;
}
profile
SSAFY 9기
post-custom-banner

0개의 댓글