Hinzufügen von Steuerelementen

Hinweis

Dieses Thema ist Teil der Tutorialreihe Erstellen eines einfachen Universelle Windows-Plattform (UWP) mit DirectX. Das Thema unter diesem Link legt den Kontext für die Reihe fest.

[ Für UWP-Apps auf Windows 10 aktualisiert. Artikel zu Windows 8.x finden Sie im Archiv ]

Ein gutes Universelle Windows-Plattform-Spiel (UWP) unterstützt eine Vielzahl von Schnittstellen. Ein potenzieller Spieler kann Windows 10 auf einem Tablet ohne physische Tasten, einem PC mit angeschlossenem Xbox-Controller oder dem neuesten Desktop-Gaming-Rig mit einer leistungsstarken Maus und Einer Gaming-Tastatur haben. In unserem Spiel werden die Steuerelemente in der MoveLookController-Klasse implementiert. Diese Klasse aggregiert alle drei Eingabetypen (Maus und Tastatur, Toucheingabe und Gamepad) in einem einzelnen Controller. Das Endergebnis ist ein Ego-Shooter, der Genrestandard-Move-Look-Steuerelemente verwendet, die mit mehreren Geräten funktionieren.

Hinweis

Weitere Informationen zu Steuerelementen finden Sie unter Move-Look-Steuerelemente für Spiele und Touchsteuerelemente für Spiele.

Ziel

An diesem Punkt haben wir ein Spiel, das gerendert wird, aber wir können unseren Spieler nicht bewegen oder die Ziele schießen. Wir werden einen Blick darauf werfen, wie unser Spiel die Bewegungssteuerungen des Ego-Shooters für die folgenden Eingabetypen in unserem UWP DirectX-Spiel implementiert.

  • Maus und Tastatur
  • Touch
  • Gamepad

Hinweis

Wenn Sie den neuesten Spielcode für dieses Beispiel nicht heruntergeladen haben, wechseln Sie zu Direct3D-Beispielspiel. Dieses Beispiel ist Teil einer großen Sammlung von UWP-Featurebeispielen. Anweisungen zum Herunterladen des Beispiels finden Sie unter Beispielanwendungen für die Windows-Entwicklung.

Allgemeine Verhaltensweisen von Steuerelementen

Die Implementierung von Touchsteuerelementen und Maus-/Tastatursteuerelementen ist im Grunde sehr ähnlich. In einer UWP-App ist ein Zeiger einfach ein Punkt auf dem Bildschirm. Sie können ihn bewegen, indem Sie die Maus oder den Finger auf dem Touchscreen bewegen. Folglich können Sie einen einzelnen Satz von Ereignissen registrieren und müssen sich keine Gedanken darüber machen, ob der Spieler eine Maus oder einen Touchscreen zum Bewegen und Betätigen des Zeigers verwendet.

Wenn die MoveLookController-Klasse im Beispielspiel initialisiert wird, wird sie für vier zeigerspezifische Ereignisse und ein mausspezifisches Ereignis registriert:

Ereignis BESCHREIBUNG
CoreWindow::P ointerPressed Die linke oder rechte Maustaste wurde gedrückt (und gedrückt gehalten), oder der Touchscreen wurde berührt.
CoreWindow::P ointerMoved Die Maus wurde bewegt, oder eine Zieh-Aktion wurde auf dem Touchscreen ausgeführt.
CoreWindow::P ointerReleased Die linke Maustaste wurde losgelassen, oder das Objekt, das den Touchscreen berührt, wurde angehoben.
CoreWindow::P ointerExited Der Zeiger wurde aus dem Hauptfenster bewegt.
Windows::D evices::Input::MouseMoved Die Maus wurde über eine bestimmte Distanz bewegt. Beachten Sie, dass wir nur an Deltawerten der Mausbewegung und nicht an der aktuellen X-Y-Position interessiert sind.

Diese Ereignishandler werden so festgelegt, dass sie mit dem Lauschen auf Benutzereingaben beginnen, sobald der MoveLookController im Anwendungsfenster initialisiert wird.

void MoveLookController::InitWindow(_In_ CoreWindow const& window)
{
    ResetState();

    window.PointerPressed({ this, &MoveLookController::OnPointerPressed });

    window.PointerMoved({ this, &MoveLookController::OnPointerMoved });

    window.PointerReleased({ this, &MoveLookController::OnPointerReleased });

    window.PointerExited({ this, &MoveLookController::OnPointerExited });

    ...

    // There is a separate handler for mouse-only relative mouse movement events.
    MouseDevice::GetForCurrentView().MouseMoved({ this, &MoveLookController::OnMouseMoved });

    ...
}

Der vollständige Code für InitWindow kann auf GitHub angezeigt werden.

Um zu bestimmen, wann das Spiel auf bestimmte Eingaben lauschen soll, weist die MoveLookController-Klasse unabhängig vom Controllertyp drei controllerspezifische Zustände auf:

State BESCHREIBUNG
None Dies ist der Initialisierungszustand für den Controller. Alle Eingaben werden ignoriert, da das Spiel keine Controllereingaben erwartet.
WaitForInput Der Controller wartet darauf, dass der Spieler eine Nachricht aus dem Spiel bestätigt, indem er entweder einen linken Mausklick, ein Touchereignis oder die Menüschaltfläche auf einem Gamepad verwendet.
Aktiv Der Controller befindet sich im aktiven Spielmodus.

