Archived

This topic is now archived and is closed to further replies.

Luke Miklos

event handling (keypress)

Recommended Posts

I''m in the process of modularizing a lot of code for a few programs (mostly games that use openGL) & I''d really like to modularize the keypress event handling. Depending on the game state, keypresses mean different things. Instead of checking for whether or not keys are down each frame in the main game loop, I''d like to add keyListeners for various objects, say... the main character would be listening for the keys that would tell it to move or jump & the game state module would listen for pause/resume or help-menu commands. I am aiming towards even more abstraction than this, but I think you get the idea... I would know exactly how to accomplish this in Java (don''t shoot me, I had to take this one class that uses java), but am clueless for C++. Any tuts or samples out there ?

Share this post


Link to post
Share on other sites
How funny. I'm working on the exact same thing, though not with OpenGL, and in C#. I'm not certain about any of this for C++, but I imagine you could capture the event or message (MFC or not) in the main application window. Have an array of function pointers that point to key handlers. Loop through the handlers until one of them processes the key and returns true.

Something like this:

typedef bool (*FuncPtr)(char);

FuncPtr functionlist[50];

AddListener(FuncPtr function)
{
functionList[count] = function;
}

ProcessKey(char key)
{
for(int i=0;i<50;i++)
{
if((*functionlist)(key))
return;
}
}



Not very sophisticated, but hopefully you get the idea. As you add objects to your application, you can add their key listeners.

[edited by - variant on March 19, 2004 3:33:16 PM]

Share this post


Link to post
Share on other sites
Com'on, you're talkting about C++, not C, so use classes:

First provide some interfaces (pure virtual classes, this
means one or more methods have the "= NULL" extension which
means it is NOT implemented here -> pure virtual):


// our event class - provide only the event id - all other

// required stuff must be implemented in a sub class derived

// from this virtual base class

class CEvent
{
public:
virtual int getEventID ( ) const = NULL;
};

// now we need a listener. This is only important because

// instead of using a C callback function we use this class

// to pass an event to it's receiveEvent method

// all classes which want to listen to an event must be

// derived from this class

class CListener
{
public:
virtual void receiveEvent ( CEvent* pEvent ) = NULL;
}


Ok, now we have the basics required for our event handling
system. Lets see how an event handler should look like:


// the event handler properties:

// - should have a list of listeners

// - able to add/remove listeners

// - it should be possible to call this handler from everywhere

// so one can trigger an event


class CEventHandler
{
int m_nListenerCount; // number of listeners

CListener** m_pListenerList; // list with listeners


int m_nEventFifoStart; // first queued event

int m_nEventFifoEnd; // first free position

CEvent** m_pEventFifo; // event fifo


public:

// allocates data - good style means to do this NOT in

// the constructor (-> here you can check for errors!)

bool initEventHandler ( );

// every listener registeres here (means: the listener

// is added to the listeners list in the event handler

bool registerListener ( CListener* pListener );

// remove a listener from the listeners list

bool unregisterListener ( CListener* pListener );

// call this to send an event to all listeners - can be

// i.e. a windows message in the WinProc function

// note: it's up to you if you handle events immidiately

// or queue them (see performEvents ())

void triggerEvent ( CEvent* pEvent );

// if you queue your events call this in your main app loop

// to handle the queued events

void performEvents ( );
};


Remarks: Fifo
first-in-first-out: first event triggered should be the first handled (i.e. a ring buffer)

Remarks: Listener List
instead of a normal C/C++ pointer list for the listeners
one could use a double linked list like the stl list
(important if the listeners often register and unregister)


I hope you have a slight idea of how it should work. I have
implemented this for my OpenGL framework and it works fine. I
use this as well for sending rendering and AI events as well
as for keyboard and mouse events ( = actions ).

If you have further questions, I can send you some working
source.

Lord Jake



x = rx ( 1 - x ) ; r > 3.5699
... and chaos may begin ...

[edited by - Lord_Jake on March 24, 2004 9:03:54 AM]

Share this post


