본문으로 바로가기

8장 Input System - ① 입력 장치

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

💡 8장의 목차

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

입력장치

이전까지 우리는 유저의 입력을 이진 값으로 얻었다. 키가 눌러졌으면 true, 눌러지지 않았다면 false라는 사실을 통해서 액터를 조작할 수 있었다. 때문에 입력장치가 '반쯤 눌러졌는지'는 감지할 수 없다. 일부 입력장치는 값의 범위를 제공한다. 예를 들면 컨트롤러의 조이스틱은 특정 방향으로 얼마나 움직였는지를 범위 값으로 제공하고 있다.

폴링

폴링(Polling)을 직역하면 여론조사, 투표라는 뜻이다. 실제 어떤 상태인지 뽑아 보는 의미가 있다. 게임 프로그래밍에서 폴링은 매 프레임마다 특정 키의 값을 확인하여 눌러졌는지 아닌지 확인하는 방법을 뜻한다. 지금까지 프로젝트에서 우리는 Game::ProcessInput() 함수에서 SDL_PollEvent()를 호출하는 방식으로 폴링을 활용해왔다. 폴링 입력 시스템은 이해하기 개념적으로 단순하다. 그래서 많은 게임 개발자들은 폴링 접근법을 사용하고, 여기서도 계속 폴링을 사용한다.

상승 에지와 하강 에지

스페이스바를 누르면 총알이 나가는 게임을 상상해보자. 60fps 게임일 경우 한 프레임이 대략 0.016초 이기 때문에 유저가 잠깐(0.1초) 스페이스바를 누르더라도 총알은 의도하지 않게 여러 발이 나가는 상황이 발생한다. 이는 플레이어가 버튼을 눌렀는지 혹은 뗐는지만 구분하기 때문에 발생하는 문제이다. 이를 해결하기 위해서는 상승에지와 하강 에지 개념을 추가해야 한다.

  • 상승 에지: 이전 프레임 0, 현재 프레임 1
  • 하강 에지: 이전 프레임 1, 현재 프레임 0

상승 에지와 하강 에지를 도입하여 기존의 true, false 상태 체계를 아래와 같이 enum으로 정의하여 사용할 수 있다.

enum ButtonState
{
    ENone,      // 입력 없음
    EPressed,   // 상승 에지
    EReleased,  // 하강 에지
    EHeld       // 계속 누르고 있음
};

만약 입력된 key의 ButtonState를 알고 싶다면 아래와 같이 GetKeyState() 함수를 만들 수 있다.

ButtonState KeyboardState::GetKeyState(SDL_Scancode keyCode) const
{
    if (mPrevState[keyCode] == 0)
    {
        if (mCurrState[keyCode] == 0)
        {
            return ENone;
        }
        else
        {
            return EPressed;
        }
    }
    else // Prev state must be 1
    {
        if (mCurrState[keyCode] == 0)
        {
            return EReleased;
        }
        else
        {
            return EHeld;
        }
    }
}

입력 시스템 설계

지금까지의 입력 체계는 아래와 같이 Game 클래스의 ProcessInput 함수에서 모든 액터와 컴포넌트들에 가상 함수로 선언된 ProcessInput() 함수를 호출한다. 그리고 매개변수로 SDL의 키보드 상태를 넘겨주는 방식으로 구성되어 있다.

void Game::ProcessInput(){
// .. (중략) ..
    const Uint8* keyState = SDL_GetKeyboardState(NULL);
    if (keyState[SDL_SCANCODE_ESCAPE])
    {
        mIsRunning = false;
    }
    for (auto actor : mActors)
    {
        actor->ProcessInput(keyState);
    }
}

이런 방식을 사용할 경우 액터와 컴포넌트에서 SDL함수를 직접 호출하지 않으면 마우스나 컨트롤러에 접근할 수 없다. 때문에 아래와 같은 두 가지 문제가 발생할 수 있다.

  • 액터나 컴포넌트 코드만 작성하는 프로그래머가 SDL 함수와 관련된 특정 지식을 숙지해야 하는 불편함
  • 일부 SDL 입력 함수는 함수 호출 간 상태 차이를 반환. 때문에 한 프레임에 두 번 이상 호출해서는 안되기 때문에 여러 엑터에서 동시에 호출 불가능.

이를 해결하기 위해서 별도의 InputSystem 클래스를 선언한다. InputSystem 클래스의 전체 구현은 교재와 소스코드를 참고하자.


출처:

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