Handle input state

[ This article is for Windows 8.x and Windows Phone 8.x developers writing Windows Runtime apps. If you’re developing for Windows 10, see the latest documentation ]

Learn how to use and modify the WDC DirectX game learning template's InputManager class to incorporate controls into your project.

Adding InputManager into your game app

InputManager is a "helper" learning template class provided to demonstrate how to consolidate and incorporate common controls into your game. It is not designed to replace specific, tailored controls, but rather is intended to allow you to explore game code with a working input model. This class takes input from a touch surface, mouse and keyboard, and Xbox controllers. It processes input from all these sources and returns an appropriate game action, such as "move" or "jump," along with any data that you can use in calculating game object behaviors.

To use input manager, declare a reference to it on your game's main class. In the template, it's declared like this.

// Input manager instance
std::unique_ptr<InputManager>      m_inputManager;

Once you've declared a member variable for the InputManager instance (for example, in the Initialize method of your main app class's view provider implementation), you create it like this:

m_inputManager = std::unique_ptr<InputManager>(new InputManager());
// Supports all control types by default.
m_inputManager->SetFilter(INPUT_DEVICE_ALL);
m_inputManager->Initialize(CoreWindow::GetForCurrentThread());

Additionally, if you are using touch controls, you'll need some way to manage the touch regions you create. Touch regions contain specific touch controls, like virtual analog sticks and buttons, and touch controls will likely be different for different game screens. Having access to the regions you created lets you turn them on and off as needed. The template uses a simple array to store IDs of the touch regions it creates. If you create more touch control regions, you should expand this array.

// Tracks the touch region ID, allowing you to enable/disable touch regions.
// Note to developer: You can expand this array and use it to enable/disable regions for different game screens.
unsigned int m_touchRegionIDs[3];

Processing user input with InputManager

One you've initialized input manager, you can begin processing user input in your game. The following code is taken from the template (SampleDebugTextRenderer.cpp) and demonstrates the use of InputManager to draw the processed input states and values to a Direct2D overlay.

m_playersAttached = playersAttached;

for (unsigned int i = 0; i < XINPUT_MAX_CONTROLLERS; i++)
{
  std::wstring inputText = L"";

  unsigned int playerAttached = (playersAttached & (1 << i));

  if (!playerAttached)
    continue;

  for (unsigned int j = 0; j < playerInputs->size(); j++)
  {
    PlayerInputData playerAction = (*playerInputs)[j];

    if (playerAction.ID != i) continue;

    switch (playerAction.PlayerAction)
    {
      case PLAYER_ACTION_TYPES::INPUT_FIRE_PRESSED:
          inputText += L"\n FirePressed(" + std::to_wstring(playerAction.NormalizedInputValue) + L") ";
          break;
      case PLAYER_ACTION_TYPES::INPUT_FIRE_DOWN:
          inputText += L"\n FireDown(" + std::to_wstring(playerAction.NormalizedInputValue) + L") ";
          break;
      case PLAYER_ACTION_TYPES::INPUT_FIRE_RELEASED:
          inputText += L"\n FireReleased(" + std::to_wstring(playerAction.NormalizedInputValue) + L") ";
          break;
      case PLAYER_ACTION_TYPES::INPUT_JUMP_PRESSED:
          inputText += L"\n JumpPressed(" + std::to_wstring(playerAction.NormalizedInputValue) + L") ";
          break;
      case PLAYER_ACTION_TYPES::INPUT_JUMP_DOWN:
          inputText += L"\n JumpDown(" + std::to_wstring(playerAction.NormalizedInputValue) + L") ";
          break;
      case PLAYER_ACTION_TYPES::INPUT_JUMP_RELEASED:
          inputText += L"\n JumpReleased(" + std::to_wstring(playerAction.NormalizedInputValue) + L") ";
          break;
      case PLAYER_ACTION_TYPES::INPUT_MOVE:
          inputText += L"\n MoveX(" + std::to_wstring(playerAction.X) + L") ";
          inputText += L"\n MoveY(" + std::to_wstring(playerAction.Y) + L") ";
          break;
      case PLAYER_ACTION_TYPES::INPUT_AIM:
          inputText += L"\n AimX(" + std::to_wstring(playerAction.X) + L") ";
          inputText += L"\n AimY(" + std::to_wstring(playerAction.Y) + L") ";
          break;
      case PLAYER_ACTION_TYPES::INPUT_BRAKE:
          inputText += L"\n Brake(" + std::to_wstring(playerAction.NormalizedInputValue) + L") ";
          break;
      default:
          break;
    }
  }
}

