IGameInput을 사용하여 게임에 대한 네이티브 터치 인터페이스 빌드
플레이어가 모바일 장치로 게임을 스트리밍할 때 게임의 만족도를 높일 수 있는 가장 좋은 방법 중 하나는 터치 컨트롤을 사용하여 게임과 상호 작용하는 기능입니다. Xbox 게임 스트리밍은 터치 적응 키트를 사용하여 게임에 터치 컨트롤을 오버레이하는 기능을 지원하며, 가상 게임 패드를 사용하여 자연스럽게 플레이하는 게임의 화면에 적합할 수 있습니다. 메뉴, 지도, 인벤토리 화면 등과 같은 게임의 부분은 터치 입력으로 직접 게임과 상호 작용하는 것이 더 자연스러울 수 있습니다.
예를 들어 대부분의 플레이어는 메뉴 옵션을 탭하여 상호 작용하거나 휴대폰 제스처를 사용하여 지도를 확대하고 스크롤하고 싶어 합니다. Microsoft GDK(게임 개발 키트)는 게임에 이 네이티브 터치 인터페이스를 빌드할 수 있는 API를 제공하여 진정한 모바일 환경처럼 느낄 수 있도록 지원합니다.
터치 적응 키트와 네이티브 터치는 상호 배타적이지 않으며 게임에서는 타이틀에 가장 적합한 형태의 터치 입력을 모두 사용할 수 있습니다. 예를 들어 기본 게임플레이 루프에 터치 적응 키트를 사용하는 동시에 네이티브 터치 메뉴를 사용하는 것이 타이틀의 터치 입력 전략을 구성하는 합리적인 방법일 수 있습니다.
참고 항목
네이티브 터치로 게임을 빌드하는 경우 계정 관리자에게 알려주세요. 게임이 소매 환경에 배포될 때 터치 이벤트를 수신할 수 있도록 Xbox 게임 스트리밍 백 엔드 서비스를 구성해야 합니다.
네이티브 터치는 IGameInputReading::GetTouchState API를 통해 게임에 사용할 수 있습니다. 이 API는 플레이어가 화면을 터치하는 현재 손가락 집합을 나타내는 현재 터치 포인트 집합을 제공합니다.
이 터치 포인트를 눌러서 이동하고 이벤트를 해제하도록 변환하려면 각 프레임에서 해당 상태를 추적하는 방법이 유용할 수 있습니다.
- 이전 프레임에는 없지만 현재 프레임에 있는 모든 터치 포인트는 새로운 터치 프레스입니다.
- 터치의 좌표가 변경되지 않은 경우 이전 프레임에도 있고 현재 프레임에도 계속 존재하는 터치 포인트는 이동 또는 no-op입니다.
- 이전 프레임에는 있지만 더 이상 존재하지 않는 터치 포인트는 터치 릴리스입니다.
이러한 프레스, 이동, 릴리스 이벤트를 빌드하는 기본적인 입력 루프는 다음과 같습니다.
struct TouchPoint
{
// GameInputTouchState.touchId corresponding to this point
uint64_t Id = 0;
// X pixel being touched
uint32_t X = 0;
// Y pixel being touched
uint32_t Y = 0;
// Is this entry in the array of touch points currently being used to track a touch
bool IsInUse = false;
// Is this TouchPoint tracking a finger which is currently touching the screen
bool IsTouchActive = false;
};
// Touch points currently being tracked
std::array<TouchPoint, 10> g_touchPoints = {};
extern IGameInput* g_gameInput;
extern uint32_t g_frameWidth;
extern uint32_t g_frameHeight;
void TouchPointPressed(const TouchPoint& touchPoint);
void TouchPointMoved(const TouchPoint& touchPoint);
void TouchPointReleased(const TouchPoint& touchPoint);
void ProcessTouchInput()
{
// Reset the active touch state from the previous frame
for (TouchPoint& touchPoint : g_touchPoints)
{
touchPoint.IsTouchActive = false;
}
// Process any new touch points for this frame
Microsoft::WRL::ComPtr<IGameInputReading> reading = nullptr;
if (SUCCEEDED(g_gameInput->GetCurrentReading(GameInputKindTouch, nullptr, &reading)))
{
uint32_t touchCount = reading->GetTouchCount();
if (touchCount > 0)
{
std::vector<GameInputTouchState> touchStates(touchCount);
touchCount = reading->GetTouchState(touchCount, touchStates.data());
for (const GameInputTouchState& touchState : touchStates)
{
const uint32_t x = static_cast<uint32_t>(touchState.positionX * g_frameWidth);
const uint32_t y = static_cast<uint32_t>(touchState.positionY * g_frameHeight);
// Check to see if we are already tracking this touch point
auto existingPoint = std::find_if(std::begin(g_touchPoints), std::end(g_touchPoints), [id = touchState.touchId](const TouchPoint& point)
{
return point.IsInUse && point.Id == id;
});
if (existingPoint != g_touchPoints.end())
{
// We were already tracking the point - it is still alive this frame, but it may have
// also moved position.
existingPoint->IsTouchActive = true;
if (existingPoint->X != x || existingPoint->Y != y)
{
existingPoint->X = x;
existingPoint->Y = y;
TouchPointMoved(*existingPoint);
}
}
else
{
// This is a new touch point. Start tracking it and treat it as a press
auto insertPoint = std::find_if(std::begin(g_touchPoints), std::end(g_touchPoints), [](const TouchPoint& point)
{
return !point.IsInUse;
});
if (insertPoint != std::end(g_touchPoints))
{
insertPoint->Id = touchState.touchId;
insertPoint->X = x;
insertPoint->Y = y;
insertPoint->IsInUse = true;
insertPoint->IsTouchActive = true;
TouchPointPressed(*insertPoint);
}
}
}
}
// Look for any points which were pressed last frame but are no longer pressed this frame
// and treat those as touch releases
for (TouchPoint& touchPoint : g_touchPoints)
{
if (touchPoint.IsInUse && !touchPoint.IsTouchActive)
{
TouchPointReleased(touchPoint);
touchPoint = TouchPoint{};
}
}
}
}