WaitForInput-Status und Anhalten des Spiels

Das Spiel wechselt in den WaitForInput-Zustand , wenn das Spiel angehalten wurde. Dies geschieht, wenn der Spieler den Zeiger außerhalb des Standard Fensters des Spiels bewegt oder die Pausenschaltfläche (die P-Taste oder die Startschaltfläche des Gamepads) drückt. Der MoveLookController registriert den Druck und informiert die Spielschleife, wenn die IsPauseRequested-Methode aufgerufen wird. Wenn IsPauseRequested an diesem Punkt true zurückgibt, ruft die Spielschleife WaitForPress auf dem MoveLookController auf, um den Controller in den WaitForInput-Zustand zu verschieben.

Sobald sich der WaitForInput-Zustand befindet, beendet das Spiel die Verarbeitung fast aller Spieleingabeereignisse, bis es in den Status Aktiv zurückkehrt. Die Ausnahme ist die Pausenschaltfläche, bei der das Spiel durch drücken zurück in den aktiven Zustand wechselt. Anders als die Pausenschaltfläche muss der Spieler ein Menüelement auswählen, damit das Spiel wieder in den Status Aktiv wechselt.

Der Status "Aktiv"

Während des Status "Aktiv" verarbeitet der MoveLookController-instance Ereignisse von allen aktivierten Eingabegeräten und interpretiert die Absichten des Players. Infolgedessen aktualisiert es die Geschwindigkeit und die Blickrichtung der Ansicht des Spielers und teilt die aktualisierten Daten mit dem Spiel, nachdem Update aus der Spielschleife aufgerufen wurde.

Alle Zeigereingaben werden im Status Aktiv nachverfolgt, wobei verschiedene Zeiger-IDs unterschiedlichen Zeigeraktionen entsprechen. Wenn ein PointerPressed-Ereignis empfangen wird, ruft die MoveLookController-Instanz den vom Fenster erstellten Wert der Zeiger-ID ab. Die Zeiger-ID stellt einen bestimmten Eingabetyp dar. Bei einem Multitouchgerät sind etwa gleichzeitig mehrere unterschiedliche aktive Eingaben möglich. Die IDs werden verwendet, um den vom Spieler verwendeten Eingabetyp nachzuverfolgen. Wenn sich ein Ereignis im Move-Rechteck des Touchscreens befindet, wird eine Zeiger-ID zugewiesen, um alle Zeigerereignisse im Verschieben-Rechteck nachzuverfolgen. Andere Zeigerereignisse im Schießrechteck werden separat mit einer anderen Zeiger-ID nachverfolgt.

Hinweis

Eingaben von der Maus und dem rechten Fingerabdruck eines Gamepads verfügen auch über IDs, die separat behandelt werden.

Nachdem die Zeigerereignisse einer bestimmten Spielaktion zugeordnet wurden, müssen die Daten aktualisiert werden, die das MoveLookController-Objekt an die Hauptspielschleife weitergibt.

Beim Aufruf verarbeitet die Update-Methode im Beispielspiel die Eingabe und aktualisiert die Geschwindigkeits- und Lookrichtungsvariablen (m_velocity und m_lookdirection), die die Spielschleife dann durch Aufrufen der öffentlichen Velocity - und LookDirection-Methoden abruft.

Hinweis

Weitere Details zur Update-Methode finden Sie weiter unten auf dieser Seite.

Die Spielschleife kann prüfen, ob der Spieler schießt, indem sie die IsFiring-Methode der MoveLookController-Instanz aufruft. Die MoveLookController-Instanz überprüft, ob der Spieler über einen der drei Eingabetypen die Schießtaste betätigt hat.

bool MoveLookController::IsFiring()
{
    if (m_state == MoveLookControllerState::Active)
    {
        if (m_autoFire)
        {
            return (m_fireInUse || (m_mouseInUse && m_mouseLeftInUse) || PollingFireInUse());
        }
        else
        {
            if (m_firePressed)
            {
                m_firePressed = false;
                return true;
            }
        }
    }
    return false;
}

Nun sehen wir uns die Implementierung der drei Steuerelementtypen etwas ausführlicher an.

Hinzufügen relativer Maussteuerelemente

Wenn Mausbewegungen erkannt werden, möchten wir diese Bewegung verwenden, um die neue Tonhöhe und das Gieren der Kamera zu bestimmen. Hierzu implementieren wir relative Maussteuerungen, bei denen nicht die absoluten x-y-Pixelkoordinaten der Bewegung aufgezeichnet werden, sondern die relative Distanz der Mausbewegung (also das Delta zwischen Start und Ende der Bewegung) erfasst wird.

Dazu ermitteln wir die Änderung der X-Koordinate (horizontale Bewegung) und der Y-Koordinate (vertikale Bewegung), indem wir die Felder MouseDelta::X und MouseDelta::Y für das vom MouseMoved-Ereignis zurückgegebene Windows::Device::Input::MouseEventArgs::MouseDelta-Argumentobjekt überprüfen.

