使用 IGameInput 为游戏构建本机触摸界面
当玩家将您的游戏流式传输到他们的移动设备时,你可以提高他们玩游戏乐趣的最佳方式之一是让他们能够使用触摸控件与您的游戏进行交互。 Xbox 游戏流式处理支持使用 触控适配工具包覆盖游戏上的触摸控件,这对于游戏中的许多屏幕来说都是一个不错的选择,其中虚拟游戏板是玩游戏的自然方式。 但是,对于部分游戏(如菜单、地图、库存屏幕等)而言,直接与触摸输入交互可能更自然。
例如,玩家想要点击菜单选项与之交互,或者想要使用移动电话手势放大地图并在地图上滚动是非常自然的。 Microsoft 游戏开发工具包 (GDK) 提供 API,使你能够将此本机触摸界面构建到游戏中,使其感觉具有真正的移动体验。
触控适配工具包和本机触控不是互斥的,你可以在游戏中同时使用这两种功能,使用每种形式的触摸输入,选择其中最适合游戏。 例如,在主游戏循环中使用触控适配工具包时,使用本机触摸菜单可能是构建游戏触摸输入策略的合理方法。
注意
如果你正在使用本机触控构建游戏,请通知客户经理。 需要配置 Xbox 游戏流式处理后端服务,使游戏能够在游戏部署到零售环境时接收触摸事件。
可通过 IGameInputReading::GetTouchState API 获取游戏的本机触摸。 此 API 将提供当前触摸点集,表示玩家触摸屏幕的当前手指集。
若要将这些触摸点转换为按下、移动和释放事件,在每个帧中跟踪其状态非常有用。
- 前一帧中不存在但当前帧中存在的任何触摸点都是新的触摸按下
- 任何在前一帧中存在且在当前帧中仍然存在的触点,如果触点的坐标没有改变,要么是移动,要么是无操作。
- 在前一帧中出现但不再出现的触点是触控释放。
构建这些按下、移动和发布事件的基本输入循环如下:
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{};
}
}
}
}