본문으로 바로가기

8장 Input System - ② 키보드와 마우스 입력

게임이 영화나 애니메이션과 같은 엔터테인먼트와 다른 이유는 다양한 입력 장치에 반응하기 때문이다. 8장에서는 게임의 대표적인 입력장치인 키보드, 마우스, 컨트롤러를 활용하여 유저의 입력을 받는 방식을 구현한다. 모든 액터와 컴포넌트는 자신이 필요로 하는 입력과 상호작용할 수 있도록 입력 장치를 시스템에 통합하는 방법을 다룬다.

💡 8장의 목차

  • 입력 장치
  • 키보드와 마우스 입력
  • 컨트롤러 입력

키보드

지금까지 프로젝트들에서 유저의 입력을 받을때 키보드를 사용했다. 그리고 키보드 입력 상태를 확인하기 위해서 SDL에서 제공하는 SDL_GetKeyboardState함수를 사용해왔다. 이 함수는 키보드 상태에 대한 포인터를 반환한다. 그리고 이 포인터는 응용프로그램의 생명주기동안 변경되지 않는다. 따라서 키보드의 현재 상태를 추적하기 위해서는 한 번만 초기화된 단일 포인터만 있으면 된다. 그리고 SDL_PollEvents 함수가 호출될 때 마다 SDL내부에서 이 포인터가 가리키고 있는 배열에 새로운 키보드 상태를 덮어쓰므로 상승, 하강 에지를 계산하기 위해서는 이전 프레임 상태를 저장하기 위한 별도의 배열이 필요하다. 이를 통해서 각 액터와 컴포넌트는 필요한 키보드의 4가지 상태(ENone, EPressed, EReleased, EHeld )를 알 수 있다. 아래와 같이 KeyboardState를 선언할 수 있다.

class KeyboardState
{
public:
    // InputSystem을 friend로 선언해서 멤버 데이터를 쉽게 갱신하게 한다
    friend class InputSystem;

    // 키의 이진값 true/false를 얻는다
    bool GetKeyValue(SDL_Scancode keyCode) const;

    // 현재/이전 프레임을 토대로 상태를 얻는다
    ButtonState GetKeyState(SDL_Scancode keyCode) const;
private:
    // 현재 상태 한 번의 초기화로 현재 상태를 계속 참조할 수 있다.
    const Uint8* mCurrState;
    // 이전 프레임의 상태
    Uint8 mPrevState[SDL_NUM_SCANCODES];
};

KeyboardState의 구현은 교재와 소스코드를 참고하자. keyboardState 클래스는 InputSystem 클래스 파일 내부에 함께 구현되어 있다.


마우스

마우스는 버튼으로만 구성되어 있는 키보드보다 조금 더 복잡하다. 마우스의 입력 종류는 아래와 같이 세 가지 이다.

  • 버튼 입력: 키보드 버튼과 거의 비슷
  • 마우스 이동: 2가지의 입력모드 존재. (절대 및 상대)
  • 스크롤 휠 이동: SDL은 오직 이벤트를 통해서만 휠 이동 범위를 제공.

그리고 SDL은 SDL_ShowCursor() 함수로 시스템 마우스 커서를 켜고 끌수 있는 기능을 제공한다.

SDL_ShowCursor(SDL_FASLE);  // 시스템 커서를 끈다.
//SDL_ShowCursor(SDL_TRUE); // 시스템 커서를 켠다.

마우스 버튼 입력

마우스의 위치와 버튼은 SDL_GetMouseState() 함수로 알 수 있다.

int x = 0, y = 0;   // 마우스의 좌표를 담을 변수
Uint32 buttons = SDL_GetMouseState(&x, &y); // 마우스의 버튼 상태를 반환함.

SDL_GetMouseState()의 리턴값은 비트마스크로 AND 연산을 통해서 특정 비트값이 true 인지 false인지에 따라서 버튼이 눌러졌는지 확인 할 수 있다.

bool leftIsDown = (buttons & SDL_BUTTON(SDL_BUTTON_LEFT)) == 1;

SDL은 5개의 여러 마우스 버튼에 해당하는 버튼 상수를 제공한다.

  • SDL_BUTTON_LEFT: 왼쪽 클릭
  • SDL_BUTTON_RIGHT: 오른쪽 클릭
  • SDL_BUTTON_MIDDLE: 가운데 버튼
  • SDL_BUTTON_BUTTON_X1: 4번 버튼
  • SDL_BUTTON_BUTTON_X2: 5번 버튼

