Freigeben über


Hinzufügen von Eingaben und Interaktivität zum Marble Maze-Beispiel

Spiele der universellen Windows-Plattform (UWP) werden auf einer Vielzahl von Geräten ausgeführt, z. B. Desktopcomputer, Laptops und Tablets. Ein Gerät kann über eine Vielzahl von Eingabe- und Steuerungsmechanismen verfügen. In diesem Dokument werden die wichtigsten Methoden beschrieben, die Sie berücksichtigen sollten, wenn Sie mit Eingabegeräten arbeiten, und zeigt, wie Marble Maze diese Methoden anwendet.

Hinweis

Den diesem Dokument entsprechenden Beispielcode finden Sie im DirectX-Beispiel anhand des Spiels Marble Maze.

  Hier sind einige der wichtigsten Punkte, die in diesem Dokument erläutert werden, wenn Sie mit Eingaben in Ihrem Spiel arbeiten:

  • Unterstützen Sie nach Möglichkeit mehrere Eingabegeräte, damit Ihr Spiel eine größere Auswahl an Vorlieben und Fähigkeiten für Ihre Kunden bietet. Obwohl die Verwendung von Gamecontrollern und Sensoren optional ist, empfehlen wir es dringend, die Spielerfreundlichkeit zu verbessern. Wir haben die Gamecontroller- und Sensor-APIs entwickelt, damit Sie diese Eingabegeräte einfacher integrieren können.

  • Zum Initialisieren der Toucheingabe müssen Sie sich für Fensterereignisse registrieren, z. B. wenn der Zeiger aktiviert, losgelassen und verschoben wird. Um den Beschleunigungsmesser zu initialisieren, erstellen Sie beim Initialisieren der Anwendung ein Windows::D evices::Sensors::Accelerometer-Objekt. Ein Gamecontroller erfordert keine Initialisierung.

  • Überlegen Sie bei Spielen mit einem Spieler, ob Sie Eingaben von allen möglichen Controllern kombinieren möchten. Auf diese Weise müssen Sie nicht nachverfolgen, welche Eingaben von welchem Controller stammen. Oder verfolgen Sie einfach Eingaben nur vom zuletzt hinzugefügten Controller, wie in diesem Beispiel.

  • Verarbeiten sie Windows-Ereignisse, bevor Sie Eingabegeräte verarbeiten.

  • Gamecontroller und Beschleunigungsmesser unterstützen den Abruf. Das heißt, Sie können Daten abfragen, wenn Sie diese benötigen. Zeichnen Sie touchbezogene Ereignisse in Datenstrukturen auf, die für den Eingabeverarbeitungscode verfügbar sind.

  • Überlegen Sie, ob Eingabewerte in ein gängiges Format normalisiert werden sollen. Auf diese Weise können Sie vereinfachen, wie eingaben von anderen Komponenten Ihres Spiels interpretiert werden, z. B. Physiksimulation, und sie können das Schreiben von Spielen vereinfachen, die auf unterschiedlichen Bildschirmauflösungen funktionieren.

Von Marble Maze unterstützte Eingabegeräte

Marble Maze unterstützt den Gamecontroller, die Maus und die Toucheingabe zum Auswählen von Menüelementen sowie den Gamecontroller, die Maus, die Toucheingabe und den Beschleunigungsmesser zum Steuern des Spielspiels. Marble Maze verwendet die Windows::Gaming::Input-APIs, um den Controller zur Eingabe abzufragen. Mit der Toucheingabe können Anwendungen Fingerspitzeneingaben nachverfolgen und darauf reagieren. Ein Beschleunigungsmesser ist ein Sensor, der die Kraft misst, die entlang der X-, Y- und Z-Achsen angewendet wird. Mithilfe der Windows-Runtime können Sie den aktuellen Zustand des Beschleunigungsmessergeräts abrufen sowie Touchereignisse über den Ereignisbehandlungsmechanismus der Windows-Runtime empfangen.

Hinweis

In diesem Dokument wird die Toucheingabe verwendet, um sowohl auf Die Toucheingabe als auch auf Mauseingaben und Zeiger zu verweisen, um auf jedes Gerät zu verweisen, das Zeigerereignisse verwendet. Da Toucheingabe und Maus standardmäßige Zeigerereignisse verwenden, können Sie beide Geräte verwenden, um Menüelemente auszuwählen und die Spielwiedergabe zu steuern.

 

