여기까지의 작업내용: https://github.com/hwi-middle/HimchanSoftwareRenderer/tree/5f60ce091d4a69b2071bad751aeb2ca73636d833
키보드 입력을 위한 함수
일단 전공 수업이었던 <윈도우 프로그래밍> 과목에서 배운 바에 따르면 키보드 입력은 WndProc에서 WM_KEYDOWN, WM_KEYUP 같은 메시지를 통해서 처리할 수 있다고 했다.
그러나 내가 만들고 있는 프로그램은 WndProc에서 모든 메시지를 처리하는 구조를 가지고 있지 않다. 그래서 WndProc이 아닌 곳에서 키보드 입력을 검출할 수는 없는지 알아보았다.
그렇게 아래 2개의 함수를 알게 되었다.
- GetKeyState: 메시지 큐를 통해 해당 키의 상태를 조사
- GetAsyncKeyState: 메시지 큐와 상관없이 즉시 해당 키의 상태를 조사
나는 후자를 골라 구현에 적용했다.
HCEngine 모듈 추가
새로운 모듈(=프로젝트) HCEngine을 추가했다. 키보드 입력은 기존 모듈인 HCCore, HCGraphics, HCMath 어디에도 속하지 않기 때문이었다.
하여, HCEngine 모듈을 추가했다. 앞으로 구현될 리소스 관리 같은 영역도 여기서 담당하게 될 것 같다.
키보드 입력 구현을 위한 준비
enum class EKeyCode
{
NONE = 0,
A = 0x41,
B = 0x42,
C = 0x43,
// ...
Y = 0x59,
Z = 0x5A,
F1 = VK_F1,
// ...
F12 = VK_F12,
ESC = VK_ESCAPE,
ALPHA_0 = 0x30,
ALPHA_1 = 0x31,
// ...
ALPHA_9 = 0x39,
NUM_0 = VK_NUMPAD0,
// ...
NUM_9 = VK_NUMPAD9,
SPACE = VK_SPACE,
BACKSPACE = VK_BACK,
TAB = VK_TAB,
ENTER = VK_RETURN,
LEFT_ARROW = VK_LEFT,
RIGHT_ARROW = VK_RIGHT,
UP_ARROW = VK_UP,
DOWN_ARROW = VK_DOWN,
};
먼저 각 주요(?) 키들을 키 코드로 매핑해준다. 특히 알파벳 키는 16진수고 특수 키들은 VK_로 시작하는 가상 키 코드로 매핑되어있는데, 이 부분을 내가 다시 EKeyCode라는 키 코드로 정의하여 구현할 때 이러한 정보를 몰라도 되도록 했다.
enum class EAxis
{
NONE,
VERTICAL,
HORIZONTAL,
AXIS_1,
AXIS_2,
AXIS_3,
AXIS_4,
};
그리고 Axis는 일단 VERTICAL, HORIZONTAL 정도로 만들어두었다. AXIS_1, 2, 3, 4는 혹시나 쓸 일이 생기면 다시 Rename해서 쓸 생각이다.
struct AxisData
{
EKeyCode Positive;
EKeyCode Negative;
EKeyCode AltPositive;
EKeyCode AltNegative;
AxisData(EKeyCode InPositive, EKeyCode InNegative)
: Positive(InPositive), Negative(InNegative), AltPositive(EKeyCode::NONE), AltNegative(EKeyCode::NONE) {}
AxisData(EKeyCode InPositive, EKeyCode InNegative, EKeyCode InAltPositive, EKeyCode InAltNegative)
: Positive(InPositive), Negative(InNegative), AltPositive(InAltPositive), AltNegative(InAltNegative) {}
};
각 Axis는 AxisData를 가지는데, AxisData는 Positive, Negative, AltPositive, AltNegative로 구성된다. 이건 유니티의 (옛날)입력 시스템에서 따왔다.
Input 클래스
class Input
{
public:
bool GetKeyDown(EKeyCode InKey);
bool GetKey(EKeyCode InKey);
bool GetKeyUp(EKeyCode InKey);
float GetAxis(EAxis InAxis);
void Update();
private:
std::unordered_set<EKeyCode> CurrentlyPressedKey;
std::unordered_set<EKeyCode> PreviouslyPressedKey;
static const std::unordered_map<EAxis, AxisData> AxisMap;
bool GetKeyWasDowned(EKeyCode InKey);
};
Input 클래스의 선언은 이렇다. public으로 선언된 함수를 보면 알겠지만 이 부분도 유니티의 함수 이름을 따왔다. 내부적으로는 unordered_set과 unordered_map을 쓰는데, 전자는 현재/이전 프레임에서 눌린 키를 저장하기 위함이고 후자는 Axis과 AxisData를 매핑하기 위함이다.
constexpr int PRESS = 0x8000;
const std::unordered_map<EAxis, AxisData> Input::AxisMap =
{
{
EAxis::HORIZONTAL,
{
EKeyCode::RIGHT_ARROW,
EKeyCode::LEFT_ARROW,
EKeyCode::D,
EKeyCode::A
}
},
{
EAxis::VERTICAL,
{
EKeyCode::UP_ARROW,
EKeyCode::DOWN_ARROW,
EKeyCode::W,
EKeyCode::S
}
}
};
bool Input::GetKeyDown(EKeyCode InKey)
{
int CurrentState = GetAsyncKeyState(static_cast<int>(InKey));
bool bIsDown = (CurrentState & PRESS) != 0;
bool bWasDown = GetKeyWasDowned(InKey);
if (bIsDown)
{
CurrentlyPressedKey.insert(InKey);
}
return bIsDown && !bWasDown;
}
bool Input::GetKey(EKeyCode InKey)
{
int CurrentState = GetAsyncKeyState(static_cast<int>(InKey));
bool bIsDown = (CurrentState & PRESS) != 0;
if (bIsDown)
{
CurrentlyPressedKey.insert(InKey);
}
return bIsDown;
}
bool Input::GetKeyUp(EKeyCode InKey)
{
int CurrentState = GetAsyncKeyState(static_cast<int>(InKey));
bool bIsUp = (CurrentState & PRESS) == 0;
bool bWasDown = GetKeyWasDowned(InKey);
return bIsUp && bWasDown;
}
float Input::GetAxis(EAxis InAxis)
{
const AxisData& AxisData = AxisMap.at(InAxis);
bool bPositive = GetKey(AxisData.Positive) || GetKey(AxisData.AltPositive);
bool bNegative = GetKey(AxisData.Negative) || GetKey(AxisData.AltNegative);
if (bPositive && bNegative)
{
return 0.f;
}
else if (bPositive)
{
return 1.f;
}
else if (bNegative)
{
return -1.f;
}
return 0.f;
}
void Input::Update()
{
PreviouslyPressedKey = CurrentlyPressedKey;
CurrentlyPressedKey.clear();
}
bool Input::GetKeyWasDowned(EKeyCode InKey)
{
return PreviouslyPressedKey.find(InKey) != PreviouslyPressedKey.end();
}
구현은 이렇다. 일단 AxisMap은 HORIZONTAL에 좌우 방향키와 A, D를 매핑했고 VERTICAL에 상하 방향키와 W, S를 매핑했다. 각 함수에 대한 간략한 설명은 리스트 형식으로 덧붙이겠다.
- GetKeyDown: 키가 눌리기 시작했는지 체크한다. 지금 눌렸고, 이전 프레임에서는 안눌렸다면 키가 '눌리기 시작한 것'이다.
- GetKey: 키가 눌리고 있는지 체크한다.
- GetKeyUp: 키가 떼어지기 시작했는지 체크한다. 지금 눌리지 않았고, 이전프레임에서는 눌렸다면 키가 '떼어지기 시작한 것'이다.
- GetAxis: (Alt)Positive와 (Alt)Negative에 매핑된 키가 눌려있는 지에 따라 Axis 값을 반환한다.
- Update: 현재 눌린 키 목록을 이전 프레임에 눌린 키 목록으로 저장한다. 현재 프레임에서는 키 입력 처리를 마무리하겠다는 의미이므로 PostUpdate 시점에 호출해야한다.
- GetKeyWasDowned: 이전 프레임에서 해당 키가 눌렸는지 조사한다. private 함수이다.
구현 결과
키보드 입력에 따라 정점을 이동시켜서 렌더링해주는 방식으로 테스트해보았다. 잘 작동된다!
이제 키 입력 처리도 마무리되었으니 지난 번에 말했던대로 일단 3차원 공간으로 확장해나갈 차례다. 다만 시기상 2학기 졸업작품 작업을 시작할 때가 되어서 시간이 좀 걸릴 것 같기는 하다. 그래도 남는 시간을 활용해서 올해 내로 3D 렌더러를 완성하는 것이 내 목표다.
해낼 수 있다!
'개발일지 > 소프트 렌더러' 카테고리의 다른 글
소프트웨어 렌더러 만들기 - 17 (3D 렌더링) (0) | 2024.12.23 |
---|---|
소프트웨어 렌더러 만들기 - 16 (삼각형 클리핑 + 버그 수정) (0) | 2024.08.22 |
소프트웨어 렌더러 만들기 - 14 (텍스처링) (1) | 2024.07.25 |
소프트웨어 렌더러 만들기 - 13 (삼각형 채우기, 보간) (1) | 2024.07.22 |
소프트웨어 렌더러 만들기 - 12 (삼각형 그리기 + 버그 수정) (0) | 2024.07.19 |