• entries
570
2427
• views
216465

Untitled

236 views

Random posting: How to write a Combo-Event Manager.
[ Source: .cpp | .h ]

I'm going to simplify this to a single-button input system, not for any obvious reasons except that it makes drawing the input signals easier, and that the system can be easily built on to differentiate between buttons.

First, what exactly are we trying to make? A 'combo', for the purposes of this nonsense is a string of input (on a single signal) which represents a specific action. Say we have the following setup -

From this, you can probably ascertain what's going on. We have a single boolean signal representing the button state, and two registered 'combos'. Each '^' represents an event (change in button state).

The desired output is for the the 'Slash' combo to be triggered after the second event, and 'Double Slash' after the third. Each event can be qualified as such - what state its toggling to, and its length -

struct ActionEvent {	unsigned int state;	unsigned long length;};enum {	BUTTON_UP = 1,	BUTTON_DOWN = 2};

Now we need some method of describing our combos. Each combo is, essentially, a list of actions needed to be taken to execute (up up down down left right left right B A). Since we're dealing with a continuous signal, however, we also need to know the necessary length of the signal, ie, we want to differentiate between a short press and a long press. The easiest way to do this is by setting a range of 'accepted' lengths.

struct Action {	unsigned int state;	unsigned long minDuration;	unsigned long maxDuration;};

Okay, here's where things begin to get slightly complicated - the actual combo class. We want a class which has a list of Actions which need to be executed, and we need an easy way to know which one we're on. Well, an easy way to do implement this is with either a Queue or a Stack, depending on whether you want FIFO or FILO behavior. I went with a Queue -

class Combo {	typedef std::queue< details::Action > ActionStack;	ActionStack _stack;

Nevermind that I named it 'stack'. Expand your horizons - misname variables. Anyway.

We also need to deal with what happens when the Combo actually happens. Now, there's a lot of ways to do this (a lot a lot), but I just went with the easiest one - give each Combo a function pointer to a void(void) function to call when its queue is empty.

	typedef boost::function< void ( void ) > CallbackType;	CallbackType _callback;

boost::function, if you haven't used it, is absolutely *drool*. There's craploads of awesome things you can do with it and boost::bind, but I'm not going to explain them so much today. Just ... *huggle*.

Oh, right. Here's the rest of the member functions for the Combo class. They're all pretty self-explanatory, I think.

	void addAction( unsigned int state, unsigned long minDuration, 		unsigned long maxDuration );

Adds an Action object to the back of the queue.

	bool checkEvent( details::ActionEvent e );

Determines whether the passed event matches the necessities for the Action at the front of the queue.

	bool pop();};

Pops the front event off the queue and calls the function pointer if the queue is now empty, and returns whether or not it is empty.

There's one final part that we need - something to tie all this together and into your input system. But, actually, before that, erg.

Basically, you can probably already see that our Combo class instances are going to be mucked up - all of their member functions reduce themselves to nothing. The solution to this is to have two lists - a list of prototype combos which we never call pop on, and a second list of combos currently 'in progress':

class ComboListener :	public pain::core::KeyUpListener,	public pain::core::KeyDownListener{	typedef std::vector< Combo > ComboVector;	typedef std::list< Combo > ComboList;	ComboVector _comboPrototypes;	ComboList _activeCombos;

Also, you'll want to somehow glue this piece into your input system. I almost always use the Observer pattern (subscribe listeners to an event handler, etc), because it makes me warm and fuzzy inside.

In any case, we have our two lists of combos. Oh, and a way to add them in -

	void addCombo( const Combo& combo );};

The final thing needed to be discussed is what happens when the ComboListener gets passed an event - which I'm too lazy to type out here, so I'm just going to be lazy and paste in the commented code or something -

void ComboListener::raiseEvent( unsigned int state ) {	unsigned long _currtime = timeGetTime();	details::ActionEvent ae( state, _currtime - _lastevent );		// pass event to active combos, remove	for ( ComboList::iterator i = _activeCombos.begin();		i != _activeCombos.end(); ++i )	{		if ( !i->checkEvent( ae ) || !i->pop() )			i = _activeCombos.erase( i );	}	// generate any new active combos	for ( ComboVector::iterator i = _comboPrototypes.begin();		i != _comboPrototypes.end(); ++i )	{		if ( i->checkEvent( ae ) ) {			// pop the event, activate it if needed			Combo ce( *i );			if ( ce.pop() ) {				_activeCombos.push_back( ce );			}		}	}	_lastevent = _currtime;}

zomg. Anyway, this function gets called whenever the button gets pressed/released, and gets a variable passed to it indicating which it is (pressed/released).

First we go through the entire list of active combos, and see if the event matches each combo's next action. If it does, then we pop that action off the combo. We check to see if the combo is now empty so we don't end up with something bad happening.

Then we go through the combo prototype list and copy over any combos whose first action matches the current event (if it has more than 1 event).

So, in a nutshell, we transform a single-dimensional signal (a button) into a bi-dimensional signal (button and time) to extract more input from a single device.

What's next? Well, you don't have to restrict yourself to a single set of combos. If you build a state machine out of ComboListeners and have the combos alter the state of that machine, then you've got one hell of a complicated input system.

Imagry time - you've got a cute female knight. Her current state is standing, you tap the button twice, and she draws her sword. This changes her state, so now holding the button down (which would have previously made her start walking) draws back the sword.

And so forth and so on.

Just figured I'd post something in here, since I'm paying for it.

Thanks for describing this combo input system. I can use it to detect something like jabs (tapping a button) or wind up haymakers (holding the button down).

Yay! I'm happy to be helpful :3