01
03

http://rastertek.com/dx11tut13.html

 

Tutorial 13: Direct Input

Tutorial 13: Direct Input This tutorial will cover using Direct Input in DirectX 11. The code in this tutorial will be based on the previous font tutorial code. Direct Input is the high speed method of interfacing with input devices that the DirectX API pr

rastertek.com

 

Window API 윈도우 메시지를 받아 키보드와 마우스 입력을 구현했었다.

윈도우 입력보다 월등히 빠른 Direct Input은

DirectX API가 제공하는 고속의 입력함수의 모음이다.

게임과 같은 빠른 응답시간을 요구하는 프로그램이라면 Direct Input를 사용해야한다.

 

KInput.h

 

Input 클래스 헤더에는 버젼을 정의한다. 

선언을 안했을때는 버전 8로 하겠다는 메시지를 띄운다.

그리고 헤더파일을 포함시키고 두 라이브러리를 링크시킨다.

#pragma comment(lib, "dinput8.lib") 
#pragma comment(lib, "dxguid.lib")

 

그리고 Direct Input에 대한 인터페이스를 만든다.

 

LPDIRECTINPUT8		  m_pDI;
LPDIRECTINPUTDEVICE8  m_pKeyDevice; // 키보드
LPDIRECTINPUTDEVICE8  m_pMouseDevice;// 마우스

 

싱글톤 클래스로 구현하기 때문에 포인터를 쓰지 않았다.

아래는 상태값을 저장하는 변수들이다.

이전 마우스 상태를 따로 만든것은 드래그를 구현하기 위함이다.

 

DIMOUSESTATE		  m_DIMouseState;
BYTE			m_MouseState[3];
BYTE			m_BeforeMouseState[3];
BYTE			m_KeyState[256];

 

Direct Input의 인터페이스 초기화 함수

더보기
bool KInput::InitDirectInput()
{
    HRESULT hr = S_OK;
    if (FAILED(hr = DirectInput8Create(g_hInstance,
        DIRECTINPUT_VERSION,
        IID_IDirectInput8,
        (void**)&m_pDI, NULL)))
    {
        return false;
    }
    if (FAILED(hr = m_pDI->CreateDevice(GUID_SysKeyboard,
        &m_pKeyDevice, NULL)))
    {
        return false;
    }
    // 장치별 반환 데이터 설정
    m_pKeyDevice->SetDataFormat(&c_dfDIKeyboard);
    // 장치별 독점 및 비독점 설정(협조레벨)
    if (FAILED(hr = m_pKeyDevice->SetCooperativeLevel(
        g_hWnd,
        DISCL_NONEXCLUSIVE |
        DISCL_FOREGROUND |
        DISCL_NOWINKEY)))
    {
        return false;
    }

    while (m_pKeyDevice->Acquire() == DIERR_INPUTLOST);


    if (FAILED(hr = m_pDI->CreateDevice(GUID_SysMouse,
        &m_pMouseDevice, NULL)))
    {
        return false;
    }
    m_pMouseDevice->SetDataFormat(&c_dfDIMouse);

    if (FAILED(hr = m_pMouseDevice->SetCooperativeLevel(
        g_hWnd,
        DISCL_NONEXCLUSIVE |
        DISCL_FOREGROUND)))
    {
        return true;
    }
    while (m_pMouseDevice->Acquire() == DIERR_INPUTLOST);
    return true;
}

 

DXINPUT 인터페이스 객체 초기화

  HRESULT hr = S_OK;
    if (FAILED(hr = DirectInput8Create(g_hInstance,
        DIRECTINPUT_VERSION,
        IID_IDirectInput8,
        (void**)&m_pDI, NULL)))
    {
        return false;
    }

 

위 코드처럼 LPDIRECTINPUT8 객체을 얻게되면 다른 입력장치 초기화 가능하다.

 

