游戏的触摸控件

了解如何使用 DirectX 将基本触摸控件添加到通用 Windows 平台(UWP)C++游戏。 我们介绍如何添加基于触摸的控件,以在 Direct3D 环境中移动固定平面相机,其中用手指或触笔拖动会移动相机透视。

你可以在需要玩家拖动以在 3D 环境(例如地图或游戏区域)中滚动或平移的游戏中使用这些控件。 例如,在策略或谜题游戏中,可以使用这些控件让玩家通过向左或向右平移来查看大于屏幕的游戏环境。

注释 我们的代码也适用于基于鼠标的平移控件。 指针相关事件由 Windows 运行时 API 抽象化,因此它们可以处理基于触摸或鼠标的指针事件。

 

目标

  • 在 DirectX 游戏中创建用于平移固定平面相机的简单触摸拖动控件。

设置基本触摸事件基础结构

首先,我们定义基本控制器类型(CameraPanController,在本例中)。 在这里,我们将控制器定义为抽象的想法,即用户可以执行的行为集。

CameraPanController 类是有关相机控制器状态信息的定期刷新集合,并为应用从更新循环获取该信息提供了一种方法。

using namespace Windows::UI::Core;
using namespace Windows::System;
using namespace Windows::Foundation;
using namespace Windows::Devices::Input;
#include <directxmath.h>

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

现在,让我们创建一个标头来定义相机控制器的状态,以及实现相机控制器交互的基本方法和事件处理程序。

ref class CameraPanController
{
private:
    // Properties of the controller object
    DirectX::XMFLOAT3 m_position;               // the position of the camera

    // Properties of the camera pan control
    bool m_panInUse;                
    uint32 m_panPointerID;          
    DirectX::XMFLOAT2 m_panFirstDown;           
    DirectX::XMFLOAT2 m_panPointerPosition;   
    DirectX::XMFLOAT3 m_panCommand;         
    
internal:
    // Accessor to set the position of the controller
    void SetPosition( _In_ DirectX::XMFLOAT3 pos );

       // Accessor to set the fixed "look point" of the controller
       DirectX::XMFLOAT3 get_FixedLookPoint();

    // Returns the position of the controller object
    DirectX::XMFLOAT3 get_Position();

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 );

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

};  // Class CameraPanController

专用字段包含相机控制器的当前状态。 让我们回顾一下它们。

  • m_position 是相机在场景空间中的位置。 在此示例中,z 坐标值固定在 0。 可以使用 DirectX::XMFLOAT2 来表示此值,但为了此示例和将来的扩展性,我们使用 DirectX::XMFLOAT3。 我们将此值通过 get_Position 属性传递给应用本身,以便它可以相应地更新视区。
  • m_panInUse 是一个布尔值,指示平移操作是否处于活动状态;或者,更具体地说,玩家是否正在触摸屏幕和移动相机。
  • m_panPointerID 是指针的唯一编号。 我们不会在示例中使用此功能,但最好将控制器状态类与特定指针相关联。
  • m_panFirstDown 是玩家首次在屏幕上触摸或在相机平移的操作期间单击鼠标的点。 我们稍后使用此值设置死区,以防止屏幕触摸时抖动,或者鼠标摇动一点。
  • m_panPointerPosition 是指玩家当前在屏幕上移动指针的位置。 我们通过检查它与 m_panFirstDown的相对位置,来确定玩家希望移动的方向。
  • m_panCommand 是相机控制器的最终计算命令:向上、向下、向左或向右。 由于我们使用的是固定到 x-y 平面的相机,因此这可能是 DirectX::XMFLOAT2 值。

我们使用这 3 个事件处理程序更新相机控制器状态信息。

  • OnPointerPressed 是一个事件处理程序,当玩家将手指按到触摸屏上且指针移动到按压位置时,我们的应用会调用此事件处理程序。
  • OnPointerMoved 是当玩家在触摸图面上轻扫手指时应用调用的事件处理程序。 它使用拖动路径的新坐标进行更新。
  • OnPointerReleased 是一个事件处理程序,当玩家从触摸屏上抬起按下的手指时,应用程序会调用它。

最后,我们使用这些方法和属性来初始化、访问和更新相机控制器状态信息。

  • Initialize 是应用调用的事件处理程序,用于初始化控件并将其附加到描述显示窗口的 CoreWindow 对象。
  • SetPosition 是我们应用程序调用的方法,用于设置场景空间中控件的 (x、y 和 z) 坐标。 请注意,本教程中的 z 坐标为 0。
  • get_Position 是应用访问的一个属性,用于获取相机在场景空间中的当前位置。 使用此属性作为将当前相机位置传达给应用的方式。
  • get_FixedLookPoint 是我们的应用访问的一个属性,用于获取控制器中相机所朝向的当前点。 在此示例中,它被锁定为 x-y 平面的正常状态。
  • 更新 是读取控制器状态并更新相机位置的方法。 你在应用程序的主循环中不断调用<某个>,以刷新相机控制器数据和场景空间中的相机位置。

现在,你已拥有实现触摸控件所需的所有组件。 可以检测触摸或鼠标指针事件发生的时间和位置,以及操作是什么。 你可以设置相机相对于场景空间的位置和方向,并跟踪更改。 最后,可以将新的相机位置传达给呼叫应用。

现在,让我们将这些部分连接在一起。

创建基本触摸事件