Link to post
Share on other sites
Thanks variant for your reply, I can tell you have though that through very well in C#. & THANK YOU JAKE for your C++ explanation, that is more along the lines of what I am looking for. Perhaps we can get a bit more detailed... I''m going to rattle off a bunch of crap... & if you have the time, could you stop me & explain if I''m wrong or could do it a better way?

So in a broad sense... I would create a CEventHandler object upon initialization & in the windproc function (where else?) I would decode the events that windows passes me, & for every key/mouse event create an instance of a CEvent (mouse or key) & pass that event to the handler by calling triggerEvent(). The handler than does the rest, depending on specifics of implementation. fyi- I don''t think I will do Queueing of key/mouse events, because I would like the program to be VERY responsive to the user''s actions (does that sound wise?).

Now say for instance the only 2 events I''m worried about right now are KEY events (key presses) & MOUSE events (down,drag,up,move,clicked). The rest of the events can be handled as they are being handled right now. So I should design 2 classes that both derive from the "CEvent" class, for example the mouse class would contain a variable for mouse coordinates & the key class would contain a variable for what key was pressed.

Does this sound good... all of the widget classes in my GUI package (button, text line edit, num line edit, slider) could then inherit from the CListener class & they would each implement their own receiveEvent() method. For instance, if the user hits the ''N'' key & a button on the current panel has ''N'' for its hotkey, then when it receives the event, it could swallow the event & perform the action that it was created to do, perhaps.... start a "New game".

I would say more now... but I think I need to sit down with a pencil & a pad & start hacking out my own ideas... thanks for the start.

last Q - is the popular place to trigger key/mouse events from inside the winproc() function????

Share this post


Link to post
Share on other sites
Yes, you got it right - except for creating the class in the WinProc function.

If you want global access to a class a very common thing is to add a static method called "getInstance" which internally will create an instance of that class if it does not exist and return a reference to that class.
In your normal init function of your application you just add
a call your global class'' init method.


class MyGlobalClass
{
protected:
// define constructor and copy constructor protected so

// that they cannot accessed from outside the class

// -> ensures that the only way is to call getInstance ()

// and there really only exists ONE instance of it


MyGlobalClass ( );
MyGlobalClass ( const MyGlobalClass& );

public:

// destructor still must be public:

virtual ~MyGlobalClass ( );

// define the static access method

static MyGlobalClass& getInstance ();
};

// implementation of getInstance is very simple:

// return value: reference to the global instance

MyGlobalClass& MyGlobalClass::getInstance ()
{
// first access here the global (static) instance

// is created

static MyGlobalClass g_aInstance;

// because g_aInstance is static we can return a

// reference to it (-> it is NOT local!)

return g_aInstance;
}

// and in your app''s init

bool CMyApp::init (....param....)
{
...

// add a call to the init function of your global

// class - this first call will create the instance

// as well

MyGlobalClass::getInstance ().init ();

...
}

// btw, this would be a thing for macros

// GET_MY_GLOBAL_CLASS().triggerEvent ();

#define GET_MY_GLOBAL_CLASS MyGlobalClass::getInstance


About the CEvents for mouse and keyboard. You could also create a CWindowsEvent class which passes the LPARAM and WPARAM of the windows message - thats up to you.

Oh, two more things to think about:
- Multiple modes:
If you think about an Input Event Handler for keyboard and mouse, think about adding special "modes" for which the listeners are registered. These modes could be a "menu" mode and a "game" mode for example. Sure you want to react different on a mouse click in menu mode than in game mode. You could create a listener list for every mode and just send to the list of the current mode (-> you wont need to register/unregister every time your game switches between menu and normal game).

- instant events:
As you wrote you want to pass your keyboard and mouse events directly without queuing. You can create a second triggering method "triggerInstantEvent( CEvent* )" to trigger those keyboard and mouse events to be handled immidiately and use the other method for normal events which can be queued. Queuing can be very useful if you want to split the cpu time your program has (i.e. one part rendering, one part game actions, on part artificial intelligence).


x = rx ( 1 - x ) ; r > 3.5699
... and chaos may begin ...

