Tutorial: add touch controls to your DirectX game

[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 add basic touch controls to your Store C++ game with DirectX. We show you how to add touch-based controls to move a fixed-plane camera in a Direct3D environment, where dragging with a finger or stylus shifts the camera perspective.

You can incorporate these controls in games where you want the player to drag to scroll or pan over a 3D environment, such as a map or playfield. For example, in a strategy or puzzle game, you can use these controls to let the player view a game environment that is larger than the screen by panning left or right.

Note  Our code also works with mouse-based panning controls. The pointer related events are abstracted by the Windows Runtime APIs, so they can handle either touch- or mouse-based pointer events.

 

Objectives

  • Create a simple touch drag control for panning a fixed-plane camera in a DirectX game.

Set up the basic touch event infrastructure

First, we define our basic controller type, the CameraPanController, in this case. Here we define a controller as an abstract idea. the set of behaviors the user can perform.

The CameraPanController class is a regularly refreshed collection of information about the camera controller state, and provides a way for our app to obtain that information from its update loop.

using namespace Windows::UI::Core;
using namespace Windows::System;
using namespace Windows::Foundation;
using namespace Windows::Devices::Input;

// Methods to get input from the UI pointers
ref class CameraPanController
{
}

Now, let's create a header that defines the state of the camera controller, and the basic methods and event handlers that implement the camera controller interactions.

ref class CameraPanController
{
private:
    // properties of the controller object
    float3 m_position;              // the position of the camera

    // properties of the camera pan control
    bool m_panInUse;                
    uint32 m_panPointerID;          
    float2 m_panFirstDown;          
    float2 m_panPointerPosition;   
    float3 m_panCommand;            

public:

    // Methods to get input from the UI pointers
    void OnPointerPressed(
        _In_ Windows::UI::Core::CoreWindow^ sender,
        _In_ Windows::UI::Core::PointerEventArgs^ args
        );

    void OnPointerMoved(
        _In_ Windows::UI::Core::CoreWindow^ sender,
        _In_ Windows::UI::Core::PointerEventArgs^ args
        );

    void OnPointerReleased(
        _In_ Windows::UI::Core::CoreWindow^ sender,
        _In_ Windows::UI::Core::PointerEventArgs^ args
        );

    // set up the Controls supported by this controller
    void Initialize( _In_ Windows::UI::Core::CoreWindow^ window );

    // accessor to set the position of the ontroller
    void SetPosition( _In_ float3 pos );

       // accessor to set fixed "look point" of the controller
       float3 get_FixedLookPoint();

    // returns the position of the controller object
    float3 get_Position();


    void Update( Windows::UI::Core::CoreWindow ^window );

};  // class CameraPanController

The private fields contain the current state of the camera controller. Let's review them.

  • m_position is the position of the camera in the scene space. In this example, the z-coordinate value is fixed at 0. We could use a float2 to represent this value, but for the purposes of this sample and future extensibility, we use a float3. We pass this value through the get_Position property to the app itself so it can update the viewport accordingly.
  • m_panInUse is a Boolean value that indicates whether a pan operation is active; or, more specifically, whether the player is touching the screen and moving the camera.
  • m_panPointerID is a unique ID for the pointer. We won't use this in the sample, but it's a good practice to associate your controller state class with a specific pointer.
  • m_panFirstDown is the point on the screen where the player first touched the screen or clicked the mouse during the camera pan action. We use this value later to set a dead zone to prevent jitter when the screen is touched. or if the mouse shakes a little.
  • m_panPointerPosition is the point on the screen where the player has currently moved the pointer. We use it to determine what direction the player wanted to move by examining it relative to m_panFirstDown.
  • m_panCommand is the final computed command for the camera controller: up, down, left, or right. Because we are working with a camera fixed to the x-y plane, this could be a float2 value instead.

We use these 3 event handlers to update the camera controller state info.

  • OnPointerPressed is an event handler that our app calls when the players presses a finger onto the touch surface and the pointer is moved to the coordinates of the press.
  • OnPointerMovedis an event handler that our app calls when the player swipes a finger across the touch surface. It updates with the new coordinates of the drag path.
  • OnPointerReleasedis an event handler that our app calls when the player removes the pressing finger from the touch surface.

Finally, we use these methods and properties to initialize, access, and update the camera controller state information.

  • Initialize is an event handler that our app calls to initialize the controls and attach them to the CoreWindow object that describes your display window.
  • SetPosition is a method that our app calls to set the (x, y, and z) coordinates of your controls in the scene space. Note that our z-coordinate is 0 throughout this tutorial.
  • get_Position is a property that our app accesses to get the current position of the camera in the scene space. You use this property as the way of communicating the current camera position to the app.
  • get_FixedLookPoint is a property that our app accesses to get the current point toward which the controller camera is facing. In this example, it is locked normal to the x-y plane.
  • Update is a method that reads the controller state and updates the camera position. You continually call this <something> from the app's main loop to refresh the camera controller data and the camera position in the scene space.

Now you have here all the components you need to implement touch controls. You can detect when and where the touch or mouse pointer events have occurred, and what the action is. You can set the position and orientation of the camera relative to the scene space, and track the changes. Finally, you can communicate the new camera position to the calling app.

Now, let's connect these pieces together.

Create the basic touch events

The Windows Runtime event dispatcher provides 3 events we want our app to handle:

These events are implemented on the CoreWindow type. We assume that you have a CoreWindow object to work with. For more info, see How to set up your Windows Store C++ app to display a DirectX view.

As these events fire while our app is running, the handlers update the camera controller state info defined in our private fields.

First, let's populate the touch pointer event handlers. In the first event handler, OnPointerPressed, we get the x-y coordinates of the pointer from the CoreWindow that manages our display when the user touches the screen or clicks the mouse.

OnPointerPressed

void CameraPanController::OnPointerPressed(
                                           _In_ CoreWindow^ sender,
                                           _In_ PointerEventArgs^ args)
{
    // get the current pointer position
    uint32 pointerID = args->CurrentPoint->PointerId;
    float2 position = float2( args->CurrentPoint->Position.X, args->CurrentPoint->Position.Y );

    auto device = args->CurrentPoint->PointerDevice;
    auto deviceType = device->PointerDeviceType;
    

       if ( !m_panInUse )   // if no pointer is in this control yet
    {
       m_panFirstDown = position;                   // save location of initial contact
       m_panPointerPosition = position;
       m_panPointerID = pointerID;              // store the id of pointer using this control
       m_panInUse = TRUE;
    }
    
}

We use this handler to let the current CameraPanController instance know that camera controller should be treated as active by setting m_panInUse to TRUE. That way, when the app calls Update , it will use the current position data to update the viewport.

Now that we've established the base values for the camera movement when the user touches the screen or click-presses in the display window, we must determine what to do when the user either drags the screen press or moves the mouse with button pressed.

The OnPointerMoved event handler fires whenever the pointer moves, at every tick that the player drags it on the screen. We need to keep the app aware of the current location of the pointer, and this is how we do it.

OnPointerMoved

void CameraPanController::OnPointerMoved(
                                        _In_ CoreWindow ^sender,
                                        _In_ PointerEventArgs ^args)
{
    uint32 pointerID = args->CurrentPoint->PointerId;
    float2 position = float2( args->CurrentPoint->Position.X, args->CurrentPoint->Position.Y );

    m_panPointerPosition = position;
}

Finally, we need to deactivate the camera pan behavior when the player stops touching the screen. We use OnPointerReleased, which is called when PointerReleased is fired, to set m_panInUse to FALSE and turn off the camera pan movement, and set the pointer ID to 0.

OnPointerReleased

void CameraPanController::OnPointerReleased(
                                             _In_ CoreWindow ^sender,
                                             _In_ PointerEventArgs ^args)
{
    uint32 pointerID = args->CurrentPoint->PointerId;
    float2 position = float2( args->CurrentPoint->Position.X, args->CurrentPoint->Position.Y );

    m_panInUse = FALSE;
    m_panPointerID = 0;
}

Initialize the touch controls and the controller state

Let's hook the events and initialize all the basic state fields of the camera controller.

Initialize

void CameraPanController::Initialize( _In_ CoreWindow^ window )
{

    // Start recieving touch/mouse events
    window->PointerPressed += 
    ref new TypedEventHandler<CoreWindow^, PointerEventArgs^>(this, &CameraPanController::OnPointerPressed);

    window->PointerMoved += 
    ref new TypedEventHandler<CoreWindow^, PointerEventArgs^>(this, &CameraPanController::OnPointerMoved);

    window->PointerReleased += 
    ref new TypedEventHandler<CoreWindow^, PointerEventArgs^>(this, &CameraPanController::OnPointerReleased);


    // Initialize state of the controller
    m_panInUse = FALSE;             
    m_panPointerID = 0;

    //  Initialize this as it is reset every frame
    m_panCommand = float3( 0.0f, 0.0f, 0.0f );

}

Initialize takes a reference to the app's CoreWindow instance as a parameter and registers the event handlers we developed to the appropriate events on that CoreWindow.

Getting and setting the position of the camera controller

Let's define some methods to get and set the position of the camera controller in the scene space.

void CameraPanController::SetPosition( _In_ float3 pos )
{
    m_position = pos;
}

// Returns the position of the controller object
float3 CameraPanController::get_Position()
{
    return m_position;
}

float3 CameraPanController::get_FixedLookPoint()
{
    // For this sample, we don't need to use the trig functions because our
    // look point is fixed. 
    return m_position + float3( 0, 0, 1 );
}

SetPosition is a public method that we can call from our app if we need to set the camera controller position to a specific point.

get_Position is our most important public property: it's the way our app gets the current position of the camera controller in the scene space so it can update the viewport accordingly.

get_FixedLookPoint is a public property that, in this example, obtains a look point that is normal to the x-y plane. You can change this method to use the trigonometric functions, sin and cos, when calculating the x, y, and z coordinate values if you want to create more oblique angles for the fixed camera.

Updating the camera controller state information

Now, we perform our calculations that convert the pointer coordinate info tracked in m_panPointerPosition into new coordinate info respective of our 3D scene space. Our app typically calls this method is called every time we refresh the main app loop, and in it we compute the new position info we want to pass to the app to update the view matrix before projection into the viewport.


void CameraPanController::Update( CoreWindow ^window )
{
    if ( m_panInUse )
    {
        float2 pointerDelta = m_panPointerPosition - m_panFirstDown;

        if ( pointerDelta.x > 16.0f )        // leave 32 pixel-wide dead spot for being still
            m_panCommand.x += 1.0f;
        else
            if ( pointerDelta.x < -16.0f )
                m_panCommand.x += -1.0f;

        if ( pointerDelta.y > 16.0f )        
            m_panCommand.y += 1.0f;
        else
            if (pointerDelta.y < -16.0f )
                m_panCommand.y += -1.0f;
    }

       float3 command = m_panCommand;
   
    // our velocity is based on the command
    float3 Velocity;
    Velocity.x =  command.x;
    Velocity.y =  command.y;
    Velocity.z =  0.0f;

    // integrate
    m_position = m_position + Velocity;

    // Clear the movement input accumulator for use during next frame
    m_panCommand = float3( 0.0f, 0.0f, 0.0f );

}

Because we don't want touch or mouse jitter to make our camera panning jerky, we set a dead zone around the pointer with a diameter of 32 pixels. We also have a velocity value, which in this case is 1:1 with the pixel traversal of the pointer past the dead zone. You can adjust this behavior to slow down or speed up the rate of movement.

Updating the view matrix with the new camera position

We can now obtain a scene space coordinate that our camera is focused on, and which is updated whenever you tell your app to do so (every 60 seconds in the main app loop, for example). This pseudocode suggests the calling behavior you can implement:

 myCameraPanController->Update( m_window ); 

 // update the view matrix based on the camera position
 myCamera->MyMethodToComputeViewMatrix(
        myController->get_Position(),        // the position in the 3D scene space
        myController->get_FixedLookPoint(),      // the point in the space we are looking at
        float3( 0, 1, 0 )                   // the axis that is "up" in our space
        );  

Congratulations! You've implemented a simple set of camera panning touch controls in Windows Store app using DirectX with C++