if (FAILED(hr = m_pDI->CreateDevice(GUID_SysKeyboard,
        &m_pKeyDevice, NULL)))
    {
        return false;
    }
    // 장치별 반환 데이터 설정
    m_pKeyDevice->SetDataFormat(&c_dfDIKeyboard);

 

 협력레벨 협조레벨을 설정하는 것은 어떻게 사용되는지 결정하는 것이다.

DLSCL_EXCLUSIVE 플래그는 다른 프로그램들과 공유하지 않는다.

NONEXCLUSIVE로 했다. 캡쳐같은 입력을 하기 위해서다.

 

    // 장치별 독점 및 비독점 설정(협조레벨)
    if (FAILED(hr = m_pKeyDevice->SetCooperativeLevel(
        g_hWnd,
        DISCL_NONEXCLUSIVE |
        DISCL_FOREGROUND |
        DISCL_NOWINKEY)))
    {
        return false;
    }

 

키보드 세팅이 끝나면 Acquire 함수로 키보드에 대한 접근을 취득함

 

 while (m_pKeyDevice->Acquire() == DIERR_INPUTLOST);

 

마찬가지로 마우스 인터페이스 설정과 객체 초기화이다.

위에 키보드와 같으니 설명은 생략한다.

 

마우스 인터페이스 초기화 및 설정

 if (FAILED(hr = m_pDI->CreateDevice(GUID_SysMouse,
        &m_pMouseDevice, NULL)))
    {
        return false;
    }
    m_pMouseDevice->SetDataFormat(&c_dfDIMouse);

    if (FAILED(hr = m_pMouseDevice->SetCooperativeLevel(
        g_hWnd,
        DISCL_NONEXCLUSIVE |
        DISCL_FOREGROUND)))
    {
        return true;
    }
    while (m_pMouseDevice->Acquire() == DIERR_INPUTLOST);
    return true;

 

DXINPUT 객체 해제

bool KInput::ShutDownDirectInput()
{
    if (m_pMouseDevice)
    {
        m_pMouseDevice->Unacquire();
        m_pMouseDevice->Release();
        m_pMouseDevice = NULL;
    }
    if (m_pKeyDevice)
    {
        m_pKeyDevice->Unacquire();
        m_pKeyDevice->Release();
        m_pKeyDevice = NULL;
    }
    if (m_pDI)
    {
        m_pDI->Release();
        m_pDI = NULL;
    }
}

 

DirectInput은 해제전에 Unacquire()로 장치 해제를 해줘야한다.

위 코드 Shutdown하는 함수이다. 

 

DXINPUT 객체 Frame

아래는 풀소스이다.

