Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

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

share|improve this question
add comment

2 Answers

up vote 19 down vote accepted

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.

share|improve this answer
    
This seems like a very clean and elegant approach. Thank you! –  Basic Sep 8 '11 at 21:50
1  
Very nice. Much better than mine :P –  Jordaan Mylonas Sep 8 '11 at 22:04
3  
Very nice abstraction. But be careful when implementing: do not create and destroy new impulse object for every user action. Use pooling. Garbage collector will thank you. –  grega g Sep 9 '11 at 15:15
    
Absolutely. A static array sized to the max number of simultaneous impulses is almost always the best option; since almost always you want only one instance of each impulse active at any one time. Most often that array only has a few items in it, so it's fast to iterate. –  MrCranky Sep 10 '11 at 16:06
    
@grega thanks both of you - I hadn't thought of that. –  Basic Sep 12 '11 at 12:52
add comment

Honestly, I'd say optimal interface would depend heavily on the usage in game. However, for a general usage scenario, I'd suggest a two-tier architecture which separates event based and polling based approaches.

The lower tier could be considered the "polling based" tier, and exposes the current state of a button. One such interface could simply have 2 function, GetAnalogState(InputIdentifier) and GetDigitalState(InputIdentifier) where InputIdentifier is an enumerated value representing which button, trigger or stick you're checking against. (Running GetAnalogState for a button would return 1.0 or 0.0, and running GetDigitalState for an analog stick would return true if over a preset threshold, or false otherwise).

The second tier would then use the lower tier to generate events upon state change, and allow elements to register for callbacks (C# Events are glorious). These callbacks would include events for press, release, tap, longhold, etc. For an analog stick, you could include gestures, e.g. QCF for a fighting game. The number of events exposed would depend on how detailed of an event you want dispatched. You could just fire a simple ButtonStateChanged(InputIdentifier) if you wanted to handle everything else in separate logic.

So, if you needed to check current state of an input button or control stick, check the lower tier. If you wanted to simply fire off a function upon an input event, register for a callback from the second tier.

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.