기본적으로 마우스 버튼 구현은 비트마스크를 활용한다는 점을 제외하면 키보드 버튼 구현과 동일하기에 코드내 주석을 참고하자.

상대 마우스 모드

SDL에서는 마우스의 이동 입력을 위해 상대모드와 절대모드 두 가지를 제공한다. SDL의 기본 모드는 커서의 현재 좌표를 알리는 절대모드를 사용한다. 하지만 FPS게임 처럼 마우스의 이동에 따라 회전을 하는 경우 커서의 위치가 아니라 프레임 간 마우스의 상대적인 변화를 알기 원할 수도 있다.

상대 모드를 사용하기 위해서는 다음과 같이 상대 마우스 모드를 활성화 해야한다.

SDL_SetRelativeMouseMode(SDL_TRUE); // 상대 마우스 모드 활성화
//SDL_SetRelativeMouseMode(SDL_FALSE); // 상대 마우스 모드 비활성화

상대 마우스 모드가 활성화되면 SDL_GetMouseState() 대신에 SDL_GetRelativeMouseState() 함수를 사용해야한다. 이를 위해서 InputSystem에 마우스 상대 모드를 활성화 시키는 함수와 현재 모드를 저장할 bool 타입 변수mIsRelative를 추가하자. 그리고 mIsRelative의 상태에 따라서 마우스 좌표를 가져오는 함수를 결정하자.

void InputSystem::SetRelativeMouseMode(bool value)
{
    SDL_bool set = value ? SDL_TRUE : SDL_FALSE;
    SDL_SetRelativeMouseMode(set);

    mState.Mouse.mIsRelative = value;
}
// 입력값 갱신
void InputSystem::Update()
{
    int x = 0, y = 0;
    if (mState.Mouse.mIsRelative)
    {
        mState.Mouse.mCurrButtons =
            SDL_GetRelativeMouseState(&x, &y);
    }
    else
    {
        mState.Mouse.mCurrButtons =
            SDL_GetMouseState(&x, &y);
    }

    mState.Mouse.mMousePos.x = static_cast<float>(x);
    mState.Mouse.mMousePos.y = static_cast<float>(y);
    // .. (이하 생략) ...

스크롤 휠

SDL은 마우스 스크롤 휠의 현재 상태를 조회하는 기능을 제공하지 않는다. 대신 SDL_MOUSEWHEEL 이벤트를 생성한다. 그래서 InputSystem에 스크롤 휠을 지원하려면 Game::ProcessInput() 함수에서 InputSystem으로 이벤틀 전달해야한다.

void Game::ProcessInput()
{
    // .. (생략)

    // 큐에 여전히 이벤트가 남아 있는 동안
    while (SDL_PollEvent(&event))
    {
        switch (event.type)
        {
            // 이벤트가 SDL_QUIT이면 루프를 종료한다.
        case SDL_QUIT:
            mIsRunning = false;
            break;
        case SDL_MOUSEWHEEL:
            mInputSystem->ProcessEvent(event);
            break;
        default:
            break;
        }
    }
    // .. (이하 생략) ..

이벤트를 전달 받은 InputSystem에서는 아래와 같이 스크롤 값을 멤버변수 mScrollWheel에 저장한다. SDL은 x/y 두 방향의 스크롤링 값을 알려주기 때문에 mScrollWheelVector2를 사용한다.

void InputSystem::ProcessEvent(SDL_Event& event)
{
    switch (event.type)
    {
    case SDL_MOUSEWHEEL:
        mState.Mouse.mScrollWheel = Vector2(
            static_cast<float>(event.wheel.x),
            static_cast<float>(event.wheel.y));
        break;
    default:
        break;
    }
}

그리고 마우스 휠 이벤트는 스크롤 휠이 움직이는 프레임에서만 트리거되므로 PrepareForUpdate함수에서 반드시 아래와 같이 초기화해야한다.

mState.Mouse.mScrollWheel = Vector2::Zero;

나머지 전체 구현은 아래의 소스코드와 교재를 참고하자.


 

출처:

에이콘 출판 <Game Programming in C++>