더보기
bool KInput::Frame()
{
    // 화면좌표
    GetCursorPos(&m_ptPos);
    // 클라이언트 좌표
    ScreenToClient(g_hWnd, &m_ptPos);

    #pragma region 다이렉트 인풋 상태 읽어오기
    HRESULT hr;
    if (m_pMouseDevice == NULL || m_pKeyDevice == NULL) return false;

    if (FAILED(hr = m_pKeyDevice->GetDeviceState(256, m_KeyState)))
    {
        while (m_pKeyDevice->Acquire() == DIERR_INPUTLOST);
    }
    if (FAILED(hr = m_pMouseDevice->GetDeviceState(sizeof(DIMOUSESTATE), &m_DIMouseState)))
    {
        while (m_pMouseDevice->Acquire() == DIERR_INPUTLOST);
    }
    #pragma endregion

    #pragma region  마우스 상태
    for (int iButton = 0; iButton < 3; iButton++)
    {
        m_BeforeMouseState[iButton] = m_DIMouseState.rgbButtons[iButton];
    }
    for (int iButton = 0; iButton < 3; iButton++)
    {
        if (m_BeforeMouseState[iButton] & 0x80)
        {
            if (m_MouseState[iButton] == KEY_FREE)
                m_MouseState[iButton] = KEY_PUSH;
            else
                m_MouseState[iButton] = KEY_HOLD;
        }
        else
        {
            if (m_MouseState[iButton] == KEY_PUSH ||
                m_MouseState[iButton] == KEY_HOLD)
                m_MouseState[iButton] = KEY_UP;
            else
                m_MouseState[iButton] = KEY_FREE;
        }
    }

    ZeroMemory(&g_InputData, sizeof(INPUT_MAP));

    if (m_MouseState[0] == KEY_PUSH) 	g_InputData.bLeftClick = true;
    if (m_MouseState[1] == KEY_PUSH) 	g_InputData.bRightClick = true;
    if (m_MouseState[2] == KEY_PUSH) 	g_InputData.bMiddleClick = true;

    if (m_MouseState[0] >= KEY_PUSH) 	g_InputData.bLeftHold = true;
    if (m_MouseState[1] >= KEY_PUSH) 	g_InputData.bRightHold = true;
    if (m_MouseState[2] >= KEY_PUSH) 	g_InputData.bMiddleHold = true;

    g_InputData.iMouseValue[0] = m_DIMouseState.lX;
    g_InputData.iMouseValue[1] = m_DIMouseState.lY;
    g_InputData.iMouseValue[2] = m_DIMouseState.lZ;
    #pragma endregion
  
    g_InputData.bWKey = GetKey(DIK_W);
    g_InputData.bAKey = GetKey(DIK_A);
    g_InputData.bSKey = GetKey(DIK_S);
    g_InputData.bDKey = GetKey(DIK_D);

    g_InputData.bLShift = GetKey(DIK_LSHIFT);

    g_InputData.bLeftKey = GetKey(DIK_LEFT);
    g_InputData.bRightKey = GetKey(DIK_RIGHT);
    g_InputData.bUpKey = GetKey(DIK_UP);
    g_InputData.bDownKey = GetKey(DIK_DOWN);
    g_InputData.bExit = GetKey(DIK_ESCAPE);
    g_InputData.bSpace = GetKey(DIK_SPACE);
    g_InputData.bExit = GetKey(DIK_ESCAPE);

    if (GetKey(DIK_F5) == KEY_HOLD) 	
    g_InputData.bChangeFillMode = true;

    return true;
}

 

제어권 재 취득 및 입력상태 저장

    HRESULT hr;
    if (m_pMouseDevice == NULL || m_pKeyDevice == NULL) return false;

    if (FAILED(hr = m_pKeyDevice->GetDeviceState(256, m_KeyState)))
    {
        while (m_pKeyDevice->Acquire() == DIERR_INPUTLOST);
    }
    if (FAILED(hr = m_pMouseDevice->GetDeviceState(sizeof(DIMOUSESTATE), &m_DIMouseState)))
    {
        while (m_pMouseDevice->Acquire() == DIERR_INPUTLOST);
    }

 

입력장치를 버퍼에 저장하기전에, 입력장치가 읽는데 실패할때가 있다.

바로 포커스를 잃거나 취득불가상태인 경우이다. 이럴때는 제어권을 돌려받을때까지

Acquire() 다시 취득하기 위해 반복문을 돌려하는 것이 좋다.  

각 상태는 m_KeyState와 m_DIMouseState에 저장된다.

 

마우스 인풋

    #pragma region  마우스 상태
    for (int iButton = 0; iButton < 3; iButton++)
    {
        m_BeforeMouseState[iButton] = m_DIMouseState.rgbButtons[iButton];
    }
    for (int iButton = 0; iButton < 3; iButton++)
    {
        if (m_BeforeMouseState[iButton] & 0x80)
        {
            if (m_MouseState[iButton] == KEY_FREE)
                m_MouseState[iButton] = KEY_PUSH;
            else
                m_MouseState[iButton] = KEY_HOLD;
        }
        else
        {
            if (m_MouseState[iButton] == KEY_PUSH ||
                m_MouseState[iButton] == KEY_HOLD)
                m_MouseState[iButton] = KEY_UP;
            else
                m_MouseState[iButton] = KEY_FREE;
        }
    }

 