The states (PLAYER_ACTION_TYPES enumeration) are defined in InputManager.h and can be changed by editing the InputManager implementation code.

Incorporating touch controls

Adding a touch region

The WDC DirectX game learning template implements a virtual analog stick and buttons using defined touch regions. You can use this code, found in SampleVirtualController.h and SampleVirtualController.cpp, as a base for implementing your own touch controls.

To start using touch regions in conjunction with the input manager, set up the regions when initializing or loading your app (Initialize and Load in your view provider implementation) like this:

// Here we set up the touch control regions.
Windows::Foundation::Size logicalSize = m_deviceResources->GetLogicalSize();
XMFLOAT2 screenTopLeft = XMFLOAT2(0, 0);
XMFLOAT2 screenTopRight = XMFLOAT2(logicalSize.Width, 0);
XMFLOAT2 screenBottomLeft = XMFLOAT2(0, logicalSize.Height);
XMFLOAT2 screenBottomRight = XMFLOAT2(logicalSize.Width, logicalSize.Height);
float width = screenTopRight.x - screenTopLeft.x;
float height = screenBottomLeft.y - screenTopLeft.y;

// Clear previous touch regions.
m_virtualControllerRenderer->ClearTouchControlRegions();
m_inputManager->ClearTouchRegions();

// The following scoped code region sets up the analog stick.
{
    const float stickRegionPercent = 0.75f;
    float stickRegionWidth = (width * stickRegionPercent);
    XMFLOAT2 touchRegionBoundary = XMFLOAT2(screenTopLeft.x + stickRegionWidth, screenBottomRight.y);

    TouchControlRegion touchControlRegionStick(
        screenTopLeft,
        touchRegionBoundary,
        TOUCH_CONTROL_REGION_ANALOG_STICK,
        PLAYER_ACTION_TYPES::INPUT_MOVE,
        PLAYER_ID::PLAYER_ID_ONE
        );

    DWORD errorCode = m_inputManager->SetDefinedTouchRegion(&touchControlRegionStick, m_touchRegionIDs[0]);

    if (!errorCode)
        m_virtualControllerRenderer->AddTouchControlRegion(touchControlRegionStick);
}

