PDA

View Full Version : Generic Controller Classes


Goz
06-02-2006, 01:35 AM
With the onset of Wii and PS3 we're going to start suffering from tilt sensors and all sorts in games. This has left me wondering. Has anyone ever come up with a decent clean interface for handling all the different types of controllers that will need to be supported when doing cross platform games (alas, X-Box 360, PS3, Wii and PC all have different controllers that work in different ways).

I have set myself a project to come up with a truly generic way of handling all these different controllers. This has proved to be no mean feat (Oh why won't i just carry on doing graphics dev work? ;)). I will list out my idea below. Can anybody who has done things like this provide me with some feedback over what problem areas i may suffer?

Now if all axes, tilit controllers, buttons (analog or digital) are considered to be "Modifiers" could we set up a class Hierarchy that works as follows

Modifier (Base class)
DigitalButton : Modifier (Standard buttons)
AnalogButton : DigitalButton (Like the GC triggers)
AnalogAxis : Modifier
ClampedAnalogAxis : AnalogAxis (such as a joystick axis that goes from -1 to 1)

Each input "device" would then have specific class coded for it. That class would then poll for the data and then send it off through the classes above based from some sort of pre-defined map. Of course for a DirectInput style device the data above could be built on the fly and the map could be stored in memory (But i will ignore non specific device types for the moment).

Now this would mean that I would need to pass a float of some degree in to the virtual function on modifier which propogates through to the correct class. Then for digital data it could be converted so that 0 is off and anything none zero is on. ClampedAnalog data would be provided as 0 -> 1 and General Analog Axis could go from FLT_MIN -> FLT_MAX (for those who can move their mouse REALLY fast ;)).

This owuld all, then, be run by some sort of manager class that would detect the types of controllers attached to the machine and set up the relevant classes.

This way we'd have a BaseInputDevice that client code can query. For the more extended controllers it can then cast up to the relevant class and call the stuff directly.

Now can anyone see the flaws with this idea? Im very intrigued as to people's opinions and whether they can see any issues i've missed.

Cheers!

dave_
06-02-2006, 02:15 AM
Obiviously you've got a few common traits between controllers
You've got:
Digital or discrete inputs (in the form of buttons and hats ie on off)
Analogue or continuous inputs (such as stick or tilt)
Positions such as a (3d)mouse position - perhaps these could be represented by analog inputs.

Controllers arent very complicated. Thats why people dont abstract them further, they're simple concepts.

I mean you could treat a keyboard as a controller? After all its just a bunch of buttons. But why would you bother?

Jare
06-02-2006, 02:31 AM
What's the difference between an analog button and an analog axis? Does such a difference warrant a separate class? Consider for example a racing game: some people will want to use an analog trigger for throttle, whereas others may prefer the axis of an analog stick. Even more, the negative range of that analog stick might be used for braking and reverse gear, so the same axis would be controlling several different inputs.

I guess what I'm suggesting is that you try to create a unified interface to all these controls, rather than separating them in a hierarchy that will limit more than help:

ControlInput:
- Returns a scalar value (usually but not necessarily within [0..Range])
- Can be given a response curve, with some pre-made common curves (clamped min and max, scaled min and max, etc).
- Can be queried digitally against a "pushed" level and a "released" level.

Controller:
- gives a list of ControlInputs it contains
- their "natural" names ("left stick up", "B Button", "L2", etc)
- info about natural usage (physically analog or digital, etc) and grouping (these two form a self-centering axis).

On each platform you would likely hardcode the routing of a controller to your game input and setup screens, because somewhere, at some level, someone has to KNOW that you're dealing with a Dual Shock or a Wiimote.

Something along those lines...

juhnu
06-02-2006, 04:44 AM
We are handling buttons thru a general keycode-to-action mapping system, but for sticks and pads we have a following style interface.

class IJoypad {
public:
//joypads have two analog sticks always
virtual float2 GetPrimaryStickPosition() const=0;
virtual float2 GetSecondaryStickPosition() const=0;
virtual int2 GetPadPosition() const=0;
//possible force feedback methods here too
};

virtual size_t GetJoypadCount() const=0;
virtual void SetPrimaryJoypad(size_t index)=0;
virtual IJoypad& GetJoypad(size_t index)=0;
virtual IJoypad& GetPrimaryJoypad()=0;

Wernaeh
06-02-2006, 05:33 AM
Just another thought...

Why bother to seperate between analog and digital input devices ?

The way I'm currently going for is to keep analog values for both device types.
Then, I also see that all input values get clamped to the 0...1 range (for example via a user controllable sensitivity). Then, digital input will just produce an output of 1, whereas an analog stick also can set any value in between.

This allows for easily interchangeable controls, even when mapping a digital input to an actually analog action (i.e. movement).

The thought behind that is that the actual game need not know what input device actually is connected. For the game, everything there is are various input slots, identified by some enum, but all of them actually act the same.

Cheers,
- Wernaeh

Goz
06-02-2006, 06:29 AM
On each platform you would likely hardcode the routing of a controller to your game input and setup screens, because somewhere, at some level, someone has to KNOW that you're dealing with a Dual Shock or a Wiimote.


This is exactly what i mean, tbh. PC is another world of pain altogether ;)

I do quite like the idea of just having a simple input type that is float (or something). Surely, though, i'd need some kind of class that sits in between the value and the data, though, to handle things like "acceleration" when you hold down a digital button to avoid too jerky a response. What about when something simply want to know if a button is pressed ... I can't leave it to the game coders to write

if ( controller.GetButtonValue() > 0.5f )

for various legacy reasons. So i need to have some sort of class that handles mapping an analog button to a simple on/off. But then if im creating all these mapping classes isn't it exactly the same as just defining a pretty standard class hierarchy for each of these things?