마우스 상태는 m_DImouseSTATE.rgbButtons 배열로 받아온다. 

0번 인덱스는 왼쪽마우스 1번 인덱스는 오른쪽 마우스 3번 인덱스는

마우스 가운데 버튼을 의미한다.

 

 if (m_MouseState[0] == KEY_PUSH) 	g_InputData.bLeftClick = true;
    if (m_MouseState[1] == KEY_PUSH) 	g_InputData.bRightClick = true;
    if (m_MouseState[2] == KEY_PUSH) 	g_InputData.bMiddleClick = true;

    if (m_MouseState[0] >= KEY_PUSH) 	g_InputData.bLeftHold = true;
    if (m_MouseState[1] >= KEY_PUSH) 	g_InputData.bRightHold = true;
    if (m_MouseState[2] >= KEY_PUSH) 	g_InputData.bMiddleHold = true;

    g_InputData.iMouseValue[0] = m_DIMouseState.lX;
    g_InputData.iMouseValue[1] = m_DIMouseState.lY;
    g_InputData.iMouseValue[2] = m_DIMouseState.lZ;

 

g_InputData 구조체를 사용했다. 

 

키보드 입력 상태 반환 함수

BYTE KInput::GetKey(BYTE dwKey)
{
    BYTE sKey;
    sKey = m_KeyState[dwKey];
    if (sKey & 0x80)
    {
        if (m_KeyStateOld[dwKey] == KEY_FREE)
            m_KeyStateOld[dwKey] = KEY_PUSH;
        else
            m_KeyStateOld[dwKey] = KEY_HOLD;
    }
    else
    {
        if (m_KeyStateOld[dwKey] == KEY_PUSH ||
            m_KeyStateOld[dwKey] == KEY_HOLD)
            m_KeyStateOld[dwKey] = KEY_UP;
        else
            m_KeyStateOld[dwKey] = KEY_FREE;
    }
    return m_KeyStateOld[dwKey];
}

 

비트연산 & 0x80로 키보드가 눌렸을때 상태를 정해주는 함수이다.

사실 이 GetKey 함수로 바로 입력처리가 가능하지만

g_Inputdata라는 구조체를 사용했다.

 

g_InputData 전역 구조체

struct INPUT_MAP
{
	bool bUpKey;
	bool bDownKey;
	bool bLeftKey;
	bool bRightKey;

	bool bWKey;
	bool bSKey;
	bool bAKey;
	bool bDKey;
	bool bQKey;
	bool bEKey;
	bool bZKey;
	bool bCKey;

	bool bLeftClick;
	bool bRightClick;
	bool bMiddleClick;

	bool bLeftHold;
	bool bRightHold;
	bool bMiddleHold;

	bool bExit;
	bool bSpace; 

	int  iMouseValue[3];

	bool bFullScreen;
	bool bChangeFillMode;
	bool bChangePrimitive;
	bool bChangeCullMode;
	bool bChangeCameraType;
	bool bDebugRender;
};
extern INPUT_MAP g_InputData;

 

전역 구조체로 마우스의 상태와

지정한 키 상태를 전역으로 저장한다. 

구조체를 사용한 이유는 실제로 사용할때 간결해지고

원하는 키입력만 조작하게 하기 위함이다.

 

유니티나 언리얼에도 설정에서 인풋매니져에서 

설정하듯이 등록해서 써야하듯이.

 