void MoveLookController::OnMouseMoved(
    _In_ MouseDevice const& /* mouseDevice */,
    _In_ MouseEventArgs const& args
    )
{
    // Handle Mouse Input via dedicated relative movement handler.

    switch (m_state)
    {
    case MoveLookControllerState::Active:
        XMFLOAT2 mouseDelta;
        mouseDelta.x = static_cast<float>(args.MouseDelta().X);
        mouseDelta.y = static_cast<float>(args.MouseDelta().Y);

        XMFLOAT2 rotationDelta;
        // Scale for control sensitivity.
        rotationDelta.x = mouseDelta.x * MoveLookConstants::RotationGain;
        rotationDelta.y = mouseDelta.y * MoveLookConstants::RotationGain;

        // Update our orientation based on the command.
        m_pitch -= rotationDelta.y;
        m_yaw += rotationDelta.x;

        // Limit pitch to straight up or straight down.
        float limit = XM_PI / 2.0f - 0.01f;
        m_pitch = __max(-limit, m_pitch);
        m_pitch = __min(+limit, m_pitch);

        // Keep longitude in sane range by wrapping.
        if (m_yaw > XM_PI)
        {
            m_yaw -= XM_PI * 2.0f;
        }
        else if (m_yaw < -XM_PI)
        {
            m_yaw += XM_PI * 2.0f;
        }
        break;
    }
}

Hinzufügen von Touchunterstützung

Touchsteuerelemente eignen sich hervorragend für die Unterstützung von Benutzern mit Tablets. Dieses Spiel sammelt Toucheingaben, indem bestimmte Bereiche des Bildschirms abgezont werden, wobei jeder an bestimmten In-Game-Aktionen ausgerichtet ist. Die Toucheingabe dieses Spiels verwendet drei Zonen.

Touchlayout verschieben

Die folgenden Befehle fassen unser Verhalten der Touchsteuerung zusammen. Benutzereingabe | Aktion :------- | :-------- Rechteck verschieben | Die Toucheingabe wird in einen virtuellen Joystick umgewandelt, bei dem die vertikale Bewegung in Vorwärts-/Rückwärtspositionsbewegung übersetzt und horizontale Bewegung in links/rechts übersetzt wird. Feuerrechteck | Feuern Sie eine Kugel ab. Berühren außerhalb des Bewegungs- und Feuerrechtecks | Ändern Sie die Drehung (Tonhöhe und Gier) der Kameraansicht.

Die MoveLookController-Instanz überprüft anhand der Zeiger-ID, wo das Ereignis aufgetreten ist, und führt eine der folgenden Aktionen aus:

  • Wenn das PointerMoved-Ereignis im Bewegungs- oder Schießrechteck aufgetreten ist, wird die Zeigerposition für den Controller aktualisiert.
  • Wenn das PointerMoved-Ereignis an einer anderen Stelle des Bildschirms (definiert als Blicksteuerung) aufgetreten ist, werden die Änderungen des Nick- und Gierwinkels des Blickrichtungsvektors berechnet.

Nachdem wir unsere Touch-Steuerelemente implementiert haben, zeigen die Rechtecke, die wir zuvor mithilfe von Direct2D gezeichnet haben, den Spielern an, wo sich die Bewegungs-, Feuer- und Lookzonen befinden.

Toucheingabesteuerelemente

Sehen wir uns nun an, wie wir die einzelnen Steuerelemente implementieren.

Verschieben und Auslösen des Controllers

Das Rechteck des Bewegungscontrollers im unteren linken Quadranten des Bildschirms wird als Richtungspad verwendet. Wenn Sie den Daumen in diesem Bereich nach links und rechts bewegen, bewegt sich der Spieler nach links und rechts, während nach oben und unten die Kamera vorwärts und rückwärts bewegt wird. Nachdem Sie dies eingerichtet haben, wird durch Tippen auf den Feuercontroller im unteren rechten Quadranten des Bildschirms eine Kugel ausgelöst.

Die Methoden SetMoveRect und SetFireRect erstellen unsere Eingaberechtecke, wobei zwei 2D-Vektoren verwendet werden, um die Positionen der oberen linken und unteren rechten Ecke jedes Rechtecks auf dem Bildschirm anzugeben.

Die Parameter werden dann m_fireUpperLeft und m_fireLowerRight zugewiesen, die uns helfen zu bestimmen, ob der Benutzer innerhalb des Rechtecks berührt.

m_fireUpperLeft = upperLeft;
m_fireLowerRight = lowerRight;

Wenn die Größe des Bildschirms geändert wird, werden diese Rechtecke auf die gewünschte Größe neu gezeichnet.

Nachdem wir nun unsere Steuerelemente in zonengesteuert haben, ist es an der Zeit, zu bestimmen, wann ein Benutzer sie tatsächlich verwendet. Dazu richten wir einige Ereignishandler in der MoveLookController::InitWindow-Methode ein, wenn der Benutzer den Zeiger drückt, verschiebt oder freigibt.

window.PointerPressed({ this, &MoveLookController::OnPointerPressed });

window.PointerMoved({ this, &MoveLookController::OnPointerMoved });

window.PointerReleased({ this, &MoveLookController::OnPointerReleased });

