Forma correcta de abstraer un controlador XBox

12

Tengo un controlador XBox360 que me gustaría usar como entrada para una aplicación.

Lo que no puedo resolver es la mejor forma de exponer esto a través de una interfaz.

Detrás de escena, la clase que maneja los controladores se basa en el estado del botón de sondeo.

Inicialmente probé algo enlace:

Event ButtonPressed() as ButtonEnum

donde ButtonEnumestaba ButtonRed, ButtonStartetc ...

Esto es un poco limitado, ya que solo admite presionar botones, no retener / patrones (presionar dos veces, etc.)

La siguiente idea fue simplemente exponer el estado del botón a la aplicación, por ejemplo

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

Esto es muy flexible, pero realmente obliga a trabajar demasiado en la aplicación y requiere que la aplicación realice una encuesta; preferiría que sea posible si se realiza un evento.

Pensé en agregar varios eventos, por ejemplo:

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

pero esto parece un poco torpe y fue un verdadero dolor en la pantalla "Botón de enlace".

¿Puede alguien indicarme la forma "correcta" de manejar las entradas de los controladores?

NB: estoy usando SlimDX dentro de la clase que implementa la interfaz. Esto me permite leer el estado muy fácilmente. Cualquier alternativa que resolvería mi problema también es apreciada

Básico
fuente

Respuestas:

21

No existe un mapeo perfecto que le brinde una abstracción específica de la plataforma, porque obviamente la mayoría de los identificadores que tienen sentido para un controlador 360 son incorrectos para un controlador de PlayStation (A en lugar de X, B en lugar de Circle). Y, por supuesto, un controlador de Wii es otra cosa completamente distinta.

La forma más efectiva que he encontrado para lidiar con esto es usar tres capas de implementación. La capa inferior es completamente específica de la plataforma / controlador, y sabe cuántos botones digitales y ejes analógicos están disponibles. Es esta capa la que sabe cómo sondear el estado del hardware, y es esta capa la que recuerda el estado anterior lo suficientemente bien como para saber cuándo se ha presionado un botón, o si ha estado presionado durante más de un tic, o si no se presionó . Aparte de eso, es tonto: una clase de estado puro que representa un solo tipo de controlador. Su valor real es abstraer el meollo de la cuestión al consultar el estado del controlador lejos de la capa intermedia.

La capa intermedia es el mapeo de control real desde botones reales hasta conceptos de juego (por ejemplo, A -> Jump). Los llamamos impulsos en lugar de botones, porque ya no están vinculados a un tipo de controlador en particular. Es en esta capa donde puedes reasignar los controles (ya sea durante el desarrollo o en tiempo de ejecución a instancias del usuario). Cada plataforma tiene su propia asignación de controles a impulsos virtuales . No puedes ni debes intentar alejarte de esto. Cada controlador es único y necesita su propia asignación. Los botones pueden asignarse a más de un impulso (según el modo de juego), y más de un botón puede asignarse al mismo impulso (por ejemplo, A y X aceleran, B e Y desaceleran). El mapeo define todo eso,

La capa superior es la capa del juego. Toma impulsos y no le importa cómo se generaron. Tal vez vinieron de un controlador, o una grabación de un controlador, o tal vez vinieron de una IA. A este nivel, no te importa. Lo que le importa es que haya un nuevo impulso de salto, o que el impulso de aceleración haya continuado, o que el impulso de inmersión tenga un valor de 0.35 en este tic.

Con este tipo de sistema, escribe la capa inferior una vez para cada controlador. La capa superior es independiente de la plataforma. El código para la capa intermedia solo necesita escribirse una vez, pero los datos (la reasignación) necesitan rehacerse para cada plataforma / controlador.

MrCranky
fuente
Esto parece un enfoque muy limpio y elegante. ¡Gracias!
Básico
1
Muy agradable. Mucho mejor que el mío: P
Jordaan Mylonas
3
Muy bonita abstracción. Pero tenga cuidado al implementar: no cree ni destruya nuevos objetos de impulso para cada acción del usuario. Use la agrupación. El recolector de basura te lo agradecerá.
grega g
Absolutamente. Una matriz estática del tamaño máximo de impulsos simultáneos es casi siempre la mejor opción; ya que casi siempre solo quieres una instancia de cada impulso activo a la vez. En la mayoría de los casos, esa matriz solo tiene algunos elementos, por lo que es rápido iterar.
MrCranky
@grega gracias a los dos, no había pensado en eso.
Básico
1

Honestamente, diría que la interfaz óptima dependería en gran medida del uso en el juego. Sin embargo, para un escenario de uso general, sugeriría una arquitectura de dos niveles que separa los enfoques basados ​​en eventos y en encuestas.

El nivel inferior podría considerarse el nivel "basado en encuestas" y expone el estado actual de un botón. Una de esas interfaces podría simplemente tener 2 funciones, GetAnalogState(InputIdentifier)y GetDigitalState(InputIdentifier)donde InputIdentifierhay un valor enumerado que representa con qué botón, disparador o palanca está comprobando. (La ejecución de GetAnalogState para un botón devolvería 1.0 o 0.0, y la ejecución de GetDigitalState para un dispositivo analógico devolvería verdadero si supera un umbral preestablecido o falso en caso contrario).

El segundo nivel luego usaría el nivel inferior para generar eventos al cambiar el estado y permitir que los elementos se registren para devoluciones de llamada (los eventos de C # son gloriosos). Estas devoluciones de llamada incluirían eventos para prensa, lanzamiento, toque, larga duración, etc. Para un dispositivo analógico, podría incluir gestos, por ejemplo, QCF para un juego de lucha. El número de eventos expuestos dependerá de qué tan detallado sea el evento que desea enviar. Podrías disparar un simple ButtonStateChanged(InputIdentifier)si quisieras manejar todo lo demás en una lógica separada.

Entonces, si necesita verificar el estado actual de un botón de entrada o una palanca de control, verifique el nivel inferior. Si desea simplemente activar una función en un evento de entrada, regístrese para una devolución de llamada desde el segundo nivel.

Jordaan Mylonas
fuente