I've got a XBox360 controller which I'd like to use as input for an application.
What I can't work out is the best-practice way to expose this via an interface.
Behind the scenes, the class which handles the controller(s) relies on polling button state.
I initially tried something link:
Event ButtonPressed() as ButtonEnum
where ButtonEnum
was ButtonRed
, ButtonStart
, etc...
This is a little limited in that it only supports button presses, not holds / patterns (press twice, etc.)
The next idea was to simply expose the button state to the app eg
Property RedPressed as Boolean
Property StartPressed as Boolean
Property Thumb1XAxis as Double
This is very flexible but really it forces too much work into the app and requires the app to poll - I'd prefer event driven if possible.
I considered adding multiple events eg:
Event ButtonPressed(Button as ButtonEnum)
Event ButtonPressedTwice(Button as ButtonEnum)
Event ButtonHeldStart(Button as ButtonEnum)
Event ButtonHeldEnd(Button as ButtonEnum)
but this seems a little clunky and was a real pain on the "Bind button" screen.
Can someone please point me at the "correct" way to handle inputs from controllers.
NB: I'm using SlimDX inside the class which implements the interface. This allows me to read state very easily. Any alternatives which would solve my problem are also appreciated
Answer
There is no one perfect mapping that gives you a platform specific abstraction, because obviously most of the identifiers that make sense for a 360 controller are wrong for a PlayStation controller (A instead of X, B instead of Circle). And of course a Wii controller is another thing altogether.
The most effective way I've found to deal with this is to use three layers of implementation. The lower layer is entirely platform / controller specific, and knows how many digital buttons and analogue axes are available. It's this layer which knows how to poll the hardware state, and it's this layer that remembers the previous state well enough that it knows when a button has just been pressed, or whether it's been down for more than one tick, or whether it's not pressed. Other than that it is dumb - a pure state class representing a single type of controller. It's real value is to abstract the nitty gritty of querying the controller state away from the middle layer.
The middle layer is the actual control mapping from real buttons to game concepts (e.g. A -> Jump). We call them impulses instead of buttons, because they're no longer bound to a particular controller type. It's at this layer you can remap controls (either during development or at runtime at the user's behest). Each platform has its own mapping of controls to virtual impulses. You cannot and should not try to get away from this. Every controller is unique, and needs its own mapping. Buttons may map to more than one impulse (depending on the game mode), and more than one button may map to the same impulse (e.g. A and X both accelerate, B and Y both decelerate). The mapping defines all of that, but is simply a list of inputs (in terms of buttons or axes) to outputs (in terms of digital or analogue impulses).
The upper layer is the game layer. It takes impulses, and doesn't care how they were generated. Maybe they came from a controller, or a recording of a controller, or maybe they came from an AI. At this level, you don't care. What you care about is that there is a new Jump impulse, or that the Accelerate impulse has continued, or that the Dive impulse has a value of 0.35 this tick.
With this sort of a system, you write the bottom layer once for each controller. The upper layer is platform independent. The code for the middle layer only needs written once, but the data (the remapping) needs redone for each platform / controller.
No comments:
Post a Comment