Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
In diesem Thema werden Muster und Techniken für die effektive Verwendung von Eingabegeräten in UWP-Spielen (Universelle Windows-Plattform) beschrieben.
Wenn Sie dieses Thema lesen, lernen Sie Folgendes:
- Wie man Spieler nachverfolgt und welche Eingabe- und Navigationsgeräte sie derzeit verwenden
- Erkennung von Tastenübergängen (gedrückt-zu-losgelassen, losgelassen-zu-gedrückt)
- So erkennen Sie komplexe Tastenanordnungen mit einem einzelnen Test
Auswählen einer Eingabegeräteklasse
Es stehen ihnen viele verschiedene Arten von Eingabe-APIs zur Verfügung, z. B. ArcadeStick, FlightStick und Gamepad. Wie entscheiden Sie, welche API für Ihr Spiel verwendet werden soll?
Sie sollten auswählen, welche API Ihnen die am besten geeignete Eingabe für Ihr Spiel bietet. Wenn Sie beispielsweise ein 2D-Plattform-Spiel erstellen, können Sie einfach die Gamepad Klasse verwenden und müssen sich nicht mit der zusätzlichen Funktionalität anderer Klassen beschäftigen. Dies würde das Spiel auf die Unterstützung von Gamepads beschränken und eine konsistente Schnittstelle bereitstellen, die für viele verschiedene Gamepads ohne zusätzlichen Code funktioniert.
Andererseits sollten Sie für komplexe Flug- und Rennsimulationen alle RawGameController-Objekte als Ausgangspunkt auflisten, um sicherzustellen, dass sie jedes Nischengerät unterstützen, das enthusiastische Spieler möglicherweise haben, einschließlich Geräte wie separate Pedale oder Schubregler, die weiterhin von einem einzelnen Spieler genutzt werden.
Von dort aus können Sie die FromGameController--Methode einer Eingabeklasse verwenden, z. B. Gamepad.FromGameController-, um zu prüfen, ob jedes Gerät eine gezieltere Ansicht bietet. Wenn das Gerät z. B. auch ein Gamepad-ist, sollten Sie die Schaltflächenzuordnungs-UI entsprechend anpassen und einige vernünftige Standardschaltflächenzuordnungen bereitstellen, aus denen Sie auswählen können. (Dies ist im Gegensatz dazu, dass der Spieler die Gamepadeingaben manuell konfigurieren muss, wenn Sie nur RawGameController verwenden.)
Alternativ können Sie sich die Anbieter-ID (VID) und die Produkt-ID (PID) eines RawGameController- (mit HardwareVendorId und HardwareProductId) ansehen und vorgeschlagene Schaltflächenzuordnungen für beliebte Geräte bereitstellen, während sie weiterhin mit unbekannten Geräten kompatibel bleiben, die zukünftig über manuelle Zuordnungen des Spielers herauskommen.
Nachverfolgen von verbundenen Controllern
Während jeder Controllertyp eine Liste verbundener Controller (z. B. Gamepads) enthält, empfiehlt es sich, eine eigene Liste der Controller zu verwalten. Weitere Informationen finden Sie in der Liste der Gamepads (jeder Controllertyp hat einen ähnlich benannten Abschnitt zu einem eigenen Thema).
Was geschieht jedoch, wenn der Spieler seinen Controller abzieht oder einen neuen anschließt? Sie müssen diese Ereignisse behandeln und Ihre Liste entsprechend aktualisieren. Weitere Informationen finden Sie unter Hinzufügen und Entfernen von Gamepads (auch hier hat jeder Controllertyp einen ähnlich benannten Abschnitt zu seinem eigenen Thema).
Da die hinzugefügten und entfernten Ereignisse asynchron ausgelöst werden, können Sie beim Umgang mit der Liste der Controller falsche Ergebnisse erhalten. Daher sollten Sie immer dann, wenn Sie auf Ihre Liste der Controller zugreifen, eine Sperre darauf setzen, damit jeweils nur ein Thread darauf zugreifen kann. Dies kann mit dem Concurrency Runtimeerfolgen, insbesondere mit der critical_section Klassein <ppl.h>.
Eine weitere Sache, die zu beachten ist, ist, dass die Liste der angeschlossenen Controller anfangs leer ist und ein oder zwei Sekunden benötigt, bis sie angezeigt wird. Wenn Sie also nur das aktuelle Gamepad in der Startmethode zuweisen, wird es null!
Um dies zu beheben, sollten Sie über eine Methode verfügen, die das Haupt-Gamepad "aktualisiert" (in einem Einzelspielerspiel; Multiplayerspiele benötigen anspruchsvollere Lösungen). Sie sollten diese Methode dann sowohl in den Ereignishandlern für das Hinzufügen und Entfernen eines Controllers als auch in Ihrer Updatemethode aufrufen.
Die folgende Methode gibt einfach das erste Gamepad in der Liste zurück (oder nullptr , wenn die Liste leer ist). Dann müssen Sie nur daran denken, nullptr zu überprüfen, wann immer Sie etwas mit dem Controller machen. Es liegt bei Ihnen, ob Sie das Gameplay blockieren möchten, wenn kein Controller angeschlossen ist (z. B. durch Anhalten des Spiels), oder ob das Gameplay einfach fortgesetzt werden soll, während die Eingabe ignoriert wird.
#include <ppl.h>
using namespace Platform::Collections;
using namespace Windows::Gaming::Input;
using namespace concurrency;
Vector<Gamepad^>^ m_myGamepads = ref new Vector<Gamepad^>();
Gamepad^ GetFirstGamepad()
{
Gamepad^ gamepad = nullptr;
critical_section::scoped_lock{ m_lock };
if (m_myGamepads->Size > 0)
{
gamepad = m_myGamepads->GetAt(0);
}
return gamepad;
}
Hier ist ein Beispiel, wie man alle Informationen zusammenfügt und Eingaben von einem Gamepad verarbeitet:
#include <algorithm>
#include <ppl.h>
using namespace Platform::Collections;
using namespace Windows::Foundation;
using namespace Windows::Gaming::Input;
using namespace concurrency;
static Vector<Gamepad^>^ m_myGamepads = ref new Vector<Gamepad^>();
static Gamepad^ m_gamepad = nullptr;
static critical_section m_lock{};
void Start()
{
// Register for gamepad added and removed events.
Gamepad::GamepadAdded += ref new EventHandler<Gamepad^>(&OnGamepadAdded);
Gamepad::GamepadRemoved += ref new EventHandler<Gamepad^>(&OnGamepadRemoved);
// Add connected gamepads to m_myGamepads.
for (auto gamepad : Gamepad::Gamepads)
{
OnGamepadAdded(nullptr, gamepad);
}
}
void Update()
{
// Update the current gamepad if necessary.
if (m_gamepad == nullptr)
{
auto gamepad = GetFirstGamepad();
if (m_gamepad != gamepad)
{
m_gamepad = gamepad;
}
}
if (m_gamepad != nullptr)
{
// Gather gamepad reading.
}
}
// Get the first gamepad in the list.
Gamepad^ GetFirstGamepad()
{
Gamepad^ gamepad = nullptr;
critical_section::scoped_lock{ m_lock };
if (m_myGamepads->Size > 0)
{
gamepad = m_myGamepads->GetAt(0);
}
return gamepad;
}
void OnGamepadAdded(Platform::Object^ sender, Gamepad^ args)
{
// Check if the just-added gamepad is already in m_myGamepads; if it isn't,
// add it.
critical_section::scoped_lock lock{ m_lock };
auto it = std::find(begin(m_myGamepads), end(m_myGamepads), args);
if (it == end(m_myGamepads))
{
m_myGamepads->Append(args);
}
}
void OnGamepadRemoved(Platform::Object^ sender, Gamepad^ args)
{
// Remove the gamepad that was just disconnected from m_myGamepads.
unsigned int indexRemoved;
critical_section::scoped_lock lock{ m_lock };
if (m_myGamepads->IndexOf(args, &indexRemoved))
{
if (m_gamepad == m_myGamepads->GetAt(indexRemoved))
{
m_gamepad = nullptr;
}
m_myGamepads->RemoveAt(indexRemoved);
}
}
Nachverfolgen von Benutzern und ihren Geräten
Alle Eingabegeräte sind einem Benutzer zugeordnet, sodass seine Identität mit seinen Spielen, Erfolgen, Änderungen der Einstellungen und anderen Aktivitäten verknüpft werden kann. Benutzer können sich bei Bedarf anmelden oder sich abmelden, und es ist üblich, dass sich ein anderer Benutzer auf einem Eingabegerät anmeldet, das nach der Abmeldung des vorherigen Benutzers mit dem System verbunden bleibt. Wenn sich ein Benutzer anmeldet oder abmeldet, wird das IGameController.UserChanged-Ereignis ausgelöst. Sie können einen Ereignishandler für dieses Ereignis registrieren, um spieler und die verwendeten Geräte nachzuverfolgen.
Die Benutzeridentität ist auch die Art und Weise, wie ein Eingabegerät dem entsprechenden Benutzeroberflächennavigationscontroller zugeordnet ist.
Aus diesen Gründen sollte die Spielereingabe nachverfolgt und mit der von der IGameController-Schnittstelle geerbten User-Eigenschaft der Geräteklasse korreliert werden.
Die UserGamepadPairingUWP-Beispiel-App auf GitHub veranschaulicht, wie Sie Benutzer und die verwendeten Geräte nachverfolgen können.
Erkennen von Schaltflächenübergängen
Manchmal möchten Sie wissen, wann eine Schaltfläche zum ersten Mal gedrückt oder losgelassen wird; genau dann, wenn der Tastenzustand von "Losgelassen" zu "Gedrückt" wechselt oder umgekehrt. Um dies zu ermitteln, müssen Sie den vorherigen Messwert des Geräts im Kopf behalten und mit dem aktuellen Messwert vergleichen, um festzustellen, was sich geändert hat.
Im folgenden Beispiel wird ein grundlegender Ansatz für die Erinnerung an die vorherige Lesung veranschaulicht. Gamepads werden hier gezeigt, aber die Prinzipien sind für Arcade-Joysticks, Rennlenkräder und die anderen Eingabegerätetypen identisch.
Gamepad gamepad;
GamepadReading newReading();
GamepadReading oldReading();
// Called at the start of the game.
void Game::Start()
{
gamepad = Gamepad::Gamepads[0];
}
// Game::Loop represents one iteration of a typical game loop
void Game::Loop()
{
// move previous newReading into oldReading before getting next newReading
oldReading = newReading, newReading = gamepad.GetCurrentReading();
// process device readings using buttonJustPressed/buttonJustReleased (see below)
}
Bevor irgendetwas anderes getan wird, verschiebt Game::Loop
den vorhandenen Wert von newReading
(dem Gamepad-Lesevorgang aus der vorherigen Schleifeniteration) in oldReading
, um dann newReading
mit einer neuen Gamepad-Lesung für die aktuelle Iteration zu füllen. Dadurch erhalten Sie die Informationen, die Sie zum Erkennen von Button-Übergängen benötigen.
Im folgenden Beispiel wird ein grundlegender Ansatz zum Erkennen von Schaltflächenübergängen veranschaulicht:
bool ButtonJustPressed(const GamepadButtons selection)
{
bool newSelectionPressed = (selection == (newReading.Buttons & selection));
bool oldSelectionPressed = (selection == (oldReading.Buttons & selection));
return newSelectionPressed && !oldSelectionPressed;
}
bool ButtonJustReleased(GamepadButtons selection)
{
bool newSelectionReleased =
(GamepadButtons.None == (newReading.Buttons & selection));
bool oldSelectionReleased =
(GamepadButtons.None == (oldReading.Buttons & selection));
return newSelectionReleased && !oldSelectionReleased;
}
Diese beiden Funktionen leiten zuerst den booleschen Zustand der Schaltflächenauswahl von newReading
und oldReading
ab und führen dann eine boolesche Logik aus, um zu bestimmen, ob der Zielübergang aufgetreten ist. Diese Funktionen geben true nur dann zurück, wenn der neue Lesezustand den Zielzustand (gedrückt bzw. losgelassen) und der alte Lesezustand nicht auch den Zielzustand enthält; andernfalls geben sie falsezurück.
Erkennen komplexer Schaltflächenanordnungen
Jede Taste eines Eingabegeräts liefert einen digitalen Wert, der angibt, ob sie gedrückt (unten) oder losgelassen (oben) ist. Aus Effizienzgründen werden die Lesewerte von Schaltflächen nicht als einzelne Boolesche Werte dargestellt; stattdessen sind sie alle in Bitfeldern verpackt, die durch gerätespezifische Enumerationen wie GamepadButtonsdargestellt werden. Um bestimmte Schaltflächen zu lesen, wird die bitweise Maskierung verwendet, um die Werte zu isolieren, die Sie interessieren. Eine Taste wird gedrückt (unten), wenn das entsprechende Bit festgelegt ist; andernfalls wird sie freigegeben (nach oben).
Erinnern Sie sich daran, wie erkannt wird, ob einzelne Tasten gedrückt oder losgelassen werden; Gamepads werden hier gezeigt, aber die Prinzipien sind für Arcade-Sticks, Rennlenkräder und die anderen Eingabegerätetypen identisch.
GamepadReading reading = gamepad.GetCurrentReading();
// Determines whether gamepad button A is pressed.
if (GamepadButtons::A == (reading.Buttons & GamepadButtons::A))
{
// The A button is pressed.
}
// Determines whether gamepad button A is released.
if (GamepadButtons::None == (reading.Buttons & GamepadButtons::A))
{
// The A button is released (not pressed).
}
Wie Sie sehen können, ist die Bestimmung des Zustands einer einzelnen Schaltfläche ziemlich einfach, aber manchmal möchten Sie vielleicht ermitteln, ob mehrere Schaltflächen gedrückt oder losgelassen werden oder ob mehrere Schaltflächen auf eine bestimmte Weise angeordnet sind – einige gedrückt, andere nicht. Das Testen mehrerer Schaltflächen ist komplexer als das Testen einzelner Schaltflächen – insbesondere mit dem Potenzial eines gemischten Schaltflächenzustands – aber es gibt eine einfache Formel für diese Tests, die sowohl für Einzel- als auch für Mehrfach-Schaltflächentests gilt.
Im folgenden Beispiel wird ermittelt, ob die Gamepadtasten A und B beide gedrückt sind.
if ((GamepadButtons::A | GamepadButtons::B) == (reading.Buttons & (GamepadButtons::A | GamepadButtons::B))
{
// The A and B buttons are both pressed.
}
Im folgenden Beispiel wird ermittelt, ob die Gamepadtasten A und B freigegeben werden:
if ((GamepadButtons::None == (reading.Buttons & GamepadButtons::A | GamepadButtons::B))
{
// The A and B buttons are both released (not pressed).
}
Im folgenden Beispiel wird ermittelt, ob die Gamepadtaste A gedrückt wird, während Taste B losgelassen wird.
if (GamepadButtons::A == (reading.Buttons & (GamepadButtons::A | GamepadButtons::B))
{
// The A button is pressed and the B button is released (B is not pressed).
}
Die Gemeinsamkeit in der Formel aller fünf Beispiele besteht darin, dass die Anordnung der Schaltflächen, die getestet werden sollen, durch den Ausdruck auf der linken Seite des Gleichheitsoperators angegeben wird, während die Schaltflächen, die in Betracht gezogen werden, durch den Maskierungsausdruck auf der rechten Seite ausgewählt werden.
Im folgenden Beispiel wird diese Formel deutlicher veranschaulicht, indem das vorherige Beispiel umgeschrieben wird:
auto buttonArrangement = GamepadButtons::A;
auto buttonSelection = (reading.Buttons & (GamepadButtons::A | GamepadButtons::B));
if (buttonArrangement == buttonSelection)
{
// The A button is pressed and the B button is released (B is not pressed).
}
Diese Formel kann angewendet werden, um eine beliebige Anzahl von Schaltflächen in einer beliebigen Anordnung ihrer Zustände zu testen.
Erhalte den Akkuzustand
Für jeden Gamecontroller, der die IGameControllerBatteryInfo-Schnittstelle implementiert, können Sie TryGetBatteryReport auf der Controllerinstanz aufrufen, um ein BatteryReport-Objekt abzurufen, das Informationen zum Akku im Controller bereitstellt. Sie können Eigenschaften erhalten, wie zum Beispiel die Laderate der Batterie (ChargeRateInMilliwatts), die geschätzte Energiekapazität einer neuen Batterie (DesignCapacityInMilliwattHours) und die vollständig geladene Energiekapazität der aktuellen Batterie (FullChargeCapacityInMilliwattHours).
Für Gamecontroller, die detaillierte Akkuberichte unterstützen, können Sie diese und weitere Informationen über den Akku erhalten, wie in Abrufen von Akkuinformationenbeschrieben. Die meisten Gamecontroller unterstützen jedoch keine detaillierte Akkustatusberichterstattung und verwenden stattdessen kostengünstige Hardware. Für diese Controller müssen Sie die folgenden Überlegungen berücksichtigen:
ChargeRateInMilliwatts und DesignCapacityInMilliwattHours werden immer NULL.
Sie können den Akkuprozentsatz abrufen, indem Sie RemainingCapacityInMilliwattHours / FullChargeCapacityInMilliwattHoursberechnen. Sie sollten die Werte dieser Eigenschaften ignorieren und nur den berechneten Prozentsatz behandeln.
Der Prozentsatz des vorherigen Aufzählungspunkts wird immer einer der folgenden sein:
- 100% (vollständig)
- 70% (Mittel)
- 40% (niedrig)
- 10% (kritisch)
Wenn Ihr Code eine Aktion (z. B. die Zeichnungsbenutzeroberfläche) basierend auf dem Restanteil der Akkulaufzeit ausführt, stellen Sie sicher, dass er den oben genannten Werten entspricht. Wenn Sie beispielsweise den Spieler warnen möchten, wenn der Akku des Controllers niedrig ist, tun Sie dies, wenn er 10%erreicht.