Modo corretto di astrarre un controller XBox


12

Ho un controller XBox360 che vorrei utilizzare come input per un'applicazione.

Quello che non riesco a capire è il modo migliore per esporre questo tramite un'interfaccia.

Dietro le quinte, la classe che gestisce i controller si basa sullo stato del pulsante di polling.

Inizialmente ho provato qualcosa link:

Event ButtonPressed() as ButtonEnum

dove ButtonEnumera ButtonRed, ButtonStart, ecc ...

Questo è un po 'limitato in quanto supporta solo la pressione dei pulsanti, non le prese / i motivi (premere due volte, ecc.)

L'idea successiva era semplicemente esporre lo stato del pulsante all'app, ad es

Property RedPressed as Boolean
Property StartPressed as Boolean
Property Thumb1XAxis as Double

Questo è molto flessibile ma in realtà forza troppo lavoro nell'app e richiede l'app per il polling - preferirei se fosse guidato da eventi.

Ho preso in considerazione l'aggiunta di più eventi, ad esempio:

Event ButtonPressed(Button as ButtonEnum)
Event ButtonPressedTwice(Button as ButtonEnum)
Event ButtonHeldStart(Button as ButtonEnum)
Event ButtonHeldEnd(Button as ButtonEnum)

ma questo sembra un po 'goffo ed è stato un vero dolore nella schermata "Pulsante Bind".

Qualcuno può indicarmi il modo "corretto" di gestire gli input dai controller.

NB: sto usando SlimDX all'interno della classe che implementa l'interfaccia. Questo mi permette di leggere lo stato molto facilmente. Sono anche apprezzate tutte le alternative che potrebbero risolvere il mio problema

Risposte:


21

Non esiste una mappatura perfetta che ti dia un'astrazione specifica per la piattaforma, perché ovviamente la maggior parte degli identificatori che hanno senso per un controller 360 sono sbagliati per un controller PlayStation (A invece di X, B anziché Cerchio). E ovviamente un controller Wii è un'altra cosa del tutto.

Il modo più efficace che ho trovato per affrontare questo è usare tre livelli di implementazione. Il livello inferiore è interamente specifico per piattaforma / controller e sa quanti pulsanti digitali e assi analogici sono disponibili. È questo livello che sa eseguire il polling dello stato dell'hardware ed è questo livello che ricorda abbastanza bene lo stato precedente da sapere quando è stato appena premuto un pulsante o se è stato premuto per più di un segno di spunta o se non è stato premuto . A parte questo è stupido - una classe allo stato puro che rappresenta un singolo tipo di controller. Il suo vero valore è quello di sottrarre l'astuto spirito di interrogare lo stato del controller lontano dal livello intermedio.

Il livello intermedio è l'effettiva mappatura dei controlli da pulsanti reali a concetti di gioco (ad es. A -> Jump). Li chiamiamo impulsi anziché pulsanti, perché non sono più associati a un particolare tipo di controller. È a questo livello che puoi rimappare i controlli (durante lo sviluppo o in fase di esecuzione su richiesta dell'utente). Ogni piattaforma ha la propria mappatura dei controlli agli impulsi virtuali . Non puoi e non dovresti cercare di allontanartene. Ogni controller è unico e necessita di una propria mappatura. I pulsanti possono essere associati a più di un impulso (a seconda della modalità di gioco) e più di un pulsante può essere associato allo stesso impulso (ad esempio, A e X entrambi accelerano, B e Y entrambi rallentano). La mappatura definisce tutto ciò,

Il livello superiore è il livello di gioco. Prende impulsi e non importa come sono stati generati. Forse venivano da un controller, o da una registrazione di un controller, o forse venivano da un'intelligenza artificiale. A questo livello, non ti interessa. Quello che ti interessa è che c'è un nuovo impulso Jump, o che l'impulso Accelerate è continuato o che l'impulso Dive abbia un valore di 0,35 in questo segno di spunta.

Con questo tipo di sistema, scrivi il livello inferiore una volta per ciascun controller. Lo strato superiore è indipendente dalla piattaforma. Il codice per il livello intermedio deve essere scritto solo una volta, ma i dati (la rimappatura) devono essere rifatti per ogni piattaforma / controller.


Questo sembra un approccio molto pulito ed elegante. Grazie!
Basic

1
Molto bella. Molto meglio del mio: P
Jordaan Mylonas,

3
Astrazione molto bella. Ma fai attenzione durante l'implementazione: non creare e distruggere un nuovo oggetto impulso per ogni azione dell'utente. Usa il pooling. Garbage Collector ti ringrazierà.
Grega g,

Assolutamente. Un array statico dimensionato sul numero massimo di impulsi simultanei è quasi sempre l'opzione migliore; poiché quasi sempre si desidera che sia attiva una sola istanza di ciascun impulso alla volta. Molto spesso quell'array contiene solo pochi elementi, quindi è veloce da iterare.
MrCranky,

@grega grazie a tutti e due - non ci avevo pensato.
Base

1

Onestamente, direi che un'interfaccia ottimale dipenderà fortemente dall'uso nel gioco. Tuttavia, per uno scenario di utilizzo generale, suggerirei un'architettura a due livelli che separa gli approcci basati sugli eventi e basati sul polling.

Il livello inferiore può essere considerato il livello "basato sul polling" ed espone lo stato corrente di un pulsante. Una di queste interfacce potrebbe semplicemente avere 2 funzioni, GetAnalogState(InputIdentifier)e GetDigitalState(InputIdentifier)dov'è InputIdentifierun valore enumerato che rappresenta quale pulsante, trigger o stick stai controllando. (L'esecuzione di GetAnalogState per un pulsante restituisce 1.0 o 0.0 e l'esecuzione di GetDigitalState per una chiavetta analogica restituisce true se supera una soglia preimpostata o false in caso contrario).

Il secondo livello utilizza quindi il livello inferiore per generare eventi in caso di cambio di stato e consentire agli elementi di registrarsi per i callback (gli eventi C # sono gloriosi). Questi callback includerebbero eventi per stampa, rilascio, tap, longhold, ecc. Per uno stick analogico, potresti includere gesti, ad esempio QCF per un gioco di combattimento. Il numero di eventi esposti dipende dal livello di dettaglio di un evento che si desidera inviare. Potresti semplicemente sparare un semplice ButtonStateChanged(InputIdentifier)se volessi gestire tutto il resto in una logica separata.

Pertanto, se è necessario controllare lo stato corrente di un pulsante di input o di una levetta di controllo, controllare il livello inferiore. Se si desidera semplicemente attivare una funzione su un evento di input, registrarsi per una richiamata dal secondo livello.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.