XInput에서 GameInput으로 포팅
XInput에서 GameInput으로의 포팅은 기존 API 중 가장 복잡하지 않습니다. 이는 GameInput이 XInput의 간단한(그리고 사용하기 쉬운) 프로그래밍 모델에 크게 영향을 받았으며, 따라서 많은 XInput API가 GameInput에서 동등한 기능을 사용하여 1:1로 매핑하기 때문입니다.
주요 차이점
XInput과 GameInput의 주요 차이점은 다음 섹션에서 설명합니다.
C와 C++
XInput API는 기본 C 함수 모음입니다. 반면에 GameInput은 C++ 함수이므로 그래픽 및 오디오 API와 마찬가지로 인터페이스를 사용합니다. 실제로 이는 GameInput API를 사용하는 코드를 복잡하게 만들지 않으며, 성능에도 영향을 미치지 않습니다. 또한 GameInput이 작동하는 방식에 익숙해지면 몇 가지 이점도 누릴 수 있습니다.
이러한 인터페이스는 COM처럼 보이지만 실제로는 그렇지 않다는 점을 반드시 이해해야 합니다. 이러한 인터페이스를 사용하는 데는 참조 계수에 대한 기본적인 이해만 하면 됩니다. 자세한 내용은 GameInput 기본 사항의 인터페이스 섹션을 참조하세요.
입력 가져오기
XInput에서 대부분의 게임은 연결된 장치로 발견 될 때까지 사용자 색인을 통해 루프를 그리며 해당 장치에서 상태를 읽습니다. 게임은 종종 사용자 색인을 기억하므로 다음 번에는 루프를 반복할 필요가 없습니다. 예를 들어, 다음 코드는 사용자가 컨트롤러에서 "A를 누르도록" 요청하는 게임의 전형적인 코드입니다.
// This function looks for a gamepad that currently has the "A" button pressed.
void FindActiveGamepad()
{
for (DWORD index = 0; index < XUSER_MAX_COUNT; index++)
{
XINPUT_STATE state;
if (XInputGetState(index, &state) == ERROR_SUCCESS)
{
if (state.Gamepad.wButtons & XINPUT_GAMEPAD_A)
{
// Found the user's gamepad at this index.
}
}
}
}
GameInput에서는 원할 경우 장치를 지정하지 않고 먼저 입력을 얻고, 어떤 장치에서 입력된 것인지 쿼리할 수 있습니다. 코드는 유사하지만 명시적 장치 열거의 필요성이 없다면 알고리즘이 간단해집니다.
// This function looks for a gamepad that currently has the "A" button pressed.
void FindActiveGamepad(IGameInput * gameInput)
{
// This checks for input from all gamepads simultaneously.
IGameInputReading * reading;
if (SUCCEEDED(gameInput->GetCurrentReading(GameInputKindGamepad, nullptr, &reading)))
{
GameInputGamepadState state;
reading->GetGamepadState(&state);
if (state.buttons & GameInputGamepadA)
{
// Found the user's gamepad. At this point we can
// get the device that generated this input, and then
// pass that into future calls to the GetCurrentReading
// method to receive input only from that gamepad.
}
reading->Release():
}
}
코드는 XInput만큼 간단하지는 않지만 매우 유사합니다. GameInput API에 익숙해지면 이 모델이 XInput에는 없는 강력한 입력 처리 옵션을 제공한다는 것을 알 수 있습니다.
또한 XInput은 형식 BYTE 트리거로부터 아날로그 값, 형식 SHORT 엄지스틱으로부터 아날로그 값을 반환한다는 점도 주목할 만한 가치가 있습니다. GameInput API에서는 이러한 아날로그 값이 0~1(트리거) 또는 -1~1(엄지스틱) 사이의 float 값으로 반환됩니다.
진동 피드백
XInput에서 게임은 XInputSetState
을(를) 호출하여 장치에 진동 명령을 전송합니다. GameInput에서 게임은 장치에 대한 IGameInputDevice 인스턴스를 취득한 다음 해당 SetRumbleState 메서드를 호출해야 합니다. 두 메서드 간 사용법은 비슷합니다. 이는 장치 식별자가 아닌 장치 인터페이스에서 기능을 호출할 때 예입니다.
애플리케이션 초점
콘솔에서 GameInput은 초점에 있을 때만 애플리케이션에 입력을 제공합니다. 그렇지 않으면 반환되는 상태에는 사용자가 장치를 만지지 않을 때처럼 중립 또는 '휴면' 값이 포함됩니다. 때문에 초점의 변화를 다루는 추가 입력 코드(예: XInputEnable
호출)가 필요하지 않습니다.
PC에서 입력은 기본적으로 모든 프로세스에 제공됩니다. 나중에 SetFocusPolicy 메서드를 사용하여 이 동작을 변경할 수 있습니다.
XInputOnGameInput 래퍼
Microsoft GDK(게임 개발 키트)에는 XInputOnGameInput.h
라는 헤더 파일이 있습니다. 여기에는 GameInput 위에 구현한 XInput API가 포함되어 있습니다. 특히 키보드 및 마우스 또는 기타 입력 장치가 필요한 경우 GameInput에 직접 포팅하는 것이 좋습니다. 그러나 XInputOnGameInput 래퍼는 기존 XInput 코드에 대한 변경 없이 초기 포팅 작업을 부트스트랩하는 데 사용할 수 있습니다.
XInputOnGameInput 래퍼를 사용하려면 다음 코드를
#include <XInput.h>
다음 코드로 바꾸면 됩니다.
#include <XInputOnGameInput.h>
using namespace XInputOnGameInput;
그리고 코드를 다시 컴파일합니다.
XInput 래퍼 코드의 구현은 전적으로 헤더 파일에 있으므로 GameInput API를 사용하는 예로 검토하거나 필요에 따라 수정할 수도 있습니다.
XInput과 XInputOnGameInput의 차이
일반적으로 XInputOnGameInput 래퍼는 레거시 XInput API의 직접 적응 대체 방법입니다. 하지만 몇 가지 사소한 차이가 있습니다.
단순성을 위해 게임 패드 장치 지원만 래퍼에 코딩되었습니다. 레이싱 휠 또는 아케이드 스틱과 같은 다른 장치를 지원해야 하는 경우 GameInput을 직접 사용하거나 XInputOnGameInput 코드에 장치에 대한 지원을 추가합니다.
래퍼는 게임이 포커스를 받을 때만 게임 패드 입력을 반환합니다. 게임이 포커스가 아닐 때는 사용자가 게임 패드에 접촉하고 있지 않은 것처럼 반환된 게임 패드 상태가 보통 또는 "rest" 값으로 설정됩니다. 이는
XInputEnable
에 대한 호출 수(또는 호출이 없는 경우)에 관계없이 수행됩니다.값
XUSER_MAX_COUNT
이 4에서 8로 증가했습니다. 이는 일반적으로 대부분의 기존 XInput 코드에 투명하게 작동합니다. 그러나 코드에서XInputGetKeystroke
함수의 사용을 신중하게 검토하여XINPUT_KEYSTROKE
구조의UserIndex
멤버에서 최대 4의 값이 반드시 반환될 것이라고 가정하는 어떤 것도 하드 코딩하지 않도록 합니다. 그렇지 않으면 버퍼 오버런이 발생할 수 있습니다.몇 가지 새로운 함수가 추가되었습니다(아래 참조). 이는 프로덕션 코드에서 XInput 래퍼를 계속 사용할 때만 관심을 가지면 됩니다.
프로덕션 코드에서 XInputOnGameInput 사용
XInputOnGameInput 래퍼는 고성능으로 잠금 해제된 상태로 작성되며 GameInput API의 모든 최적화된 성능을 상속하므로 프로덕션 코드에서 사용하기에 적합합니다. 또한 GameInput의 광범위한 장치 지원(예: 인기 HID 게임 패드)을 상속하고 API에 다음과 같은 새로운 함수를 추가합니다.
XInputSetStateEx
는 유사XInputSetState
하지만 트리거 모터에 대한 지원을 추가합니다.XInputGetStateWithToken
은XInputGetState
와 비슷하지만 호출자가 D3DX 프레임 파이프라인 토큰을 공급하여 PIX에서 나중에 분석을 위해 특정 입력 읽기를 그래픽 프레임과 연결할 수 있습니다.참고 항목
5월 미리 보기 릴리스에서는
XInputGetStateWithToken
이(가) 이XInputGetState
과(와) 동일하게 작동합니다. 기반이 되는 GameInput 코드가 완전히 구현되지 않았기 때문입니다.XInputGetDeviceId
은(는) 특정 사용자 인덱스에서 장치에 대한APP_LOCAL_DEVICE_ID
을(를) 반환합니다. IGameInput에서 이 ID를 FindDeviceFromId 메서드로 전달하면 해당 사용자 인덱스의 해당하는 IGameInputDevice가 반환됩니다. 그런 다음 XInput 래퍼를 통해 노출되지 않는 GameInput API의 추가 기능에 액세스하는 데 사용할 수 있습니다.
래퍼 코드 최적화
기본적으로 XInputOnGameInput 래퍼는 기존 XInput API와 적응 호환되도록 구성되어 있습니다. 100% 호환 동작이 필요 없는 게임은 래퍼의 동작과 성능을 미세 조정할 수 있는 다음 전처리기 매크로 중 하나를 정의합니다.
XINPUT_ON_GAMEINPUT_EXPLICIT_INITIALIZATION
기본적으로 XInput 래퍼는 기본 GameInput API를 느리게 초기화하고 자동으로 래퍼 함수 중 하나를 처음으로 호출합니다. 이렇게 하면 기존 XInput 코드와 적응 호환되지만 몇 가지 사소한 단점이 있습니다.
첫 번째 XInput 래퍼 함수 호출의 실행 시간이 평소보다 길어집니다.
모든 XInput 래퍼 함수는 호출될 때마다 느린 초기화가 수행되었는지 확인하는 검사를 수행해야 합니다. 이것은 전역 변수의 간단한 테스트이므로 분기 예측기가 비용을 개선하지만 추가 오버헤드입니다.
실패해서는 안 되지만, 기본 GameInput API의 느린 초기화에 성공했는지 여부를 알 수 있는 방법이 없습니다.
모듈 언로드 또는 프로세스 종료로 인해 XInput 래퍼의 전역 변수가 정리될 때까지 기본 IGameInput 인스턴스가 해제되지 않습니다.
게임에서 XINPUT_ON_GAMEINPUT_EXPLICIT_INITIALIZATION
매크로를 정의하여 래퍼 초기화 및 종료를 수동으로 제어할 수 있습니다. 이렇게 하면 초기화와 종료 시 정확한 제어를 위해 호출할 수 있는 두 가지 새로운 함수인 XInputOnGameInputInitialize
와 XInputOnGameInputUninitialize
가 추가됩니다.
XINPUT_ON_GAMEINPUT_NO_XINPUTENABLE
XInputEnable
함수를 구현하는 데 필요한 코드로 인해 모든 XInputGetState
, XInputGetStateWithToken
, XInputSetState
, XInputSetStateEx
, XInputGetKeystroke
함수 호출 시 추가 오버헤드가 더해집니다. 코드가 XInputEnable
을 호출하지 않거나 코드에서 쉽게 제거될 수 있을 때, XINPUT_ON_GAMEINPUT_NO_XINPUTENABLE
매크로를 정의하면 XInputEnable
을 지원하지 않게 되고 관련된 오버헤드는 사라집니다. 기본 GameInput는 초점 변경에 맞춰 XInputEnable
의 기능을 자동으로 수행하므로 대부분의 게임이 가능하면 이 매크로를 정의하려 할 것입니다.
XINPUT_ON_GAMEINPUT_NO_XINPUTGETKEYSTROKE
XInputGetKeystroke
함수를 구현하는 데 필요한 코드는 래퍼 구현에 몇 가지 추가 기능과 변수를 추가합니다. 다른 XInput API 기능에 오버헤드를 추가하지는 않지만, 코드가 XInputGetKeystroke
를 호출하지 않는다면 XINPUT_ON_GAMEINPUT_NO_XINPUTGETKEYSTROKE
매크로를 정의하여 XInput 래퍼의 코드/데이터 크기를 약간 줄일 수 있습니다.