Eingabemethoden für Spiele
In diesem Thema werden Muster und Techniken für die effektive Verwendung von Eingabegeräten in Universelle Windows-Plattform -Spielen (UWP) beschrieben.
Wenn Sie dieses Thema lesen, lernen Sie Folgendes:
- Nachverfolgen von Spielern und den aktuell verwendeten Eingabe- und Navigationsgeräten
- Erkennen von Tastenübergängen (gedrückt zu losgelassen, losgelassen, losgelassen)
- 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-Plattformspiel erstellen, können Sie wahrscheinlich nur die Gamepad-Klasse verwenden und nicht mit der zusätzlichen Funktionalität, die über andere Klassen verfügbar ist, stören. 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 Basisplan aufzählen, um sicherzustellen, dass sie jedes Nischengerät unterstützen, das enthusiastische Spieler haben könnten, einschließlich Geräte wie separate Pedale oder Drosselung, die noch von einem einzelnen Spieler verwendet werden.
Von dort aus können Sie die FromGameController-Methode einer Eingabeklasse verwenden, z. B. Gamepad.FromGameController, um festzustellen, ob jedes Gerät eine kuratierte Ansicht aufweist. Wenn das Gerät z. B. auch ein Gamepad ist, sollten Sie die Schaltflächenzuordnungs-UI anpassen, um dies widerzuspiegeln, 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 verwenden RawGameController.)
Alternativ können Sie sich die Hersteller-ID (VID) und die Produkt-ID (PID) eines RawGameController (mit HardwareVendorId bzw. 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 abtöpst oder einen neuen ansteckt? 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 der Concurrency Runtime erfolgen, insbesondere mit der critical_section-Klasse in< ppl.h>.
Eine weitere Zusache ist, dass die Liste der angeschlossenen Controller anfangs leer ist und ein oder zwei Sekunden benötigt, um aufzufüllen. Wenn Sie also nur das aktuelle Gamepad in der Startmethode zuweisen, ist 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 im hinzugefügten Controller als auch in den entfernten Ereignishandlern des Controllers oder in der 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 sich nur daran erinnern, dass Nullptr bei jeder Aktion mit dem Controller auf Nullptr überprüft wird. Es liegt an Ihnen, ob Sie das Spiel blockieren möchten, wenn kein Controller angeschlossen ist (z. B. durch Anhalten des Spiels) oder einfach das Spiel 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 für die Behandlung von Eingaben aus einem Gamepad:
#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, damit seine Identität mit dem Spiel, den Erfolgen, den 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 User-Eigenschaft der Geräteklasse korreliert werden (geerbt von der IGameController-Schnittstelle ).
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" in "Gedrückt" oder "Losgelassen" wechselt. Um dies zu ermitteln, müssen Sie sich den vorherigen Gerätelesevorgang merken und den aktuellen Lesewert vergleichen, um zu sehen, was geändert wurde.
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 Sie etwas anderes tun, Game::Loop
verschiebt den vorhandenen Wert ( newReading
den Gamepad-Lesewert aus der vorherigen Schleifeniteration) in oldReading
und füllt newReading
dann einen neuen Gamepad-Lesewert für die aktuelle Iteration. Dadurch erhalten Sie die Informationen, die Sie zum Erkennen von Schaltflächenü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 ab newReading
und oldReading
führen dann eine boolesche Logik aus, um zu bestimmen, ob der Zielübergang aufgetreten ist. Diese Funktionen geben "true" nur zurück, wenn der neue Lesezustand den Zielzustand (gedrückt oder losgelassen) enthält und der alte Lesezustand nicht auch den Zielzustand enthält. Andernfalls wird "false" zurückgegeben.
Erkennen komplexer Schaltflächenanordnungen
Jede Taste eines Eingabegeräts stellt einen digitalen Lesewert bereit, der angibt, ob es gedrückt (nach unten) oder losgelassen (nach oben) ist. Aus Effizienzgründen werden Die Lesewerte von Schaltflächen nicht als einzelne boolesche Werte dargestellt; Stattdessen sind sie alle in Bitfelder verpackt, die durch gerätespezifische Enumerationen wie GamepadButtons dargestellt 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 einzelne Tasten bestimmt sind, dass sie gedrückt oder losgelassen werden; Gamepads werden hier gezeigt, aber die Prinzipien sind für Arcade-Joysticks, 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 direkt vorwärts, aber manchmal möchten Sie vielleicht ermitteln, ob mehrere Schaltflächen gedrückt oder losgelassen werden, oder wenn eine Reihe von 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 für Einzel- und Mehrere Schaltflächentests gilt.
Im folgenden Beispiel wird ermittelt, ob die Gamepadtasten A und B gedrückt werden:
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 Formel, die alle fünf dieser Beispiele gemeinsam haben, besteht darin, dass die Anordnung der zu testenden Schaltflächen durch den Ausdruck auf der linken Seite des Gleichheitsoperators angegeben wird, während die zu berücksichtigenden Schaltflächen 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.
Abrufen des Akkuzustands
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 wie die Laderate (ChargeRateInMilliwatts), die geschätzte Energiekapazität einer neuen Batterie (DesignCapacityInMilliwattHours) und die vollständig geladene Energiekapazität der aktuellen Batterie (FullChargeCapacityInMilliwattHours) abrufen.
Für Gamecontroller, die detaillierte Akkuberichte unterstützen, können Sie dies und weitere Informationen zum Akku erhalten, wie in den Informationen zum Abrufen von Akkus beschrieben. Die meisten Gamecontroller unterstützen diese Akkuberichterstattung jedoch nicht und verwenden stattdessen kostengünstige Hardware. Für diese Controller müssen Sie die folgenden Überlegungen berücksichtigen:
ChargeRateInMilliwatts und DesignCapacityInMilliwattHours sind immer NULL.
Sie können den Akkuprozentsatz abrufen, indem Sie RemainingCapacityInMilliwattHours FullChargeCapacityInMilliwattHours berechnen. / Sie sollten die Werte dieser Eigenschaften ignorieren und nur den berechneten Prozentsatz behandeln.
Der Prozentsatz des vorherigen Aufzählungspunkts ist immer eine der folgenden:
- 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.