만약 불리안 값의 입력 구조체 없이 한다면 이런 코드가 될것이다.

 

    if (g_Input.GetKey(DIK_W) >= KEY_PUSH)
    {
        m_vCameraPos = m_vCameraPos + m_vLook * m_fSpeed * g_fSecPerFrame;
    }
    if (g_Input.GetKey(DIK_S) >= KEY_HOLD)
    {
        m_vCameraPos = m_vCameraPos + m_vLook * -m_fSpeed * g_fSecPerFrame;
    }
    if (g_Input.GetKey(DIK_D) >= KEY_PUSH)
    {
        m_vCameraPos = m_vCameraPos + m_vSide * m_fSpeed * g_fSecPerFrame;
    }
    if (g_Input.GetKey(DIK_A) >= KEY_HOLD)
    {
        m_vCameraPos = m_vCameraPos + m_vSide * -m_fSpeed * g_fSecPerFrame;
    }

 

하지만 입력 구조체를 사용한다면 간결해진다.

그리고 만약에 WASD말고 방향키로 여러 입력을 받는 이동키의 경우

이전 구조체를 안쓰는 버젼으로 하면 코드가 엄청 지저분해질것이다.

 

    if (g_InputData.bWKey)
    {
        m_vCameraPos = m_vCameraPos + m_vLook * m_fSpeed * g_fSecPerFrame;
    }
    if (g_InputData.bSKey)
    {
        m_vCameraPos = m_vCameraPos + m_vLook * -m_fSpeed * g_fSecPerFrame;
    }
    if (g_InputData.bAKey)
    {
        m_vCameraPos = m_vCameraPos + m_vSide * m_fSpeed * g_fSecPerFrame;
        m_vCameraTarget.x += m_fSpeed * g_fSecPerFrame;
    }
    if (g_InputData.bDKey)
    {
        m_vCameraPos = m_vCameraPos + m_vSide * -m_fSpeed * g_fSecPerFrame;
        m_vCameraTarget.x -= m_fSpeed * g_fSecPerFrame;
    }

 

ZeroMemory(&g_InputData, sizeof(INPUT_MAP));

    if (m_MouseState[0] == KEY_PUSH) 	g_InputData.bLeftClick = true;
    if (m_MouseState[1] == KEY_PUSH) 	g_InputData.bRightClick = true;
    if (m_MouseState[2] == KEY_PUSH) 	g_InputData.bMiddleClick = true;

    if (m_MouseState[0] >= KEY_PUSH) 	g_InputData.bLeftHold = true;
    if (m_MouseState[1] >= KEY_PUSH) 	g_InputData.bRightHold = true;
    if (m_MouseState[2] >= KEY_PUSH) 	g_InputData.bMiddleHold = true;

    g_InputData.iMouseValue[0] = m_DIMouseState.lX;
    g_InputData.iMouseValue[1] = m_DIMouseState.lY;
    g_InputData.iMouseValue[2] = m_DIMouseState.lZ;
    #pragma endregion
  
    g_InputData.bWKey = GetKey(DIK_W);
    g_InputData.bAKey = GetKey(DIK_A);
    g_InputData.bSKey = GetKey(DIK_S);
    g_InputData.bDKey = GetKey(DIK_D);

    g_InputData.bLShift = GetKey(DIK_LSHIFT);

    g_InputData.bLeftKey = GetKey(DIK_LEFT);
    g_InputData.bRightKey = GetKey(DIK_RIGHT);
    g_InputData.bUpKey = GetKey(DIK_UP);
    g_InputData.bDownKey = GetKey(DIK_DOWN);
    g_InputData.bExit = GetKey(DIK_ESCAPE);
    g_InputData.bSpace = GetKey(DIK_SPACE);
    g_InputData.bExit = GetKey(DIK_ESCAPE);

    if (GetKey(DIK_F5) == KEY_HOLD) 	
    g_InputData.bChangeFillMode = true;

    return true;
}

 

Frame 함수에 입력구조체를 사용해서

매 프레임 입력받은 것을 처리해주고 전역 구조체를 다른 클래스에서

접근해서 불리안 값인 구조체를 값을 판별해 입력의 유무를 구분한다.

 

결과 

COMMENT