Hinweis

Das Paketmanifest legt Querformat als einzige unterstützte Zweifingerdrehung für das Spiel fest, um zu verhindern, dass sich die Ausrichtung ändert, wenn Sie das Gerät drehen, um die Murmel zu rollen. Um das Paketmanifest anzuzeigen, öffnen Sie Package.appxmanifest im Projektmappen-Explorer von Visual Studio.

 

Initialisieren von Eingabegeräten

Der Gamecontroller erfordert keine Initialisierung. Um die Toucheingabe zu initialisieren, müssen Sie sich für Fensterereignisse registrieren, beispielsweise wenn der Zeiger aktiviert wird (z. B. drückt der Spieler die Maustaste oder berührt den Bildschirm), losgelassen und verschoben. Zum Initialisieren des Beschleunigungsmessers müssen Sie beim Initialisieren der Anwendung ein Windows::D evices::Sensors::Accelerometer-Objekt erstellen.

Das folgende Beispiel zeigt, wie die App::SetWindow-Methode für Windows ::UI::Core::CoreWindow::P ointerPressed, Windows::UI::Core::CoreWindow::P ointerReleasedund Windows::UI::Core::CoreWindow::P ointerMoved-Zeigerereignisse registriert wird. Diese Ereignisse werden während der Anwendungsinitialisierung und vor der Spielschleife registriert.

Diese Ereignisse werden in einem separaten Thread behandelt, der die Ereignishandler aufruft.

Weitere Informationen zur Initialisierung der Anwendung finden Sie unter Marble Maze-Anwendungsstruktur.

window->PointerPressed += ref new TypedEventHandler<CoreWindow^, PointerEventArgs^>(
    this, 
    &App::OnPointerPressed);

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

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

Die MarbleMazeMain-Klasse erstellt außerdem ein std::map-Objekt zum Speichern von Touchereignissen. Der Schlüssel für dieses Kartenobjekt ist ein Wert, der den Eingabezeiger eindeutig identifiziert. Jeder Schlüssel ist dem Abstand zwischen jedem Berührungspunkt und der Mitte des Bildschirms zugeordnet. Marble Maze verwendet später diese Werte, um die Menge zu berechnen, um die das Labyrinth gekippt wird.

typedef std::map<int, XMFLOAT2> TouchMap;
TouchMap        m_touches;

Die MarbleMazeMain-Klasse enthält auch ein Beschleunigungsmesserobjekt.

Windows::Devices::Sensors::Accelerometer^           m_accelerometer;

Das Beschleunigungsmesserobjekt wird im MarbleMazeMain-Konstruktor initialisiert, wie im folgenden Beispiel gezeigt. Die Windows::Devices::Sensors::Accelerometer::GetDefault-Methode gibt eine Instanz des Standardbeschleunigungsmessers zurück. Wenn es keinen Standardbeschleunigungsmesser gibt, gibt Accelerometer::GetDefault nullptr zurück.

// Returns accelerometer ref if there is one; nullptr otherwise.
m_accelerometer = Windows::Devices::Sensors::Accelerometer::GetDefault();

Sie können die Maus, Toucheingabe oder einen Gamecontroller verwenden, um in den Menüs wie folgt zu navigieren:

  • Verwenden Sie das Steuerkreuz, um das aktive Menü-Element zu ändern.
  • Verwenden Sie die Toucheingabe, die A-Schaltfläche oder die Menüschaltfläche, um ein Menüelement zu wählen oder das aktuelle Menü zu schließen, z. B. die Highscore-Tabelle.
  • Verwenden Sie die Menüschaltfläche, um das Spiel anzuhalten oder fortzusetzen.
  • Klicken Sie mit der Maus auf ein Menüelement, um diese Aktion auszuwählen.

Nachverfolgen der Eingabe des Gamecontrollers

Um die derzeit mit dem Gerät verbundenen Gamepads nachzuverfolgen, definiert MarbleMazeMain eine Membervariable m_myGamepads, die eine Sammlung von Windows::Gaming::Input::Gamepad-Objekten ist. Dies wird im Konstruktor wie folgt initialisiert:

m_myGamepads = ref new Vector<Gamepad^>();

for (auto gamepad : Gamepad::Gamepads)
{
    m_myGamepads->Append(gamepad);
}

Darüber hinaus registriert der MarbleMazeMain-Konstruktorereignisse für das Hinzufügen oder Entfernen von Gamepads:

Gamepad::GamepadAdded += 
    ref new EventHandler<Gamepad^>([=](Platform::Object^, Gamepad^ args)
{
    m_myGamepads->Append(args);
    m_currentGamepadNeedsRefresh = true;
});

Gamepad::GamepadRemoved += 
    ref new EventHandler<Gamepad ^>([=](Platform::Object^, Gamepad^ args)
{
    unsigned int indexRemoved;

    if (m_myGamepads->IndexOf(args, &indexRemoved))
    {
        m_myGamepads->RemoveAt(indexRemoved);
        m_currentGamepadNeedsRefresh = true;
    }
});

Wenn ein Gamepad hinzugefügt wird, wird es zu m_myGamepadshinzugefügt. Wenn ein Gamepad entfernt wird, überprüfen wir, ob sich das Gamepad in m_myGamepadsbefindet, und falls ja, wird es entfernt. In beiden Fällen legen wir m_currentGamepadNeedsRefresh auf wahr fest, was angibt, dass wir m_gamepadneu zuweisen müssen.

Schließlich weisen wir dem m_gamepad ein Gamepad zu und legen m_currentGamepadNeedsRefresh auf falsch fest:

m_gamepad = GetLastGamepad();
m_currentGamepadNeedsRefresh = false;

In der Update-Methode überprüfen wir, ob m_gamepad neu zugewiesen werden muss:

if (m_currentGamepadNeedsRefresh)
{
    auto mostRecentGamepad = GetLastGamepad();

    if (m_gamepad != mostRecentGamepad)
    {
        m_gamepad = mostRecentGamepad;
    }

    m_currentGamepadNeedsRefresh = false;
}

Wenn m_gamepad neu zugewiesen werden muss, weisen wir ihm das zuletzt hinzugefügte Gamepad mit GetLastGamepad zu, das wie folgt definiert ist:

Gamepad^ MarbleMaze::MarbleMazeMain::GetLastGamepad()
{
    Gamepad^ gamepad = nullptr;

    if (m_myGamepads->Size > 0)
    {
        gamepad = m_myGamepads->GetAt(m_myGamepads->Size - 1);
    }

    return gamepad;
}

Diese Methode gibt einfach das letzte Gamepad in m_myGamepads wieder.

Sie können bis zu vier Gamecontroller mit einem Windows 10-Gerät verbinden. Um herauszufinden, welcher Controller der aktive ist, verfolgen wir einfach nur das zuletzt hinzugefügte Gamepad. Wenn Ihr Spiel mehrere Spieler unterstützt, müssen Sie die Eingaben für jeden Spieler separat nachverfolgen.

Die MarbleMazeMain::Update-Methode fragt das Gamepad für die Eingabe ab:

if (m_gamepad != nullptr)
{
    m_oldReading = m_newReading;
    m_newReading = m_gamepad->GetCurrentReading();
}

Wir verfolgen die Eingabelesedaten, die wir im letzten Frame mit m_oldReadingerhalten haben, und die neuesten Eingabedaten mit m_newReading, die wir durch Aufrufen von Gamepad::GetCurrentReading erhalten. Dadurch wird ein GamepadReading-Objekt zurückgegeben, das Informationen zum aktuellen Zustand des Gamepads enthält.

Um zu überprüfen, ob eine Schaltfläche gerade gedrückt oder losgelassen wurde, definieren wir MarbleMazeMain::ButtonJustPressed und MarbleMazeMain::ButtonJustReleased, die Schaltflächenwerte aus diesem Frame und dem letzten Frame vergleichen. Auf diese Weise können wir eine Aktion nur zu dem Zeitpunkt ausführen, zu dem eine Schaltfläche anfänglich gedrückt oder losgelassen wird, und nicht, wenn sie gehalten wird:

bool MarbleMaze::MarbleMazeMain::ButtonJustPressed(GamepadButtons selection)
{
    bool newSelectionPressed = (selection == (m_newReading.Buttons & selection));
    bool oldSelectionPressed = (selection == (m_oldReading.Buttons & selection));
    return newSelectionPressed && !oldSelectionPressed;
}

bool MarbleMaze::MarbleMazeMain::ButtonJustReleased(GamepadButtons selection)
{
    bool newSelectionReleased = 
        (GamepadButtons::None == (m_newReading.Buttons & selection));

    bool oldSelectionReleased = 
        (GamepadButtons::None == (m_oldReading.Buttons & selection));

    return newSelectionReleased && !oldSelectionReleased;
}

GamepadButtons-Lesevorgänge werden mit bitweisen Vorgängen verglichen – wir überprüfen, ob eine Taste mit Bitweise und (&) gedrückt wird. Wir bestimmen, ob eine Taste gerade gedrückt oder losgelassen wurde, indem die alte Lese- und die neue Leserichtung verglichen werden.

Mithilfe der oben genannten Methoden überprüfen wir, ob bestimmte Schaltflächen gedrückt wurden, und führen entsprechende Aktionen aus, die ausgeführt werden müssen. Wenn beispielsweise die Menüschaltfläche (GamepadButtons::Menu) gedrückt wird, ändert sich der Spielzustand von aktiv in angehalten oder angehalten in aktiv.

if (ButtonJustPressed(GamepadButtons::Menu) || m_pauseKeyPressed)
{
    m_pauseKeyPressed = false;

    if (m_gameState == GameState::InGameActive)
    {
        SetGameState(GameState::InGamePaused);
    }  
    else if (m_gameState == GameState::InGamePaused)
    {
        SetGameState(GameState::InGameActive);
    }
}

Außerdem überprüfen wir, ob der Spieler die Schaltfläche „Ansicht“ drückt. In diesem Fall starten wir das Spiel neu oder löschen die Highscore-Tabelle:

if (ButtonJustPressed(GamepadButtons::View) || m_homeKeyPressed)
{
    m_homeKeyPressed = false;

    if (m_gameState == GameState::InGameActive ||
        m_gameState == GameState::InGamePaused ||
        m_gameState == GameState::PreGameCountdown)
    {
        SetGameState(GameState::MainMenu);
        m_inGameStopwatchTimer.SetVisible(false);
        m_preGameCountdownTimer.SetVisible(false);
    }
    else if (m_gameState == GameState::HighScoreDisplay)
    {
        m_highScoreTable.Reset();
    }
}

Wenn das Hauptmenü aktiv ist, ändert sich das aktive Menüelement, wenn das Steuerkreuz nach oben oder unten gedrückt wird. Wenn der Benutzer die aktuelle Auswahl auswählt, wird das entsprechende Benutzeroberfläche-Element als ausgewählt markiert.

// Handle menu navigation.
bool chooseSelection = 
    (ButtonJustPressed(GamepadButtons::A) 
    || ButtonJustPressed(GamepadButtons::Menu));

bool moveUp = ButtonJustPressed(GamepadButtons::DPadUp);
bool moveDown = ButtonJustPressed(GamepadButtons::DPadDown);

switch (m_gameState)
{
case GameState::MainMenu:
    if (chooseSelection)
    {
        m_audio.PlaySoundEffect(MenuSelectedEvent);
        if (m_startGameButton.GetSelected())
        {
            m_startGameButton.SetPressed(true);
        }
        if (m_highScoreButton.GetSelected())
        {
            m_highScoreButton.SetPressed(true);
        }
    }
    if (moveUp || moveDown)
    {
        m_startGameButton.SetSelected(!m_startGameButton.GetSelected());
        m_highScoreButton.SetSelected(!m_startGameButton.GetSelected());
        m_audio.PlaySoundEffect(MenuChangeEvent);
    }
    break;

case GameState::HighScoreDisplay:
    if (chooseSelection || anyPoints)
    {
        SetGameState(GameState::MainMenu);
    }
    break;

case GameState::PostGameResults:
    if (chooseSelection || anyPoints)
    {
        SetGameState(GameState::HighScoreDisplay);
    }
    break;

case GameState::InGamePaused:
    if (m_pausedText.IsPressed())
    {
        m_pausedText.SetPressed(false);
        SetGameState(GameState::InGameActive);
    }
    break;
}