// The following scoped code region sets up the buttons.
{
    const float buttonRegionPercent = 0.2f;
    const float buttonWidthHeight = 80.f;
    const float buttonDesiredOffset = 300.f;

    // Control the max location to prevent overlap
    float buttonWidthOffset = (width * buttonRegionPercent) - buttonWidthHeight;
    buttonWidthOffset = buttonWidthOffset < buttonDesiredOffset ? buttonWidthOffset : buttonDesiredOffset;

    // Set up button A
    XMFLOAT2 location1 = { width - (buttonWidthOffset + 1.f * buttonWidthHeight), height - buttonDesiredOffset };
    TouchControlRegion touchControlRegionButtonA(
        XMFLOAT2(location1.x, location1.y),
        XMFLOAT2(location1.x + buttonWidthHeight, location1.y + buttonWidthHeight),
        TOUCH_CONTROL_REGION_TYPES::TOUCH_CONTROL_REGION_BUTTON,
        PLAYER_ACTION_TYPES::INPUT_FIRE_DOWN,
        PLAYER_ID::PLAYER_ID_ONE
        );

    DWORD errorCode = m_inputManager->SetDefinedTouchRegion(&touchControlRegionButtonA, m_touchRegionIDs[1]);
    if (!errorCode)
        m_virtualControllerRenderer->AddTouchControlRegion(touchControlRegionButtonA);

    // Set up button B
    XMFLOAT2 location2 = { width - buttonWidthOffset, height - buttonDesiredOffset - (1.f * buttonWidthHeight) };
    TouchControlRegion touchControlRegionButtonB(
        XMFLOAT2(location2.x, location2.y),
        XMFLOAT2(location2.x + buttonWidthHeight, location2.y + buttonWidthHeight),
        TOUCH_CONTROL_REGION_TYPES::TOUCH_CONTROL_REGION_BUTTON,
        PLAYER_ACTION_TYPES::INPUT_JUMP_DOWN,
        PLAYER_ID::PLAYER_ID_ONE
        );

    errorCode = m_inputManager->SetDefinedTouchRegion(&touchControlRegionButtonB, m_touchRegionIDs[2]);
    if (!errorCode)
        m_virtualControllerRenderer->AddTouchControlRegion(touchControlRegionButtonB);
}

Once you've defined the regions and behaviors, you must also have a class to draw the virtual controls. Check out the example provided in the SampleVirtualController class of the WDC DirectX game learning template:

Drawing a virtual button:

D2D1_ELLIPSE outerStickEllipse;
                
if (touchControl.PointerRawX > 0)
{
   // If there is analog stick input, center on the raw location.
   outerStickEllipse = D2D1::Ellipse(D2D1::Point2F(touchControl.PointerRawX, touchControl.PointerRawY), 100, 100);
}
else 
{
   // If there is no analog stick input, center on the last throw location.
   outerStickEllipse = D2D1::Ellipse(D2D1::Point2F(touchControl.PointerThrowX, touchControl.PointerThrowY), 100, 100);
}
D2D1_ELLIPSE innerStickEllipse = D2D1::Ellipse(D2D1::Point2F(touchControl.PointerThrowX, touchControl.PointerThrowY), 75, 75);

// Get opacity based on the time since the user has used the virtual analog stick.
float opacity = m_stickFadeTimer / 0.25f;

// save current opacity.
float previousOpacity = m_whiteBrush->GetOpacity();

m_whiteBrush->SetOpacity(opacity);

context->DrawEllipse(outerStickEllipse, m_whiteBrush.Get());
context->FillEllipse(innerStickEllipse, m_whiteBrush.Get());

m_whiteBrush->SetOpacity(previousOpacity);

Drawing a virtual analog stick:

// Draw a virtual analog stick                                                              
D2D1_ELLIPSE outerStickEllipse;
                
if (touchControl.PointerRawX > 0)
{
  // If there is analog stick input, center on the raw location.
  outerStickEllipse = D2D1::Ellipse(D2D1::Point2F(touchControl.PointerRawX, touchControl.PointerRawY), 100, 100);
}
else
{
   // If there is no analog stick input, center on the last throw location.
   outerStickEllipse = D2D1::Ellipse(D2D1::Point2F(touchControl.PointerThrowX, touchControl.PointerThrowY), 100, 100);
}
D2D1_ELLIPSE innerStickEllipse = D2D1::Ellipse(D2D1::Point2F(touchControl.PointerThrowX, touchControl.PointerThrowY), 75, 75);

// Get opacity based on the time since the user has used the virtual analog stick.
float opacity = m_stickFadeTimer / 0.25f;

// Save current opacity.
float previousOpacity = m_whiteBrush->GetOpacity();

m_whiteBrush->SetOpacity(opacity);

context->DrawEllipse(outerStickEllipse, m_whiteBrush.Get());
context->FillEllipse(innerStickEllipse, m_whiteBrush.Get());

m_whiteBrush->SetOpacity(previousOpacity);

Create overlays with Direct2D

Use the WDC DirectX game learning template