Wir bestimmen zunächst, was passiert, wenn der Benutzer zum ersten Mal mit der OnPointerPressed-Methode innerhalb der Rechtecke zum Verschieben oder Auslösen drückt. Hier überprüfen wir, wo sie ein Steuerelement berühren und ob sich bereits ein Zeiger in diesem Controller befindet. Wenn dies der erste Finger ist, der das bestimmte Steuerelement berührt, führen wir die folgenden Schritte aus.

  • Speichern Sie die Position des Touchdowns in m_moveFirstDown oder m_fireFirstDown als 2D-Vektor.
  • Weisen Sie die Zeiger-ID m_movePointerID oder m_firePointerID zu.
  • Legen Sie das richtige InUse-Flag (m_moveInUse oder m_fireInUse) auf fest true , da wir jetzt über einen aktiven Zeiger für dieses Steuerelement verfügen.
PointerPoint point = args.CurrentPoint();
uint32_t pointerID = point.PointerId();
Point pointerPosition = point.Position();
PointerPointProperties pointProperties = point.Properties();
auto pointerDevice = point.PointerDevice();
auto pointerDeviceType = pointerDevice.PointerDeviceType();

XMFLOAT2 position = XMFLOAT2(pointerPosition.X, pointerPosition.Y);

...
case MoveLookControllerState::Active:
    switch (pointerDeviceType)
    {
    case winrt::Windows::Devices::Input::PointerDeviceType::Touch:
        // Check to see if this pointer is in the move control.
        if (position.x > m_moveUpperLeft.x &&
            position.x < m_moveLowerRight.x &&
            position.y > m_moveUpperLeft.y &&
            position.y < m_moveLowerRight.y)
        {
            // If no pointer is in this control yet.
            if (!m_moveInUse)
            {
                // Process a DPad touch down event.
                // Save the location of the initial contact
                m_moveFirstDown = position;
                // Store the pointer using this control
                m_movePointerID = pointerID;
                // Set InUse flag to signal there is an active move pointer
                m_moveInUse = true;
            }
        }
        // Check to see if this pointer is in the fire control.
        else if (position.x > m_fireUpperLeft.x &&
            position.x < m_fireLowerRight.x &&
            position.y > m_fireUpperLeft.y &&
            position.y < m_fireLowerRight.y)
        {
            if (!m_fireInUse)
            {
                // Save the location of the initial contact
                m_fireLastPoint = position;
                // Store the pointer using this control
                m_firePointerID = pointerID;
                // Set InUse flag to signal there is an active fire pointer
                m_fireInUse = true;
                ...
            }
        }
        ...

Nachdem wir nun ermittelt haben, ob der Benutzer ein Bewegungs- oder Feuersteuerelement berührt, sehen wir, ob der Spieler mit dem gedrückten Finger Bewegungen ausführt. Mit der MoveLookController::OnPointerMoved-Methode überprüfen wir, welcher Zeiger verschoben wurde, und speichern dann die neue Position als 2D-Vektor.

PointerPoint point = args.CurrentPoint();
uint32_t pointerID = point.PointerId();
Point pointerPosition = point.Position();
PointerPointProperties pointProperties = point.Properties();
auto pointerDevice = point.PointerDevice();

// convert to allow math
XMFLOAT2 position = XMFLOAT2(pointerPosition.X, pointerPosition.Y);

switch (m_state)
{
case MoveLookControllerState::Active:
    // Decide which control this pointer is operating.

    // Move control
    if (pointerID == m_movePointerID)
    {
        // Save the current position.
        m_movePointerPosition = position;
    }
    // Look control
    else if (pointerID == m_lookPointerID)
    {
        ...
    }
    // Fire control
    else if (pointerID == m_firePointerID)
    {
        m_fireLastPoint = position;
    }
    ...

Sobald der Benutzer seine Gesten innerhalb der Steuerelemente ausgeführt hat, wird der Zeiger los. Mithilfe der MoveLookController::OnPointerReleased-Methode ermitteln wir, welcher Zeiger freigegeben wurde, und führen eine Reihe von Zurücksetzungen durch.

Wenn das Verschiebungssteuerelement freigegeben wurde, führen wir die folgenden Schritte aus.

  • Legen Sie die Geschwindigkeit des Spielers auf 0 in alle Richtungen fest, um zu verhindern, dass er sich im Spiel bewegt.
  • Wechseln Sie m_moveInUse zu, false da der Benutzer den Bewegungscontroller nicht mehr berührt.
  • Legen Sie die Verschiebungszeiger-ID auf fest 0 , da im Bewegungscontroller kein Zeiger mehr vorhanden ist.
if (pointerID == m_movePointerID)
{
    // Stop on release.
    m_velocity = XMFLOAT3(0, 0, 0);
    m_moveInUse = false;
    m_movePointerID = 0;
}

Wenn die Feuersteuerung freigegeben wurde, können wir nur die m_fireInUse Flag auf false und die Feuerzeiger-ID auf 0 ändern, da in der Feuersteuerung kein Zeiger mehr vorhanden ist.

else if (pointerID == m_firePointerID)
{
    m_fireInUse = false;
    m_firePointerID = 0;
}

Look-Controller

Zeigerereignisse von Touchgeräten für die nicht verwendeten Bereiche des Bildschirms werden als Look-Controller behandelt. Wenn Sie mit dem Finger um diese Zone gleiten, ändern Sich die Tonhöhe und das Gähnen (Drehung) der Spielerkamera.

Wenn das MoveLookController::OnPointerPressed-Ereignis auf einem Touchgerät in dieser Region ausgelöst wird und der Spielzustand auf Aktiv festgelegt ist, wird ihm eine Zeiger-ID zugewiesen.

// If no pointer is in this control yet.
if (!m_lookInUse)
{
    // Save point for later move.
    m_lookLastPoint = position;
    // Store the pointer using this control.
    m_lookPointerID = pointerID;
    // These are for smoothing.
    m_lookLastDelta.x = m_lookLastDelta.y = 0;
    m_lookInUse = true;
}

Hier weist der MoveLookController die Zeiger-ID für den Zeiger, der das Ereignis ausgelöst hat, einer bestimmten Variablen zu, die dem Lookbereich entspricht. Im Falle einer Berührung im Lookbereich wird die m_lookPointerID Variable auf die Zeiger-ID festgelegt, die das Ereignis ausgelöst hat. Eine boolesche Variable , m_lookInUse, wird ebenfalls festgelegt, um anzugeben, dass das Steuerelement noch nicht freigegeben wurde.

Sehen wir uns nun an, wie das Beispielspiel das PointerMoved-Touchscreenereignis behandelt.

Innerhalb der MoveLookController::OnPointerMoved-Methode wird überprüft, welche Art von Zeiger-ID dem Ereignis zugewiesen wurde. Wenn es sich um m_lookPointerID handelt, berechnen wir die Änderung der Position des Zeigers. Anschließend verwenden wir dieses Delta, um zu berechnen, wie stark sich die Drehung ändern soll. Schließlich sind wir an einem Punkt, an dem wir die m_pitch und m_yaw aktualisieren können, die im Spiel verwendet werden sollen, um die Spielerrotation zu ändern.

// This is the look pointer.
else if (pointerID == m_lookPointerID)
{
    // Look control.
    XMFLOAT2 pointerDelta;
    // How far did the pointer move?
    pointerDelta.x = position.x - m_lookLastPoint.x;
    pointerDelta.y = position.y - m_lookLastPoint.y;

    XMFLOAT2 rotationDelta;
    // Scale for control sensitivity.
    rotationDelta.x = pointerDelta.x * MoveLookConstants::RotationGain;
    rotationDelta.y = pointerDelta.y * MoveLookConstants::RotationGain;
    // Save for next time through.
    m_lookLastPoint = position;

    // Update our orientation based on the command.
    m_pitch -= rotationDelta.y;
    m_yaw += rotationDelta.x;

    // Limit pitch to straight up or straight down.
    float limit = XM_PI / 2.0f - 0.01f;
    m_pitch = __max(-limit, m_pitch);
    m_pitch = __min(+limit, m_pitch);
    ...
}

Der letzte Teil, den wir uns ansehen, ist, wie das Beispielspiel das PointerReleased-Touchscreenereignis behandelt. Nachdem der Benutzer die Fingereingabegeste beendet und den Finger vom Bildschirm entfernt hat, wird MoveLookController::OnPointerReleased initiiert. Wenn die ID des Zeigers, der das PointerReleased-Ereignis ausgelöst hat, die ID des zuvor aufgezeichneten Bewegungszeigers ist, legt der MoveLookController die Geschwindigkeit auf fest 0 , da der Spieler den Blickbereich nicht mehr berührt.

else if (pointerID == m_lookPointerID)
{
    m_lookInUse = false;
    m_lookPointerID = 0;
}

Hinzufügen von Maus- und Tastaturunterstützung

Dieses Spiel verfügt über das folgende Steuerelementlayout für Tastatur und Maus.

Benutzereingabe Action
W Spieler vorwärts bewegen
A Spieler nach links verschieben
E Spieler rückwärts verschieben
D Spieler nach rechts verschieben
X Ansicht nach oben verschieben
Leertaste Ansicht nach unten verschieben
P Anhalten des Spiels
Mausbewegung Ändern der Drehung (Tonhöhe und Gieren) der Kameraansicht
Linke Maustaste Auslösen einer Kugel

Um die Tastatur zu verwenden, registriert das Beispielspiel zwei neue Ereignisse, CoreWindow::KeyUp und CoreWindow::KeyDown, innerhalb der MoveLookController::InitWindow-Methode . Diese Ereignisse behandeln das Drücken und Freigeben einer Taste.

window.KeyDown({ this, &MoveLookController::OnKeyDown });

window.KeyUp({ this, &MoveLookController::OnKeyUp });

Die Maus wird etwas anders behandelt als die Touchsteuerelemente, obwohl sie einen Zeiger verwendet. Zur Ausrichtung an unserem Steuerelementlayout dreht moveLookController die Kamera, wenn die Maus bewegt wird, und wird ausgelöst, wenn die linke Maustaste gedrückt wird.

Dies wird in der OnPointerPressed-Methode des MoveLookControllers behandelt.

In dieser Methode wird überprüft, welcher Zeigergerättyp mit der Windows::Devices::Input::PointerDeviceType Enumeration verwendet wird. Wenn das Spiel Aktiv ist und PointerDeviceType nicht Touch ist, wird davon ausgegangen, dass es sich um eine Mauseingabe handelt.

case MoveLookControllerState::Active:
    switch (pointerDeviceType)
    {
    case winrt::Windows::Devices::Input::PointerDeviceType::Touch:
        // Behavior for touch controls
        ...

    default:
        // Behavior for mouse controls
        bool rightButton = pointProperties.IsRightButtonPressed();
        bool leftButton = pointProperties.IsLeftButtonPressed();

        if (!m_autoFire && (!m_mouseLeftInUse && leftButton))
        {
            m_firePressed = true;
        }

        if (!m_mouseInUse)
        {
            m_mouseInUse = true;
            m_mouseLastPoint = position;
            m_mousePointerID = pointerID;
            m_mouseLeftInUse = leftButton;
            m_mouseRightInUse = rightButton;
            // These are for smoothing.
            m_lookLastDelta.x = m_lookLastDelta.y = 0;
        }
        break;
    }
    break;

Wenn der Player keine der Maustasten mehr drückt, wird das Mausereignis CoreWindow::P ointerReleased ausgelöst, wobei die MoveLookController::OnPointerReleased-Methode aufgerufen wird, und die Eingabe ist abgeschlossen. An diesem Punkt werden Kugeln nicht mehr ausgelöst, wenn die linke Maustaste gedrückt wurde und nun losgelassen wird. Da das Aussehen immer aktiviert ist, verwendet das Spiel weiterhin denselben Mauszeiger, um die laufenden Look-Ereignisse nachzuverfolgen.

case MoveLookControllerState::Active:
    // Touch points
    if (pointerID == m_movePointerID)
    {
        // Stop movement
        ...
    }
    else if (pointerID == m_lookPointerID)
    {
        // Stop look rotation
        ...
    }
    // Fire button has been released
    else if (pointerID == m_firePointerID)
    {
        // Stop firing
        ...
    }
    // Mouse point
    else if (pointerID == m_mousePointerID)
    {
        bool rightButton = pointProperties.IsRightButtonPressed();
        bool leftButton = pointProperties.IsLeftButtonPressed();

        // Mouse no longer in use so stop firing
        m_mouseInUse = false;

        // Don't clear the mouse pointer ID so that Move events still result in Look changes.
        // m_mousePointerID = 0;
        m_mouseLeftInUse = leftButton;
        m_mouseRightInUse = rightButton;
    }
    break;

Sehen wir uns nun den letzten Steuerelementtyp an, den wir unterstützen werden: Gamepads. Gamepads werden getrennt von den Touch- und Maussteuerelementen behandelt, da sie das Zeigerobjekt nicht verwenden. Aus diesem Gründen müssen einige neue Ereignishandler und Methoden hinzugefügt werden.

Hinzufügen von Gamepad-Unterstützung

Für dieses Spiel wird gamepad-Unterstützung durch Aufrufe der Windows.Gaming.Input-APIs hinzugefügt. Dieser Satz von APIs bietet Zugriff auf Gamecontrollereingaben wie Rennlenkräder und Flugstöcke.

Im Folgenden finden Sie unsere Gamepad-Steuerelemente.

Benutzereingabe Action
Linker Analogstick Spieler verschieben
Rechter Analogstick Ändern der Drehung (Tonhöhe und Gieren) der Kameraansicht
Rechter Trigger Auslösen einer Kugel
Schaltfläche "Start/Menü" Anhalten oder Fortsetzen des Spiels

In der InitWindow-Methode fügen wir zwei neue Ereignisse hinzu, um festzustellen, ob ein Gamepad hinzugefügt oder entfernt wurde. Diese Ereignisse aktualisieren die eigenschaft m_gamepadsChanged . Dies wird in der UpdatePollingDevices-Methode verwendet, um zu überprüfen, ob sich die Liste der bekannten Gamepads geändert hat.

// Detect gamepad connection and disconnection events.
Gamepad::GamepadAdded({ this, &MoveLookController::OnGamepadAdded });

Gamepad::GamepadRemoved({ this, &MoveLookController::OnGamepadRemoved });

Hinweis

UWP-Apps können keine Eingaben von einem Xbox One-Controller empfangen, während sich die App nicht im Fokus befindet.

Die UpdatePollingDevices-Methode

Die UpdatePollingDevices-Methode des MoveLookController-instance überprüft sofort, ob ein Gamepad verbunden ist. Wenn dies der Fall ist, beginnen wir mit dem Lesen des Zustands mit Gamepad.GetCurrentReading. Dadurch wird die GamepadReading-Struktur zurückgegeben, sodass wir überprüfen können, auf welche Schaltflächen geklickt oder Fingersticks verschoben wurden.

Wenn der Status des Spiels WaitForInput lautet, hören wir nur auf die Schaltfläche Start/Menü des Controllers, damit das Spiel fortgesetzt werden kann.

Wenn es aktiv ist, überprüfen wir die Eingabe des Benutzers und bestimmen, welche In-Game-Aktion erfolgen muss. Wenn der Benutzer den linken Analogstick für instance in eine bestimmte Richtung verschoben hat, informiert dies das Spiel darüber, dass wir den Spieler in die Richtung bewegen müssen, in der der Stick bewegt wird. Die Bewegung des Sticks in eine bestimmte Richtung muss größer als der Radius der toten Zone sein; andernfalls geschieht nichts. Dieser Radius für tote Zonen ist notwendig, um "Driften" zu verhindern, d. h. wenn der Controller kleine Bewegungen vom Daumen des Spielers aufnimmt, während er auf dem Stick ruht. Ohne tote Zonen können die Steuerelemente für den Benutzer zu empfindlich erscheinen.

Die Eingabe des Fingersticks liegt sowohl für die x- als auch für die y-Achse zwischen -1 und 1. Der folgende Consant gibt den Radius der deadstick-Zone an.

#define THUMBSTICK_DEADZONE 0.25f

Mit dieser Variablen beginnen wir dann mit der Verarbeitung von Eingaben, die aktionenfähige Fingerstickeingaben ausführen können. Die Bewegung würde mit einem Wert von [-1, -.26] oder [.26, 1] auf beiden Achsen erfolgen.

Totzone für Fingersticks

Dieser Teil der UpdatePollingDevices-Methode verarbeitet die linken und rechten Fingersticks. Die X- und Y-Werte jedes Sticks werden überprüft, um festzustellen, ob sie sich außerhalb der toten Zone befinden. Wenn eine oder beides der Fall ist, aktualisieren wir die entsprechende Komponente. Wenn der linke Fingerabdruckstick beispielsweise entlang der X-Achse nach links verschoben wird, fügen wir der x-Komponente des m_moveCommand-Vektors -1 hinzu. Dieser Vektor wird verwendet, um alle Bewegungen auf allen Geräten zu aggregieren und später zu berechnen, wohin sich der Spieler bewegen soll.

// Use the left thumbstick to control the eye point position
// (position of the player).

// Check if left thumbstick is outside of dead zone on x axis
if (reading.LeftThumbstickX > THUMBSTICK_DEADZONE ||
    reading.LeftThumbstickX < -THUMBSTICK_DEADZONE)
{
    // Get value of left thumbstick's position on x axis
    float x = static_cast<float>(reading.LeftThumbstickX);
    // Set the x of the move vector to 1 if the stick is being moved right.
    // Set to -1 if moved left. 
    m_moveCommand.x -= (x > 0) ? 1 : -1;
}

// Check if left thumbstick is outside of dead zone on y axis
if (reading.LeftThumbstickY > THUMBSTICK_DEADZONE ||
    reading.LeftThumbstickY < -THUMBSTICK_DEADZONE)
{
    // Get value of left thumbstick's position on y axis
    float y = static_cast<float>(reading.LeftThumbstickY);
    // Set the y of the move vector to 1 if the stick is being moved forward.
    // Set to -1 if moved backwards.
    m_moveCommand.y += (y > 0) ? 1 : -1;
}

Ähnlich wie der linke Stick die Bewegung steuert, steuert der rechte Stick die Drehung der Kamera.

Das Verhalten des rechten Daumensticks entspricht dem Verhalten der Mausbewegung in unserer Maus- und Tastatursteuerungseinrichtung. Wenn sich der Stick außerhalb der toten Zone befindet, berechnen wir die Differenz zwischen der aktuellen Zeigerposition und dem Ort, nach dem der Benutzer jetzt suchen möchte. Diese Änderung der Zeigerposition (pointerDelta) wird dann verwendet, um die Tonhöhe und das Gähnen der Kameradrehung zu aktualisieren, die später in unserer Update-Methode angewendet werden. Der ZeigerDelta-Vektor kann vertraut aussehen, da er auch in der MoveLookController::OnPointerMoved-Methode verwendet wird , um änderungen an der Zeigerposition für maus- und toucheingaben nachzuverfolgen.

// Use the right thumbstick to control the look at position

XMFLOAT2 pointerDelta;

// Check if right thumbstick is outside of deadzone on x axis
if (reading.RightThumbstickX > THUMBSTICK_DEADZONE ||
    reading.RightThumbstickX < -THUMBSTICK_DEADZONE)
{
    float x = static_cast<float>(reading.RightThumbstickX);
    // Register the change in the pointer along the x axis
    pointerDelta.x = x * x * x;
}
// No actionable thumbstick movement. Register no change in pointer.
else
{
    pointerDelta.x = 0.0f;
}
// Check if right thumbstick is outside of deadzone on y axis
if (reading.RightThumbstickY > THUMBSTICK_DEADZONE ||
    reading.RightThumbstickY < -THUMBSTICK_DEADZONE)
{
    float y = static_cast<float>(reading.RightThumbstickY);
    // Register the change in the pointer along the y axis
    pointerDelta.y = y * y * y;
}
else
{
    pointerDelta.y = 0.0f;
}

XMFLOAT2 rotationDelta;
// Scale for control sensitivity.
rotationDelta.x = pointerDelta.x * 0.08f;
rotationDelta.y = pointerDelta.y * 0.08f;

// Update our orientation based on the command.
m_pitch += rotationDelta.y;
m_yaw += rotationDelta.x;

// Limit pitch to straight up or straight down.
m_pitch = __max(-XM_PI / 2.0f, m_pitch);
m_pitch = __min(+XM_PI / 2.0f, m_pitch);

Die Steuerung des Spiels wäre ohne die Fähigkeit, Kugeln abzufeuern, nicht vollständig!

Diese UpdatePollingDevices-Methode überprüft auch, ob der richtige Trigger gedrückt wird. Wenn dies der Fall ist, wird unsere m_firePressed Eigenschaft auf true umgedreht, was dem Spiel signalisiert, dass Kugeln mit dem Feuer beginnen sollen.

if (reading.RightTrigger > TRIGGER_DEADZONE)
{
    if (!m_autoFire && !m_gamepadTriggerInUse)
    {
        m_firePressed = true;
    }

    m_gamepadTriggerInUse = true;
}
else
{
    m_gamepadTriggerInUse = false;
}

Die Update-Methode

Um die Dinge zu schließen, untersuchen wir die Update-Methode . Diese Methode führt alle Bewegungen oder Drehungen, die der Spieler vorgenommen hat, mit einer beliebigen unterstützten Eingabe zusammen, um einen Geschwindigkeitsvektor zu generieren und unsere Tonhöhen- und Gähnwerte für unsere Spielschleife zu aktualisieren.

Die Update-Methode startet die Vorgänge, indem UpdatePollingDevices aufgerufen wird , um den Status des Controllers zu aktualisieren. Diese Methode erfasst auch alle Eingaben von einem Gamepad und fügt seine Bewegungen dem m_moveCommand-Vektor hinzu.

In unserer Update-Methode führen wir dann die folgenden Eingabeüberprüfungen durch.

  • Wenn der Player das Rechteck des Bewegungscontrollers verwendet, bestimmen wir dann die Änderung der Zeigerposition und verwenden diese, um zu berechnen, ob der Benutzer den Zeiger aus der Nicht-Zone des Controllers verschoben hat. Wenn außerhalb der toten Zone die m_moveCommand Vektoreigenschaft mit dem Wert des virtuellen Joysticks aktualisiert wird.
  • Wenn eine der Bewegungstasteneingaben gedrückt wird, wird der Wert 1.0f oder -1.0f in der entsprechenden Komponente des m_moveCommand-Vektors hinzugefügt –1.0f für vorwärts und -1.0f für rückwärts.

Nachdem alle Bewegungseingaben berücksichtigt wurden, führen wir den m_moveCommand Vektor durch einige Berechnungen aus, um einen neuen Vektor zu generieren, der die Richtung des Spielers in Bezug auf die Spielwelt darstellt. Dann nehmen wir unsere Bewegungen in Bezug auf die Welt und wenden sie auf den Spieler als Geschwindigkeit in diese Richtung an. Schließlich setzen wir den m_moveCommand-Vektor auf zurück (0.0f, 0.0f, 0.0f) , damit alles für den nächsten Spielrahmen bereit ist.

void MoveLookController::Update()
{
    // Get any gamepad input and update state
    UpdatePollingDevices();

    if (m_moveInUse)
    {
        // Move control.
        XMFLOAT2 pointerDelta;

        pointerDelta.x = m_movePointerPosition.x - m_moveFirstDown.x;
        pointerDelta.y = m_movePointerPosition.y - m_moveFirstDown.y;

        // Figure out the command from the virtual joystick.
        XMFLOAT3 commandDirection = XMFLOAT3(0.0f, 0.0f, 0.0f);
        // Leave 32 pixel-wide dead spot for being still.
        if (fabsf(pointerDelta.x) > 16.0f)
            m_moveCommand.x -= pointerDelta.x / fabsf(pointerDelta.x);

        if (fabsf(pointerDelta.y) > 16.0f)
            m_moveCommand.y -= pointerDelta.y / fabsf(pointerDelta.y);
    }

    // Poll our state bits set by the keyboard input events.
    if (m_forward)
    {
        m_moveCommand.y += 1.0f;
    }
    if (m_back)
    {
        m_moveCommand.y -= 1.0f;
    }
    if (m_left)
    {
        m_moveCommand.x += 1.0f;
    }
    if (m_right)
    {
        m_moveCommand.x -= 1.0f;
    }
    if (m_up)
    {
        m_moveCommand.z += 1.0f;
    }
    if (m_down)
    {
        m_moveCommand.z -= 1.0f;
    }

    // Make sure that 45deg cases are not faster.
    if (fabsf(m_moveCommand.x) > 0.1f ||
        fabsf(m_moveCommand.y) > 0.1f ||
        fabsf(m_moveCommand.z) > 0.1f)
    {
        XMStoreFloat3(&m_moveCommand, XMVector3Normalize(XMLoadFloat3(&m_moveCommand)));
    }

    // Rotate command to align with our direction (world coordinates).
    XMFLOAT3 wCommand;
    wCommand.x = m_moveCommand.x * cosf(m_yaw) - m_moveCommand.y * sinf(m_yaw);
    wCommand.y = m_moveCommand.x * sinf(m_yaw) + m_moveCommand.y * cosf(m_yaw);
    wCommand.z = m_moveCommand.z;

    // Scale for sensitivity adjustment.
    // Our velocity is based on the command. Y is up.
    m_velocity.x = -wCommand.x * MoveLookConstants::MovementGain;
    m_velocity.z = wCommand.y * MoveLookConstants::MovementGain;
    m_velocity.y = wCommand.z * MoveLookConstants::MovementGain;

    // Clear movement input accumulator for use during next frame.
    m_moveCommand = XMFLOAT3(0.0f, 0.0f, 0.0f);
}

Nächste Schritte

Nachdem wir nun unsere Steuerelemente hinzugefügt haben, gibt es ein weiteres Feature, das wir hinzufügen müssen, um ein immersives Spiel zu erstellen: Sound! Musik und Soundeffekte sind für jedes Spiel wichtig, also besprechen wir als Nächstes das Hinzufügen von Sound .