Nachverfolgen von Touch- und Mauseingaben

Bei der Touch- und Mauseingabe wird ein Menüelement ausgewählt, wenn der Benutzer darauf klickt oder berührt. Das folgende Beispiel zeigt, wie die MarbleMazeMain::Update-Methode Zeigereingaben verarbeitet, um Menüelemente auszuwählen. Die m_pointQueue-Membervariable verfolgt die Stellen auf dem Bildschirm nach, die der Benutzer berührt hat oder auf die er geklickt hat. Im Abschnitt Verarbeiten von Zeigerangaben wird ausführlicher beschrieben, wie Marble Maze die Zeigereingaben erfasst.

// Check whether the user chose a button from the UI. 
bool anyPoints = !m_pointQueue.empty();
while (!m_pointQueue.empty())
{
    UserInterface::GetInstance().HitTest(m_pointQueue.front());
    m_pointQueue.pop();
}

Die UserInterface::HitTest-Methode bestimmt, ob sich der angegebene Punkt in den Grenzen eines Benutzeroberfläche-Elements befindet. Alle Benutzeroberfläche-Elemente, die diesen Test bestehen, werden als berührt markiert. Diese Methode verwendet die PointInRect-Hilfsfunktion, um zu bestimmen, ob sich der bereitgestellte Punkt in den Grenzen der einzelnen Benutzeroberfläche-Elemente befindet.

void UserInterface::HitTest(D2D1_POINT_2F point)
{
    for (auto iter = m_elements.begin(); iter != m_elements.end(); ++iter)
    {
        if (!(*iter)->IsVisible())
            continue;

        TextButton* textButton = dynamic_cast<TextButton*>(*iter);
        if (textButton != nullptr)
        {
            D2D1_RECT_F bounds = (*iter)->GetBounds();
            textButton->SetPressed(PointInRect(point, bounds));
        }
    }
}

Aktualisieren des Spielzustands

Nachdem die MarbleMazeMain::Update-Methode Controller- und Toucheingabe verarbeitet hat, wird der Spielzustand aktualisiert, wenn eine Taste gedrückt wurde.

// Update the game state if the user chose a menu option. 
if (m_startGameButton.IsPressed())
{
    SetGameState(GameState::PreGameCountdown);
    m_startGameButton.SetPressed(false);
}
if (m_highScoreButton.IsPressed())
{
    SetGameState(GameState::HighScoreDisplay);
    m_highScoreButton.SetPressed(false);
}

Steuern des Spielspiels

Die Spielschleife und die MarbleMazeMain::Update-Methode arbeiten zusammen, um den Zustand von Spielobjekten zu aktualisieren. Wenn Ihr Spiel Eingaben von mehreren Geräten akzeptiert, können Sie die Eingaben von allen Geräten in einer Gruppe von Variablen sammeln, sodass Sie Code schreiben können, der einfacher zu verwalten ist. Die MarbleMazeMain::Update-Methode definiert eine Gruppe von Variablen, die Bewegungen von allen Geräten ansammeln.

float combinedTiltX = 0.0f;
float combinedTiltY = 0.0f;

Der Eingabemechanismus kann von einem Eingabegerät zu einem anderen variieren. Zeigereingaben werden z. B. mithilfe des Windows-Runtime-Ereignisbehandlungsmodells behandelt. Umgekehrt rufen Sie bei Bedarf Eingabedaten vom Gamecontroller ab. Es wird empfohlen, immer dem Eingabemechanismus zu folgen, der für ein bestimmtes Gerät vorgeschrieben ist. In diesem Abschnitt wird beschrieben, wie Marble Maze Eingaben von jedem Gerät liest, wie die kombinierten Eingabewerte aktualisiert werden und wie die kombinierten Eingabewerte verwendet werden, um den Zustand des Spiels zu aktualisieren.

Verarbeiten von Zeigereingaben

Wenn Sie mit Zeigereingaben arbeiten, rufen Sie die Windows::UI::Core::CoreDispatcher::ProcessEvents-Methode auf, um Fensterereignisse zu verarbeiten. Rufen Sie diese Methode in der Spielschleife auf, bevor Sie die Szene aktualisieren oder rendern. Marble Maze ruft dies in der App::Run-Methode auf:

while (!m_windowClosed)
{
    if (m_windowVisible)
    {
        CoreWindow::GetForCurrentThread()->
            Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent);

        m_main->Update();

        if (m_main->Render())
        {
            m_deviceResources->Present();
        }
    }
    else
    {
        CoreWindow::GetForCurrentThread()->
            Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessOneAndAllPending);
    }
}

Wenn das Fenster sichtbar ist, übergeben wir CoreProcessEventsOption::ProcessAllIfPresent an ProcessEvents, um alle Ereignisse in der Warteschlange zu verarbeiten und sofort zurückzukehren. Andernfalls übergeben wir CoreProcessEventsOption::ProcessOneAndAllPending, um alle Ereignisse in der Warteschlange zu verarbeiten und auf das nächste neue Ereignis zu warten. Nachdem Ereignisse verarbeitet wurden, rendert Marble Maze und zeigt den nächsten Frame an.

Die Windows-Runtime ruft den registrierten Handler für jedes aufgetretene Ereignis auf. Die App::SetWindow-Methode registriert für Ereignisse und leitet Zeigerinformationen an die MarbleMazeMain-Klasse weiter.

void App::OnPointerPressed(
    Windows::UI::Core::CoreWindow^ sender, 
    Windows::UI::Core::PointerEventArgs^ args)
{
    m_main->AddTouch(args->CurrentPoint->PointerId, args->CurrentPoint->Position);
}

void App::OnPointerReleased(
    Windows::UI::Core::CoreWindow^ sender, 
    Windows::UI::Core::PointerEventArgs^ args)
{
    m_main->RemoveTouch(args->CurrentPoint->PointerId);
}

void App::OnPointerMoved(
    Windows::UI::Core::CoreWindow^ sender, 
    Windows::UI::Core::PointerEventArgs^ args)
{
    m_main->UpdateTouch(args->CurrentPoint->PointerId, args->CurrentPoint->Position);
}

Die MarbleMazeMain-Klasse reagiert auf Zeigerereignisse, indem das Kartenobjekt aktualisiert wird, das Touchereignisse enthält. Die MarbleMazeMain::AddTouch-Methode wird aufgerufen, wenn der Zeiger zum ersten Mal gedrückt wird, z. B. wenn der Benutzer den Bildschirm anfänglich auf einem Gerät mit Toucheingabe berührt. Die MarbleMazeMain::UpdateTouch-Methode wird aufgerufen, wenn die Zeigerposition verschoben wird. Die MarbleMazeMain::RemoveTouch-Methode wird aufgerufen, wenn der Zeiger losgelassen wird, z. B. wenn der Benutzer das Berühren des Bildschirms beendet.

void MarbleMazeMain::AddTouch(int id, Windows::Foundation::Point point)
{
    m_touches[id] = PointToTouch(point, m_deviceResources->GetLogicalSize());

    m_pointQueue.push(D2D1::Point2F(point.X, point.Y));
}

void MarbleMazeMain::UpdateTouch(int id, Windows::Foundation::Point point)
{
    if (m_touches.find(id) != m_touches.end())
        m_touches[id] = PointToTouch(point, m_deviceResources->GetLogicalSize());
}

void MarbleMazeMain::RemoveTouch(int id)
{
    m_touches.erase(id);
}

Die PointToTouch-Funktion verschiebt die aktuelle Zeigerposition, sodass sich der Ursprung in der Mitte des Bildschirms befindet. Dann skaliert sie die Koordinaten so, dass sie ungefähr zwischen -1,0 und +1,0 liegen. Dadurch ist es einfacher, die Neigung des Labyrinths auf einheitliche Weise über verschiedene Eingabemethoden hinweg zu berechnen.

inline XMFLOAT2 PointToTouch(Windows::Foundation::Point point, Windows::Foundation::Size bounds)
{
    float touchRadius = min(bounds.Width, bounds.Height);
    float dx = (point.X - (bounds.Width / 2.0f)) / touchRadius;
    float dy = ((bounds.Height / 2.0f) - point.Y) / touchRadius;

    return XMFLOAT2(dx, dy);
}

