Controller di gioco non elaborato
Questa pagina descrive le nozioni di base della programmazione per quasi tutti i tipi di controller di gioco utilizzando Windows.Gaming.Input.RawGameController e le API correlate per la piattaforma UWP (Universal Windows Platform).
Leggendo questa pagina, si apprenderà quanto segue:
- come raccogliere un elenco di controller di gioco non elaborati collegati e i relativi utenti
- come rilevare che un controller di gioco non elaborato è stato aggiunto o rimosso
- come ottenere le funzionalità di un controller di gioco non elaborato
- come leggere l'input da un controller di gioco non elaborato
Panoramica
Un controller di gioco non elaborato è una rappresentazione generica di un controller di gioco, con input disponibili su molti tipi diversi di controller di gioco comuni. Questi input vengono esposti come semplici matrici di pulsanti, commutatori e assi senza nome. Utilizzando un controller di gioco non elaborato, è possibile consentire ai clienti di creare mappature di input personalizzate indipendentemente dal tipo di controller che utilizzano.
La classe RawGameController è in realtà concepita per scenari in cui le altre classi di input (ArcadeStick, FlightStick e così via) non soddisfano le esigenze: se si desidera qualcosa di più generico, anticipando che i clienti utilizzeranno molti tipi diversi di controller di gioco, allora questa è la classe ideale.
Rilevamento e tracciamento dei controller di gioco non elaborati
Rilevamento e tracciamento dei controller di gioco non elaborati funzionano esattamente come per i gamepad, con l'unica differenza che viene utilizzata la classe RawGameController anziché la classe Gamepad. Per maggiori informazioni, vedere Gamepad e vibrazione.
Ottenere le funzionalità di un controller di gioco non elaborato
Dopo aver identificato il controller di gioco non elaborato a cui si è interessati, è possibile raccogliere informazioni sulle funzionalità del controller. È possibile ottenere il numero di pulsanti sul controller di gioco non elaborato con RawGameController.ButtonCount, il numero di assi analogici con RawGameController.AxisCount e il numero di commutatori con RawGameController.SwitchCount. È inoltre possibile ottenere il tipo di un commutatore utilizzando RawGameController.GetSwitchKind.
L'esempio seguente ottiene i conteggi di input di un controller di gioco non elaborato:
auto rawGameController = myRawGameControllers->GetAt(0);
int buttonCount = rawGameController->ButtonCount;
int axisCount = rawGameController->AxisCount;
int switchCount = rawGameController->SwitchCount;
L'esempio seguente determina il tipo di ogni commutatore:
for (uint32_t i = 0; i < switchCount; i++)
{
GameControllerSwitchKind mySwitch = rawGameController->GetSwitchKind(i);
}
Lettura del controller di gioco non elaborato
Dopo aver appreso il numero di input in un controller di gioco non elaborato, si è pronti a raccogliere l'input da esso proveniente. Tuttavia, a differenza di altri tipi di input a cui si potrebbe essere abituati, un controller di gioco non elaborato non comunica un cambiamento di stato generando eventi. È invece possibile eseguire letture regolari del suo stato corrente eseguendone il polling.
Polling del controller di gioco non elaborato
Il polling acquisisce un'istantanea del controller di gioco non elaborato in un momento preciso. Questo approccio alla raccolta di input è ideale per la maggior parte dei giochi, in quanto la loro logica viene generalmente eseguita in un ciclo deterministico anziché essere guidata dagli eventi. In genere è anche più semplice interpretare i comandi di gioco dagli input raccolti tutti in una sola volta piuttosto che da molti input singoli raccolti nel tempo.
Eseguire il polling di un controller di gioco non elaborato chiamando RawGameController.GetCurrentReading. Questa funzione popola matrici per pulsanti, commutatori e assi che contengono lo stato del controller di gioco non elaborato.
Nell'esempio seguente viene eseguito il polling di un controller di gioco non elaborato per il relativo stato corrente:
Platform::Array<bool>^ currentButtonReading =
ref new Platform::Array<bool>(buttonCount);
Platform::Array<GameControllerSwitchPosition>^ currentSwitchReading =
ref new Platform::Array<GameControllerSwitchPosition>(switchCount);
Platform::Array<double>^ currentAxisReading = ref new Platform::Array<double>(axisCount);
rawGameController->GetCurrentReading(
currentButtonReading,
currentSwitchReading,
currentAxisReading);
Non esiste alcuna garanzia su quale posizione all'interno di ciascuna matrice conterrà quale valore di input tra i diversi tipi di controller, quindi sarà necessario controllare quale input rappresenta cosa utilizzando i metodi RawGameController.GetButtonLabel e RawGameController.GetSwitchKind.
GetButtonLabel indicherà il testo o il simbolo stampato sul pulsante fisico, anziché la funzione del pulsante, pertanto è preferibile utilizzarlo come ausilio per l'interfaccia utente per i casi in cui si desidera fornire al giocatore suggerimenti su quali pulsanti eseguono quali funzioni. GetSwitchKind indicherà il tipo di commutatore (ovvero, il numero di posizioni di cui dispone), ma non il nome.
Non esiste un modo standardizzato per ottenere l'etichetta di un asse di o un commutatore, quindi sarà necessario testarli da sé per determinare quali input rappresentano cosa.
Se si dispone di un controller specifico che si desidera supportare, è possibile ottenere RawGameController.HardwareProductId e RawGameController.HardwareVendorId e verificare se corrispondono a tale controller. La posizione di ogni input in ogni matrice è la stessa per ogni controller con lo stesso HardwareProductId e HardwareVendorId, quindi non è necessario preoccuparsi della logica potenzialmente incoerente tra diversi controller dello stesso tipo.
Oltre allo stato del controller di gioco non elaborato, ogni lettura restituisce un timestamp che indica esattamente quando è stato recuperato lo stato. Il timestamp è utile per rapportarsi alla tempistica delle letture precedenti o a quella della simulazione del gioco.
Lettura di pulsanti e commutatori
Ognuno dei pulsanti del controller di gioco non elaborato fornisce una lettura digitale che indica se è premuto (giù) o rilasciato (su). Le letture dei pulsanti sono rappresentate come singoli valori booleani in un'unica matrice. È possibile trovare l'etichetta per ciascun pulsante utilizzando RawGameController.GetButtonLabel con l'indice del valore booleano nella matrice. Ogni valore è rappresentato come GameControllerButtonLabel.
L'esempio seguente determina se il pulsante XboxA sia premuto o meno:
for (uint32_t i = 0; i < buttonCount; i++)
{
if (currentButtonReading[i])
{
GameControllerButtonLabel buttonLabel = rawGameController->GetButtonLabel(i);
if (buttonLabel == GameControllerButtonLabel::XboxA)
{
// XboxA is pressed.
}
}
}
A volte si potrebbe voler determinare quando un pulsante passa da premuto a rilasciato o da rilasciato a premuto, se più pulsanti vengono premuti o rilasciati o se una serie di pulsanti è disposta in un modo particolare, es. alcuni premuti e altri no. Per informazioni su come rilevare ciascuna di tali condizioni, vedere Rilevamento di transizioni di pulsanti e Rilevamento di disposizioni complesse di pulsanti.
I valori dei commutatori vengono forniti come matrice di GameControllerSwitchPosition. Poiché questa proprietà è un campo di bit, viene utilizzata la mascheratura bitwise per isolare la direzione del commutatore.
Nell'esempio seguente viene determinato se ciascun commutatore si trova nella posizione in alto o meno:
for (uint32_t i = 0; i < switchCount; i++)
{
if (GameControllerSwitchPosition::Up ==
(currentSwitchReading[i] & GameControllerSwitchPosition::Up))
{
// The switch is in the up position.
}
}
Lettura degli input analogici (levette, trigger, acceleratori e così via)
Un asse analogico fornisce una lettura compresa tra 0,0 e 1,0. Ciò comprende ciascuna dimensione su una levetta quale X e Y per le levette standard o persino gli assi X, Y e Z (rollio, beccheggio e imbardata, rispettivamente) per le levette di volo.
I valori possono rappresentare trigger analogici, accelerazioni o qualsiasi altro tipo di input analogico. Questi valori non vengono forniti con etichette, quindi è consigliabile testare il codice con un'ampia gamma di dispositivi di input per accertarsi che corrispondano correttamente ai presupposti.
In tutti gli assi, il valore è di circa 0,5 per una levetta quando si trova in posizione centrale, ma è normale che il valore preciso possa variare, anche tra letture successive; più avanti in questa sezione vengono descritte strategie per mitigare questa variazione.
L'esempio seguente mostra come leggere i valori analogici da un controller Xbox:
// Xbox controllers have 6 axes: 2 for each stick and one for each trigger.
float leftStickX = currentAxisReading[0];
float leftStickY = currentAxisReading[1];
float rightStickX = currentAxisReading[2];
float rightStickY = currentAxisReading[3];
float leftTrigger = currentAxisReading[4];
float rightTrigger = currentAxisReading[5];
Quando si leggono i valori delle levette, si noterà che non producono in modo affidabile una lettura neutra di 0,5 quando sono inattivi in posizione centrale; produrranno invece valori diversi prossimi a 0,5 ogni volta che la levetta viene spostata e riportata nella posizione centrale. Per mitigare queste variazioni, è possibile implementare una piccola cosiddetta zona morta, ovvero un intervallo di valori vicino alla posizione centrale ideale che vengono ignorati.
Un modo per implementare una zona morta consiste nel determinare di quanto si è spostata la levetta dal centro e ignorare le letture che sono più vicine a una certa distanza scelta. È possibile calcolare la distanza in modo approssimativo (non è esatta in quanto le letture delle levette sono essenzialmente valori polari, non planari) solo mediante il teorema di Pitagora. In questo modo viene generata una zona morta radiale.
L'esempio seguente illustra una zona morta radiale di base utilizzando il teorema di Pitagora:
// Choose a deadzone. Readings inside this radius are ignored.
const float deadzoneRadius = 0.1f;
const float deadzoneSquared = deadzoneRadius * deadzoneRadius;
// Pythagorean theorem: For a right triangle, hypotenuse^2 = (opposite side)^2 + (adjacent side)^2
float oppositeSquared = leftStickY * leftStickY;
float adjacentSquared = leftStickX * leftStickX;
// Accept and process input if true; otherwise, reject and ignore it.
if ((oppositeSquared + adjacentSquared) < deadzoneSquared)
{
// Input accepted, process it.
}