Advertisement

event handling (keypress)

Started by March 19, 2004 01:46 PM
15 comments, last by Luke Miklos 20 years, 6 months ago
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 ?
Whatsoever you do, do it heartily as to the Lord, and not unto men.
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]
Advertisement
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 classclass 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 classclass 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 eventclass 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 fifopublic:  // 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]
x = rx ( 1 - x ) ; r > 3.5699... and chaos may begin ...
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????
Whatsoever you do, do it heartily as to the Lord, and not unto men.
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 instanceMyGlobalClass&  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 initbool 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 ...
x = rx ( 1 - x ) ; r > 3.5699... and chaos may begin ...
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 ...
x = rx ( 1 - x ) ; r > 3.5699... and chaos may begin ...
Advertisement
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 listenersPostMessage(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!    
Sorry...Didn''t read close enough to realize that CEvent is not the MFC version!!
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().
Whatsoever you do, do it heartily as to the Lord, and not unto men.
Whats the MFC versus the wndproc approach????

This topic is closed to new replies.

Advertisement