Die MarbleMazeMain::Update-Methode aktualisiert die kombinierten Eingabewerte, indem der Neigungsfaktor um einen konstanten Skalierungswert erhöht wird. Dieser Skalierungswert wurde durch Experimentieren mit mehreren verschiedenen Werten bestimmt.

// Account for touch input.
for (TouchMap::const_iterator iter = m_touches.cbegin(); 
    iter != m_touches.cend(); 
    ++iter)
{
    combinedTiltX += iter->second.x * m_touchScaleFactor;
    combinedTiltY += iter->second.y * m_touchScaleFactor;
}

Verarbeiten von Beschleunigungsmessereingaben

Zum Verarbeiten der Beschleunigungsmessereingabe ruft die MarbleMazeMain::Update-Methode die Windows::D evices::Sensors::Accelerometer::GetCurrentReading-Methode auf. Diese Methode gibt ein Windows::D evices::Sensors::AccelerometerReading-Objekt wieder, das ein Beschleunigungsmesser-Lesen darstellt. Die Eigenschaften Windows::Devices::Sensors::AccelerometerReading::AccelerationX und Windows::Devices::Sensors::AccelerometerReading::AccelerationY enthalten die Schwerkraftbeschleunigung entlang der X-Achse bzw. der Y-Achse.

Das folgende Beispiel zeigt, wie die MarbleMazeMain::Update-Methode den Beschleunigungsmesser abruft und die kombinierten Eingabewerte aktualisiert. Wenn Sie das Gerät kippen, bewirkt die Schwerkraft, dass sich die Murmel schneller bewegt.

// Account for sensors.
if (m_accelerometer != nullptr)
{
    Windows::Devices::Sensors::AccelerometerReading^ reading =
        m_accelerometer->GetCurrentReading();

    if (reading != nullptr)
    {
        combinedTiltX += 
            static_cast<float>(reading->AccelerationX) * m_accelerometerScaleFactor;

        combinedTiltY += 
            static_cast<float>(reading->AccelerationY) * m_accelerometerScaleFactor;
    }
}

Da Sie nicht sicher sein können, dass ein Beschleunigungsmesser auf dem Computer des Benutzers vorhanden ist, stellen Sie immer sicher, dass Sie über ein gültiges Beschleunigungsmesserobjekt verfügen, bevor Sie den Beschleunigungsmesser abrufen.

Verarbeiten von Gamecontrollereingaben

In der MarbleMazeMain::Update-Methode verwenden wir m_newReading, um Eingaben vom linken Analogstick zu verarbeiten:

float leftStickX = static_cast<float>(m_newReading.LeftThumbstickX);
float leftStickY = static_cast<float>(m_newReading.LeftThumbstickY);

auto oppositeSquared = leftStickY * leftStickY;
auto adjacentSquared = leftStickX * leftStickX;

if ((oppositeSquared + adjacentSquared) > m_deadzoneSquared)
{
    combinedTiltX += leftStickX * m_controllerScaleFactor;
    combinedTiltY += leftStickY * m_controllerScaleFactor;
}

Wir überprüfen, ob sich die Eingabe des linken Analogsticks außerhalb des inaktiven Bereichs befindet, und wenn ja, fügen wir sie zu combinedTiltX und combinedTiltY (multipliziert mit einem Skalierungsfaktor) hinzu, um die Stufe zu kippen.

Wichtig

Wenn Sie mit einem Gamecontroller arbeiten, müssen Sie immer den inaktiven Bereich berücksichtigen. Die inaktive Zone bezieht sich auf die Varianz zwischen Gamepads in ihrer Empfindlichkeit gegenüber der anfänglichen Bewegung. In einigen Controllern generiert eine kleine Bewegung möglicherweise keinen Lesevorgang, aber in anderen wird möglicherweise ein messbarer Wert generiert. Um dies in Ihrem Spiel zu berücksichtigen, erstellen Sie eine Zone ohne Bewegung für die anfängliche Ministickbewegung. Weitere Informationen zum inaktiven Bereich finden Sie unter Lesen der Ministicks.

 

Anwenden von Eingaben auf den Spielzustand

