Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
In dit onderwerp worden patronen en technieken beschreven voor het effectief gebruik van invoerapparaten in UWP-games (Universal Windows Platform).
Door dit onderwerp te lezen, leert u het volgende:
- Hoe spelers te volgen en welke invoer- en navigatieapparaten ze momenteel gebruiken
- knopovergangen detecteren (ingedrukt naar losgelaten, losgelaten naar ingedrukt)
- complexe knopindelingen detecteren met één test
Een invoerapparaatklasse kiezen
Er zijn veel verschillende typen invoer-API's beschikbaar voor u, zoals ArcadeStick, FlightSticken Gamepad. Hoe bepaalt u welke API moet worden gebruikt voor uw game?
U moet kiezen welke API u de meest geschikte invoer geeft voor uw game. Als u bijvoorbeeld een 2D-platformspel maakt, kunt u waarschijnlijk gewoon de Gamepad klasse gebruiken en niet storen met de extra functionaliteit die beschikbaar is via andere klassen. Dit zou de game beperken tot het ondersteunen van gamepads en biedt een consistente interface die werkt op veel verschillende gamepads zonder dat er extra code nodig is.
Aan de andere kant, voor complexe vlucht- en racesimulaties, wilt u misschien alle RawGameController objecten als basis opsommen zodat ze eventuele nicheapparaten ondersteunen die liefhebbers mogelijk hebben, inclusief apparaten zoals afzonderlijke pedalen of een gashendel die door één speler worden gebruikt.
Hier kunt u de methode FromGameController van een invoerklasse gebruiken, zoals Gamepad.FromGameController, om te zien of elk apparaat een meer gecureerde weergave heeft. Als het apparaat bijvoorbeeld ook een Gamepad-is, kunt u de gebruikersinterface voor knoptoewijzing aanpassen om dat te weerspiegelen en een aantal verstandige standaardknoptoewijzingen opgeven waaruit u kunt kiezen. (Dit is in tegenstelling tot het handmatig configureren van de gamepadinvoer als u alleen RawGameController gebruikt.)
U kunt ook kijken naar de leverancier-id (VID) en product-id (PID) van een RawGameController (met behulp van HardwareVendorId en HardwareProductId, respectievelijk) en voorgestelde knoptoewijzingen bieden voor populaire apparaten terwijl ze nog steeds compatibel blijven met onbekende apparaten die in de toekomst beschikbaar zijn via handmatige toewijzingen door de speler.
Verbonden controllers bijhouden
Hoewel elk type controller een lijst met verbonden controllers bevat (zoals Gamepad.Gamepads), is het een goed idee om uw eigen lijst met controllers te onderhouden. Zie De lijst met gamepads voor meer informatie (elk type controller heeft een sectie met een vergelijkbare naam in een eigen onderwerp).
Wat gebeurt er echter wanneer de speler de controller loskoppelt of een nieuwe aansluit? U moet deze gebeurtenissen afhandelen en uw lijst dienovereenkomstig bijwerken. Zie Gamepads toevoegen en verwijderen voor meer informatie (opnieuw heeft elk type controller een sectie met een vergelijkbare naam in een eigen onderwerp).
Omdat de toegevoegde en verwijderde gebeurtenissen asynchroon worden gegenereerd, kunt u onjuiste resultaten krijgen bij het verwerken van uw lijst met controllers. Dus wanneer u toegang hebt tot uw lijst met controllers, moet u er een vergrendeling omheen plaatsen, zodat slechts één thread er tegelijk toegang toe heeft. Dit kan worden gedaan met de Concurrency Runtime, met name de critical_section klasse, in <ppl.h>.
Een ander ding om aan te denken is dat de lijst met verbonden controllers in eerste instantie leeg is en een seconde of twee nodig heeft om te vullen. Dus als u alleen het huidige gamepad toewijst in de startmethode, wordt deze null!
Om dit te verhelpen, moet u een methode hebben die het hoofdspelpad 'vernieuwt' (in een game met één speler; multiplayergames hebben meer geavanceerde oplossingen nodig). U moet deze methode vervolgens aanroepen in zowel de eventhandlers voor het toevoegen van een controller als het verwijderen van een controller, of in uw updatemethode.
De volgende methode retourneert gewoon het eerste gamepad in de lijst (of nullptr als de lijst leeg is). Vervolgens hoeft u alleen maar te controleren op nullptr wanneer u iets met de controller doet. Het is aan u of u gameplay wilt blokkeren wanneer er geen controller is verbonden (bijvoorbeeld door het spel te onderbreken) of gewoon de gameplay wilt voortzetten, terwijl invoer wordt genegeerd.
#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 volgt een voorbeeld van het afhandelen van invoer van een 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);
}
}
Gebruikers en hun apparaten bijhouden
Alle invoerapparaten zijn gekoppeld aan een Gebruiker zodat hun identiteit kan worden gekoppeld aan hun gameplay, prestaties, instellingenwijzigingen en andere activiteiten. Gebruikers kunnen zich naar believen aanmelden of afmelden, en het is gebruikelijk dat een andere gebruiker zich aanmeldt op een invoerapparaat dat verbonden blijft met het systeem nadat de vorige gebruiker zich heeft afgemeld. Wanneer een gebruiker zich aanmeldt of afmeldt, wordt de gebeurtenis IGameController.UserChanged geactiveerd. U kunt een gebeurtenis-handler voor deze gebeurtenis registreren om spelers en de apparaten die ze gebruiken bij te houden.
Gebruikersidentiteit is ook de manier waarop een invoerapparaat wordt gekoppeld aan de bijbehorende ui-navigatiecontroller.
Om deze redenen moet spelerinvoer worden bijgehouden en gecorreleerd met de eigenschap User van de apparaatklasse (overgenomen van de IGameController interface).
De UserGamepadPairingUWP voorbeeld-app op GitHub laat zien hoe u gebruikers en de apparaten kunt bijhouden die ze gebruiken.
Knopovergangen detecteren
Soms wilt u weten wanneer een knop voor het eerst wordt ingedrukt of losgelaten; Dat wil zeggen, precies wanneer de knopstatus overgaat van vrijgegeven naar ingedrukt of van ingedrukt naar vrijgegeven. Om dit te bepalen, moet u de vorige meting onthouden en de huidige meting ermee vergelijken om te zien wat er is gewijzigd.
In het volgende voorbeeld ziet u een basisbenadering voor het onthouden van de vorige lezing; gamepads worden hier weergegeven, maar de principes zijn hetzelfde voor arcadestick, racewiel en de andere invoerapparaattypen.
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)
}
Voordat er iets anders gebeurt, verplaatst Game::Loop de bestaande waarde van newReading (de gamepad-waarde van de vorige lus-iteratie) naar oldReading, en vult newReading met een nieuwe gamepad-waarde voor de huidige iteratie. Dit geeft u de informatie die u nodig hebt om knopovergangen te detecteren.
In het volgende voorbeeld ziet u een basisbenadering voor het detecteren van knopovergangen:
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;
}
Deze twee functies leiden eerst de Booleaanse status van de knopselectie af van newReading en oldReadingen voeren vervolgens Booleaanse logica uit om te bepalen of de doelovergang heeft plaatsgevonden. Deze functies retourneren echte alleen als de nieuwe leesstatus de doelstatus bevat (respectievelijk ingedrukt of vrijgegeven) en de oude leesstatus niet ook de doelstatus bevat; anders retourneren ze onwaar.
Complexe knopindelingen detecteren
Elke knop van een invoerapparaat biedt een digitale leesweergave die aangeeft of het wordt ingedrukt (omlaag) of wordt vrijgegeven (omhoog). Voor efficiëntie worden knopleeswaarden niet weergegeven als afzonderlijke Booleaanse waarden; In plaats daarvan worden ze allemaal verpakt in bitfields die worden vertegenwoordigd door apparaatspecifieke opsommingen zoals GamepadButtons. Als u specifieke knoppen wilt lezen, wordt bitwise maskering gebruikt om de waarden te isoleren waarin u geïnteresseerd bent. Een knop wordt ingedrukt (omlaag) wanneer de bijbehorende bit is gezet; anders wordt deze losgelaten (omhoog).
U herinnert zich nog dat enkele knoppen moeten worden ingedrukt of losgelaten; gamepads worden hier weergegeven, maar de principes zijn hetzelfde voor arcadestick, racewiel en de andere invoerapparaattypen.
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).
}
Zoals u kunt zien, is het bepalen van de status van één knop eenvoudig, maar soms wilt u misschien bepalen of meerdere knoppen worden ingedrukt of niet ingedrukt, of dat een set knoppen op een bepaalde manier is gerangschikt—sommige ingedrukt, anderen niet. Het testen van meerdere knoppen is complexer dan het testen van enkele knoppen, met name met het potentieel van de gemengde knopstatus, maar er is een eenvoudige formule voor deze tests die van toepassing zijn op enkele en meerdere knoptests.
In het volgende voorbeeld wordt bepaald of gamepadknoppen A en B beide worden ingedrukt:
if ((GamepadButtons::A | GamepadButtons::B) == (reading.Buttons & (GamepadButtons::A | GamepadButtons::B))
{
// The A and B buttons are both pressed.
}
In het volgende voorbeeld wordt bepaald of gamepadknoppen A en B beide worden uitgebracht:
if ((GamepadButtons::None == (reading.Buttons & GamepadButtons::A | GamepadButtons::B))
{
// The A and B buttons are both released (not pressed).
}
In het volgende voorbeeld wordt bepaald of gamepadknop A wordt ingedrukt terwijl knop B wordt vrijgegeven:
if (GamepadButtons::A == (reading.Buttons & (GamepadButtons::A | GamepadButtons::B))
{
// The A button is pressed and the B button is released (B is not pressed).
}
De formule die alle vijf deze voorbeelden gemeen hebben, is dat de rangschikking van knoppen die moeten worden getest, wordt opgegeven door de expressie aan de linkerkant van de gelijkheidsoperator terwijl de knoppen die moeten worden overwogen, worden geselecteerd door de maskeringsexpressie aan de rechterkant.
In het volgende voorbeeld ziet u deze formule duidelijker door het vorige voorbeeld te herschrijven:
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).
}
Deze formule kan worden toegepast om een willekeurig aantal knoppen in een willekeurige rangschikking van hun statussen te testen.
De status van de batterij ophalen
Voor elke gamecontroller die de IGameControllerBatteryInfo interface implementeert, kunt u TryGetBatteryReport- op de controllerinstantie aanroepen om een BatteryReport-object op te halen dat informatie over de batterij in de controller biedt. U kunt eigenschappen krijgen zoals de snelheid die de batterij laadt (ChargeRateInMilliwatts), de geschatte energiecapaciteit van een nieuwe batterij (DesignCapacityInMilliwattHours) en de volledig opgeladen energiecapaciteit van de huidige batterij (FullChargeCapacityInMilliwattHours).
Voor gamecontrollers die gedetailleerde batterijrapportage ondersteunen, kunt u deze en meer informatie over de batterij krijgen, zoals beschreven in Batterijgegevens ophalen. De meeste gamecontrollers bieden echter geen ondersteuning voor dat niveau van batterijrapportage en gebruiken in plaats daarvan goedkope hardware. Voor deze controllers moet u rekening houden met de volgende overwegingen:
ChargeRateInMilliwatts en DesignCapacityInMilliwattHours zullen altijd NULLzijn.
U kunt het batterijpercentage verkrijgen door RemainingCapacityInMilliwattHours / FullChargeCapacityInMilliwattHourste berekenen. U moet de waarden van deze eigenschappen negeren en alleen omgaan met het berekende percentage.
Het percentage van het vorige opsommingsteken is altijd een van de volgende:
- 100% (volledig)
- 70% (gemiddeld)
- 40% (laag)
- 10% (kritiek)
Als uw code een actie uitvoert (zoals de gebruikersinterface voor tekenen) op basis van het resterende batterijduurpercentage, moet u ervoor zorgen dat deze voldoet aan de bovenstaande waarden. Als u bijvoorbeeld de speler wilt waarschuwen wanneer de batterij van de controller laag is, doet u dit wanneer deze 10%bereikt.