2024년 8월 2일 금요일

DirectInput

 

키보드 입력과 마우스 좌표를 얻어내기 위한 여러 방법이 있는데

기본적으로 MFC에서 사용하던 윈도우 메시지를 처리해서

키입력을 알아내는 방법이 있다.




이 방법은 윈도우 창을 생성하게 되면

 어차피 <Windows.h>헤더를 포함시킬 수 밖에 없기에

따로 라이브러리를 설치하거나 include 하지 않아도 

사용 할 수 있는 장점이 있지만





문제는 프레임 단위마다 메시지를 처리하게 되면 WM_MOUSEMOVE 같이

많은 메시지가 발생할 때 메시지 큐에 데이터가 쌓인다는 문제가 있음

그래서 마치 렉걸린듯이 이전에 입력한 키가 몇초뒤에 처리되는 경우가 발생하는데



	while (true)
	{
		if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}


이 문제를 해결하기 위해서는 위 같이 메시지를 처리하는 루프를 

새 스레드로 돌리거나 다른 키입력 방식을 사용하는 방법밖에 없다.



메시지 루프가 키에 해당하는 bool 값을 업데이트 한다고 치면

다른 클래스에서 그 bool 변수에 접근하기 위해 동기화 작업도 해줘야 하는데

그러면 너무 번거롭기도 하고 시간이 오래 걸릴 것임



그래서 원래 쓰던 DirectInput를 사용하기로 함

DirectInput은 DirectX를 프로젝트에 포함하게 되면 사용할 수 있기에

  DirectX를 사용하는 렌더링 프로그램이면 그냥 dInput 쓰는게 낫긴하다. 



LPDIRECTINPUT8 - 이건 DirectInput 인터페이스 포인터 이고

IDirectInput8* 의 매크로 타입이다

입력장치 InputDevice를 생성할때 사용


LPDIRECTINPUTDEVICE8 - 특정 입력 장치의 입력 상태를 읽는 인터페이스

IDirectInputDevice8 의 포인터 매크로 타입



//DirectInput 생성
result = DirectInput8Create(hInstance, DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&m_directInput, nullptr);
if (FAILED(result))
{
	return false;
}

//키보드용 Device 생성
result = m_directInput->CreateDevice(GUID_SysKeyboard, &m_keyboardDevice, nullptr);
if (FAILED(result))
{
	return false;
}

//데이터 형식을 키보드 형식으로 설정
result = m_keyboardDevice->SetDataFormat(&c_dfDIKeyboard);
if (FAILED(result))
{
	return false;
}

//협력 수준 설정
result = m_keyboardDevice->SetCooperativeLevel(hwnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);
if (FAILED(result))
{
	return false;
}

//키보드 장치 획득
result = m_keyboardDevice->Acquire();
if (FAILED(result))
{
	return false;
}

이런식으로 DirectInputDevice를 생성해서

키보드의 입력 값을 받아올 수 있는 Device를 얻을 수 있음 



DirectInput8Create 함수로 DirectInput 객체를 초기화 한다.

DIRECTINPUT_VERSION 는 사용할 DirectInput의 버전이고 DirectInput 8를 가르킨다. 

DirectInput은 8이 최신이고 마지막 버전임 앞으로도 업뎃 X


IID_IDirectInput8도 마찬가지로 8 버전의 인터페이스로 초기화 하겠다는 뜻이다.


마지막껀 잘 모르겠음 그냥 다들 null 로 쓴다.



SetDataFormat 함수는 해당 디바이스가 어떤 장치용으로 쓸건지 설정하는 것



SetCooperativeLevel 함수는 입력장치가 

다른 프로그램이나 시스템이랑 상호작용하는 방식을 설정함



DISCL_FOREGROUND - 해당 윈도우가 포커싱 상태일때만 입력을 받음

DISCL_BACKGROUND - 포커싱 상태가 아니여도 입력을 받음

DISCL_EXCLUSIVE - 입력 장치를 독점한다. 다른 프로그램이 입력을 못 받음

DISCL_NONEXCLUSIVE - 장치 비독점 



Acquire 함수는 장치를 쓸 수 있는지 확인하는 용도

장치가 초기화 되지 않았거나 다른 프로그램이 독점 사용할때 false를 리턴




DirectInputDevice로부터 키보드 입력값을 받아오는 코드

	result = m_keyboardDevice->GetDeviceState(sizeof(m_keyboardState), (LPVOID)&m_keyboardState);
	if (FAILED(result))
	{

		if ((result == DIERR_INPUTLOST) || (result == DIERR_NOTACQUIRED))
		{
			m_keyboardDevice->Acquire();
		}
		else
		{
			return false;
		}
	}


DirectInputDevice로부터 키보드 입력값을 받아오는 함수

GetDeviceState의 리턴값이 false일 때

InputLost 하거나 비독점 상태일 때 다시 Acquire 함수로 장치 사용할 수 있게 만듦




	if (m_keyboardState[DIK_A] & 0x80)
	{
		return true;
	}

	return false;

그렇게 받아온 입력값을 배열 uint m_keyboardState [256]에 저장해서 판단하는데

256는 모든 입력값 개수였던걸로 앎  

위는 A키가 눌렸는지 확인하는 코드이다.



keyState의 값이 0x00 이면 Release

0x80 은 Pressed를 의미함

이는 int값으로는 128이고 이진수로 10000000 이다

이걸 비트연산 & 해서 확인하는 원리

굳이 == 128로 안하는 이유는 더 효율적이고 빠르니까


참고로 16진수는 0x1234 각 1234 가 이진수 4자리인데

0x1 0x2 0x3 0x4

0001 0010 0011 0100

요걸 합친거

그래서 0x80 이 1000 0000 이다.

c++ thread.h

 c++에서 쓰레드 돌릴려면 thread.h 헤더를 쓰면 되는데 이 친구는 쓰레드가 아직 실행 중인지, 아니면 강제 종료하거나 하는 함수가 없어서 조금 아쉬운 애다. std::thread 는 로컬 변수로 선언하든 new 동적 할당을 하든 start 함...