Share this post


Link to post
Share on other sites
One remark:

If you use the code above you have to know that the instance will be created the first time you call getInstance () and will be release on the exit of your program.

If you want to create it and destroy it at your will, you will need to do this by a pointer.



// global pointer (-> set to NULL at start, IMPORTANT)

MyGlobalClass* g_pMyGlobalClass = NULL;

class MyGlobalClass:
{
protected:
...define ctor and cctor...

public:

static MyGlobalClass* globalGetOrCreate ();
static void globalFree ();
};

// implementation:

MyGlobalClass* MyGlobalClass:globalGetOrCreate ()
{
// check if instance already exists

if (g_pMyGlobalClass == NULL)
{
// no, create instance

// ctor can be accessed from inside the class'' implementation

g_pMyGlobalClass = new MyGlobalClass ();
}
// return pointer to this class

return g_pMyGlobalClass;
}

void MyGlobalClass::globalFree ()
{
// check if we have an instance

if (g_pMyGlobalClass != NULL)
{
// delete instance

delete g_pMyGlobalClass;

// set back to zero (IMPORTANT)

g_pMyGlobalClass = NULL;
}
}




x = rx ( 1 - x ) ; r > 3.5699
... and chaos may begin ...

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
Sorry if I''m incorrect in this, but I believe CEvent is MFC which is kind of incongruous with the wndproc approach. Maybe instead you could define a user message and post to it:


#define MY_KEY_MESSAGE (WM_USER + 0x234)
#define MY_MOUSE_MESSAGE (WM_USER + 0x235)

//...in your wind proc, when you receive the mouse
// and key messages, have a mechanism to
// loop through your listeners
PostMessage(Listener->hWnd,MY_KEY_MESSAGE,someWParam,someLParam);

//...The params could be pointers to structures containing
// your data...

//Or create a class that you pass the params to that posts the
//messages for you in a similar manner.

[\code]

I don''t know if there is an event structure purely for Win32. Sorry if this is wrong or useless!

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
Sorry...Didn''t read close enough to realize that CEvent is not the MFC version!!

Share this post


Link to post
Share on other sites
I think I just wrote that little poorly, I didn''t mean that I would instantiate my eventHandler object inside of the windproc() function, but rather I meant that thats where I would decode the key/mouse events & pass them to my handler with the triggerEvent() call. The thing you described... about creating only one instance, is that called a Singleton ?

-modes-
yes yes yes... I was just gonna ask... "what would be a good way to switch between modes?" (game mode & menu mode)... I was pondering the idea of adding & removing listeners every time, but I think what you said about having 2 distinct list of listeners would be better.

In fact, wouldn''t it a be a good idea to just have 2 seperate event handler objects, one for game mode & one for menu mode (& perhaps others for other modes). Since we are going for modular, the purpose of the event handler itself is just to traffic events to all of its listeners (& maybe perhaps distinguish between the listener with the focus & other listeners, like... a highlighted menu item would have focus). I don''t know what I think about the event handler itself having 2 seperate lists... because then it relies on knowledge of the outside world to perform its task (the knowledge of what mode you are in). But I suppose you would have to manage the mode somewhere... whether you manage the lists in the eventhandler, or whether you manage different eventhandlers outside.

Question on organization for the game state/mode. The user should begin the program in menu mode & should be able to go back & forth between game/menu mode rather easily. Certain menu selections could either
A) alter the game state (ex: diff level) & thus begin a new game.
B) change settings (ex: sound level) & then resume the game.
C) others such as quit or go to high-score screen.
Would it be a good idea to have a game class, that contains a level class, entity classes for each entity, yada yada yada...

So some of the event listeners (such as these menu items) would have to have some sort of access to the game state, & then whenever a menu control is activated that puts us back into game mode, the game would be loaded : level, sounds, starting positions, entity controls (add listeners based on control settings).

say for now I only have 2 game modes.
So maybe this....
There is a MOTHER_CLASS that passes itself to some of the event listeners & all that the event listeners know about this MOTHER_CLASS is that is has such methods as:

activateMode(modeType MODE);

& inside methods like this one, the MOTHER_CLASS performs such operations as:
switching eventHandlers (game/menu),
clearing the event Queue in the handler
(in case it had some from last time it ran)
loading graphics & sound (game or menu stuff)
blah blah blah

I can almost picture organizing it like this, that way the mother class does all the thinking for the various modes, & it passes itself to the listeners that it wants to, so that they can tell the mother class to perform certain methods like activateMode().

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
Whats the MFC versus the wndproc approach????

Share this post


Link to post
Share on other sites
@anonymous:
it is NOT MFC. It''s just an idea of handling events in a more eneric way than using the windows messages.

@Luke:
If you use your event handler a bit more generic, you will see that you dont need pass the mother class to all listeners. Instead trigger an event SWITCH_KEYBOARD_MODE and your mother class is a listener as well which reacts on that event?
Events are perhaps more powerful than you might think of.

And about multiple game modes: If you want generic and without outer information in your class, just make a dynamic list of lists of listeners (list m_pModesList). The you could add a method like "registerKeyboardMode(int nModeID)" and you could dynamically add as much keyboard modes as you wish - without the class having any game logic.

btw, afaik one would call it a singleton, yes.



x = rx ( 1 - x ) ; r > 3.5699
... and chaos may begin ...

Share this post


Link to post
Share on other sites
I understand exaclty what you mean about not having to pass the mother class to each event listener, & I agree. What about this special case:

The user presses the enter key, this event is decoded & sent to the mother class, the mother class turns around & sends the event to the current game mode''s event handler. You happen to be looking at the menu & the highlighted item (with the focus) is the button that allows you to START the game. Now normally the enter key isn''t an event that tells the mother class to switch game modes... therefore... the button knows what its supposed to do when its activated, but nothing else does. The button''s action needs to be given to it when you create the button & add it to the GUI class. I think this makes sense... a little ???

Share this post


Link to post
Share on other sites
Hey, you wanted to be generic: a button does not have any logic.

If you want to handle a menu, you create a menu class which registers some keys for itself.

So if you press a button on your gui, it''s decoded and triggers an event which is send to your menu class. This class decodes which button it was and what''s his purpose.

In your case you would have a gui with some buttons, a menu class which has registered the key events for "item up" (key up), "item down" (key down), "execute item" (which could be ).

- Once the gui records a button been pressed it would send a "button pressed" event. That one is captured by the menu class and interpreted as "select item

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
Hahahah, I forgot about the html crap,

HEY EVERYBODY, CHECK OUT MY <button> BUTTON </button>

woo hoo : )

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
crap, its not for APs I guess, I''m retarded

Share this post


Link to post
Share on other sites
Thats exactly what i was looking for.
a nice event handling system.




Lazzar

[edited by - Lazzar on June 1, 2004 4:23:56 PM]

Share this post


Link to post
Share on other sites
so as lord_jake described it...

The "menu" class contains all the information about what happens when a certain button is pressed...
so when you add a button to the menu... you also have to add an action to perform when that button is pressed... those things go hand in hand... why is it modular if only the menu class knows what actions are performed & not the button''s themselves? the fact that when the button is clicked or activated... that it performs some action... is intrinsic to the button. you don''t have to actually code the action into the button class... you can have a function pointer & some method like this:

GuiButton::addAction(action* newMethod) {
myActions[numActions++] = newMethod;
}
GuiButton::clicked(void) {
for(int i=0; i< numActions; ++i ) {
myActions[i]();
}
}

is this possible?
is this not modular then? maybe I want the buttons to have to much power... the way I think about it:

- Each button is created as a generic Gui object that can receive events.
- It eats the event if its a mouse click that falls inside of it, or if its a hotkey, or perhaps the activation key & the button''s focus property is currenty true (highlighted item).
- When it eats an event, it performs all the actions its supposed to, those actions can be added dynamically (using something like the code above).

PS - you know you''re a coder when you start punctuating sentances with a " ; "

Share this post


Link to post
Share on other sites