Geräte melden Eingabewerte auf unterschiedliche Weise. Die Zeigereingabe kann sich z. B. in Bildschirmkoordinaten befinden, und die Controllereingabe weist ein völlig anderes Format auf. Eine Herausforderung beim Kombinieren von Eingaben von mehreren Geräten in einer Reihe von Eingabewerten ist die Normalisierung oder das Konvertieren von Werten in ein gängiges Format. Marble Maze normalisiert Werte durch Skalierung auf einen Bereich [-1,0, 1,0]. Die zuvor in diesem Abschnitt beschriebene PointToTouch-Funktion konvertiert Bildschirmkoordinaten in normalisierte Werte, die ungefähr zwischen -1,0 und +1,0 liegen.

Tipp

Auch wenn Ihre Anwendung eine Eingabemethode verwendet, empfehlen wir, Eingabewerte immer zu normalisieren. Auf diese Weise können Sie vereinfachen, wie eingaben von anderen Komponenten Ihres Spiels interpretiert werden, z. B. physikalische Simulationen, und es erleichtert das Schreiben von Spielen, die auf unterschiedlichen Bildschirmauflösungen funktionieren.

 

Nachdem die MarbleMazeMain::Update-Methode Eingaben verarbeitet hat, wird ein Vektor erstellt, der die Auswirkung der Neigung des Labyrinths auf die Murmel darstellt. Das folgende Beispiel zeigt, wie Marble Maze die XMVector3Normalize-Funktion verwendet, um einen normalisierten Schwerkraftvektor zu erstellen. Die MaxTilt-Variable schränkt die Neigung des Labyrinths ein und verhindert, dass das Labyrinth auf die Seite gekippt wird.

const float maxTilt = 1.0f / 8.0f;

XMVECTOR gravity = XMVectorSet(
    combinedTiltX * maxTilt, 
    combinedTiltY * maxTilt, 
    1.0f, 
    0.0f);

gravity = XMVector3Normalize(gravity);

Um die Aktualisierung von Szeneobjekten abzuschließen, übergibt Marble Maze den aktualisierten Schwerkraftvektor an die Physikalische Simulation, aktualisiert die Physiksimulation für die Zeit, die seit dem vorherigen Frame verstrichen ist, und aktualisiert die Position und Ausrichtung der Murmel. Wenn die Murmel durch das Labyrinth gefallen ist, platziert die MarbleMazeMain::Update-Methode die Murmel am letzten Prüfpunkt, den die Murmel berührt hat, und setzt den Zustand der Physiksimulation zurück.

XMFLOAT3A g;
XMStoreFloat3(&g, gravity);
m_physics.SetGravity(g);

if (m_gameState == GameState::InGameActive)
{
    // Only update physics when gameplay is active.
    m_physics.UpdatePhysicsSimulation(static_cast<float>(m_timer.GetElapsedSeconds()));

    // ...Code omitted for simplicity...

}

// ...Code omitted for simplicity...

// Check whether the marble fell off of the maze. 
const float fadeOutDepth = 0.0f;
const float resetDepth = 80.0f;
if (marblePosition.z >= fadeOutDepth)
{
    m_targetLightStrength = 0.0f;
}
if (marblePosition.z >= resetDepth)
{
    // Reset marble.
    memcpy(&marblePosition, &m_checkpoints[m_currentCheckpoint], sizeof(XMFLOAT3));
    oldMarblePosition = marblePosition;
    m_physics.SetPosition((const XMFLOAT3&)marblePosition);
    m_physics.SetVelocity(XMFLOAT3(0, 0, 0));
    m_lightStrength = 0.0f;
    m_targetLightStrength = 1.0f;

    m_resetCamera = true;
    m_resetMarbleRotation = true;
    m_audio.PlaySoundEffect(FallingEvent);
}

In diesem Abschnitt wird nicht beschrieben, wie die Physiksimulation funktioniert. Details hierzu finden Sie in den Quellen für Marble Maze in Physics.h und Physics.cpp.

Nächste Schritte

Lesen Sie das Hinzufügen von Audio zum Marble Maze-Beispiel, um Informationen zu einigen der wichtigsten Methoden zu erhalten, die Sie beim Arbeiten mit Audio berücksichtigen sollten. Im Dokument wird erläutert, wie Marble Maze Microsoft Media Foundation und XAudio2 zum Laden, Mischen und Wiedergeben von Audioressourcen verwendet.