Windows 运行时事件调度程序提供 3 个我们希望应用处理的事件:

这些事件在 CoreWindow 类型上实现。 我们假设你有一个供操作的 CoreWindow 对象。 有关详细信息,请参阅 如何设置 UWP C++ 应用以显示 DirectX 视图

当应用运行时触发这些事件时,处理程序将更新专用字段中定义的相机控制器状态信息。

首先,让我们设置触摸指针事件处理程序。 在第一个事件处理程序 OnPointerPressed中,当用户触摸屏幕或单击鼠标时,我们从 CoreWindow 获取管理显示的指针 x-y 坐标。

按下指针

void CameraPanController::OnPointerPressed(
                                           _In_ CoreWindow^ sender,
                                           _In_ PointerEventArgs^ args)
{
    // Get the current pointer position.
    uint32 pointerID = args->CurrentPoint->PointerId;
    DirectX::XMFLOAT2 position = DirectX::XMFLOAT2( 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 the ___location of the initial contact.
       m_panPointerPosition = position;
       m_panPointerID = pointerID;              // Store the id of the pointer using this control.
       m_panInUse = TRUE;
    }
    
}

我们使用此处理程序让当前 CameraPanController 实例知道相机控制器应通过将 m_panInUse 设置为 TRUE 来被视为活动状态。 这样,当应用调用 更新 时,它将使用当前位置数据来更新视区。

现在,我们已经为用户触摸屏幕或者在显示窗口中点击并按住的情况下建立了相机移动的基本值,因此,必须确定当用户拖动按住的屏幕或者在按钮按下的情况下移动鼠标时要执行的操作。

每当指针移动时,OnPointerMoved 事件处理程序都会触发,每次在玩家在屏幕上拖动它时,都会触发。 我们需要让应用了解指针的当前位置,这就是我们执行此操作的方式。

指针移动事件

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

    m_panPointerPosition = position;
}

最后,当玩家停止触摸屏幕时,我们需要停用相机平移行为。 我们使用 OnPointerReleased(在触发 PointerReleased 时调用),将 m_panInUse 设为 FALSE,关闭相机的平移运动,并将指针 ID 设为 0。

指针释放

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

    m_panInUse = FALSE;
    m_panPointerID = 0;
}

初始化触摸控件和控制器状态

让我们挂载事件,并初始化相机控制器的所有基本状态变量。

初始化

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

    // Start receiving 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 the state of the controller.
    m_panInUse = FALSE;             
    m_panPointerID = 0;

    //  Initialize this as it is reset on every frame.
    m_panCommand = DirectX::XMFLOAT3( 0.0f, 0.0f, 0.0f );

}

Initialize 采用应用的 CoreWindow 实例的引用作为参数,并将我们开发的事件处理程序注册到该 CoreWindow上的相应事件。

获取和设置相机控制器的位置

让我们定义一些方法来获取和设置相机控制器在场景空间中的位置。

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

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

DirectX::XMFLOAT3 CameraPanController::get_FixedLookPoint()
{
    // For this sample, we don't need to use the trig functions because our
    // look point is fixed. 
    DirectX::XMFLOAT3 result= m_position;
    result.z += 1.0f;
    return result;    

}

SetPosition 是一种公共方法,如果需要将相机控制器位置设置为特定点,可以从应用调用。

get_Position 是我们最重要的公共属性:这是我们的应用程序在场景空间中获取相机控制器当前位置的方式,用于相应地更新视区。

get_FixedLookPoint 是一个公有属性,在这个示例中可获得一个与 x-y 平面垂直的观察点。 如果要为固定相机创建更多倾斜角度,可以在计算 x、y 和 z 坐标值时更改此方法以使用三角函数 sin 和 cos。

更新相机控制器状态信息

现在,我们执行计算,将 m_panPointerPosition 中所跟踪的指针坐标信息转换为与我们的 3D 场景空间对应的新坐标信息。 每次刷新主应用循环时,我们的应用都会调用此方法。 在这里,我们计算要传递给应用的新位置信息,该应用在投影到显示窗口之前用于更新视图矩阵。


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

        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;
    }

       DirectX::XMFLOAT3 command = m_panCommand;
   
    // Our velocity is based on the command.
    DirectX::XMFLOAT3 Velocity;
    Velocity.x =  command.x;
    Velocity.y =  command.y;
    Velocity.z =  0.0f;

    // Integrate
    m_position.x = m_position.x + Velocity.x;
    m_position.y = m_position.y + Velocity.y;
    m_position.z = m_position.z + Velocity.z;

    // Clear the movement input accumulator for use during the next frame.
    m_panCommand = DirectX::XMFLOAT3( 0.0f, 0.0f, 0.0f );

}

由于我们不希望触摸或鼠标抖动导致相机移动不流畅,因此我们在指针周围设置了一个直径为32像素的死区。 我们还有一个速度值,在本例中为 1:1,指针的像素遍历超过死区。 可以调整此行为以减缓或加快移动速度。

使用新的相机位置更新视图矩阵

现在,我们可以获取相机所关注的场景空间坐标,并且每当告知应用执行此操作时都会更新该坐标(例如,主应用循环中的每 60 秒)。 此伪代码建议可以实现的调用行为:

 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.
        DirectX::XMFLOAT3( 0, 1, 0 )                    // The axis that is "up" in our space.
        );  

祝贺! 你已在游戏中实现了一组简单的相机平移触摸控件。