Though taking simulating analog with digital buttons into account i'd need to re-work my inheritance structure a bit.

BaseModifier
BaseAnalog : BaseModifier
BaseClampedAnalog : BaseAnalog
BaseAxis : BaseAnalog
BaseAnalogButton : BaseClampedAnalog
BaseDigitalAsAnalogButton : BaseAnalogButton
BaseDigitalButton : BaseDigitalAsAnalogButton

That way everything would, eventually, come down to a simple analog floating point value. If you wish to use a digital button as an analog button then you can use the base class of the digital button to get that data. Then if everything is set up as a set of BaseDigitalButtons it will also have access to all the previous class data in the hierarchy ... though not too sure about this ... seems a tad fiddly.

I, alas, can't assume all controllers will have 2 analog axes (or any in fact) ... which is a problem and i need to maintain a legacy interface whereby the coder can just call "IsButtonPressed( eButton_1 )" kinda thing :(

dave_
06-02-2006, 06:48 AM
I, alas, can't assume all controllers will have 2 analog axes (or any in fact) ... which is a problem and i need to maintain a legacy interface whereby the coder can just call "IsButtonPressed( eButton_1 )" kinda thing :(

I'm just curious, but why not?


you'd make life a lot easier for yourself if you just decided to support modern controllers.

Goz
06-02-2006, 06:55 AM
I'm just curious, but why not?


you'd make life a lot easier for yourself if you just decided to support modern controllers.

I am ... not all controllers have 2 joysticks on them ... And in the future ... who knows?

Afterall, its not about making my life easier its all about creating an extendable architecture that will work for a while instead of 5 minutes like every other controller class i've ever used :)

bignobody
06-02-2006, 08:30 AM
I am ... not all controllers have 2 joysticks on them ... And in the future ... who knows?


Isn't this the reason we have the IDirectInputDevice::EnumObjects method?

Regards,

Goz
06-02-2006, 08:37 AM
Isn't this the reason we have the IDirectInputDevice::EnumObjects method?

Regards,

Yes you are quite right that is exactly why DX programmers have EnumObjects ... what if you aren't using DX ... im just trying to come up with a nice easy to use system that doesn't rely on a given API. DX is fine but there MUST be a more intuitive way of providing similar functionality ... surely?

Goz
06-02-2006, 09:04 AM
The way I'm currently going for is to keep analog values for both device types.
Then, I also see that all input values get clamped to the 0...1 range (for example via a user controllable sensitivity). Then, digital input will just produce an output of 1, whereas an analog stick also can set any value in between.

On reflection (ok ok .. implementation) im beginning to see why this is such a good plan.

Only problem is ...

As i see it i need the following flags.

ButtonPressed
ButtonReleased
ButtonHeld
Clamped

Clamped is then always limited to 0.0 -> 1.0 range and an axis counts as 2 analog "things" (Can't think of a better name ;)) for positive and negative. This means that every "thing" will take up 5 bytes. Presumably that won't align in memory well so it'll end up taking up 8 bytes. A PS2 controller, this, has 22 of these "things" and hence takes up 176 bytes. I guess thats not too bad im just slightly concerned about this adding up quickly ;)

I'm ending up using NULL objects a hell of a lot here so i can always return sensible values even when the controller type is asked for data it can't supply and so forth.

Goz
06-02-2006, 09:08 AM
Should add that i think ill probably handle the "smoothing" of digital being used as analog in the controller classes themselves ...

jkleinecke
06-02-2006, 10:02 AM
A PS2 controller, this, has 22 of these "things" and hence takes up 176 bytes. I guess thats not too bad im just slightly concerned about this adding up quickly ;)

176 bytes might be an issue if your code has to fit into 64k, but with modern PCs or consoles even optimizing everything down to a single on/off bit won't make any difference.

As for your input framework, something that I've done in my engine was to define a set of values that everything has to adhere to... ie. analog sticks will always be between -1.0..1.0, buttons will always be Pressed or Released. Button events only get fired as a button is pressed or released.

Soon I will begin work on an action mapping framework that will allow a 3rd party to define how to react to certain input types from my scripting language. That way I can have a fully configurable input system, and the user can change it to suit their liking.

Jare
06-02-2006, 10:58 AM
Mouse pointer coordinates are hard to map to a specific range... for example, when you want to support a captured mouse that moves beyond the window of the application.

monjardin
06-02-2006, 12:02 PM
What's the difference between ButtonPressed and ButtonHeld? You have ButtonReleased, but no ButtonNotHeld. ;)

I agree that normalizing axis ranges to [-1, 1] is the way to go. Otherwise, you will need a way to inform the client of the true encoder range. This also alleviates your need for tracking whether a axis is clamped.

If a mouse leaves the window then allow the value to exceed this range. For example, if your window is 800 pixels wide and the user moves the mouse 400 pixels right of your window, then return 1.5. Alternatively, you can report the mouse axis as the change in position over time. It depends on how you are using it (e.g. point and click versus FPS-style mouse look).

Jare
06-02-2006, 01:25 PM
ButtonPressed and ButtonReleased are the ramp events. ButtonHeld is the state. You normally derive them like this:

ButtonPressed = (PrevState ^ CurState) & CurState;
ButtonReleased = (PrevState ^ CurState) & PrevState;
PrevState = CurState;

Wernaeh
06-02-2006, 03:34 PM
As for your input framework, something that I've done in my engine was to define a set of values that everything has to adhere to... ie. analog sticks will always be between -1.0..1.0, buttons will always be Pressed or Released.

Also, even if clamping on the input side, it's always a good idea to clamp on the game logics side, too.
Consider a client sending misfit (hacked) data, such as nonnormalized controller values....

Cheers,
- Wernaeh