Input handling design frustration

Started by
15 comments, last by SonicD007 10 years, 4 months ago

So I've been reading through this article and messing with it for close to a month (on and off because of work and school) and I understand it for the most part. I changed the code a bit so I could use SDL_Keycodes for the SetRawButtonState methods and whatnot and got all that working. My problem comes with the function pointer that InputMapper uses to do any actual work based on the input. Most likely my brain is just overworked and I can't properly think this through...

So, how would I have my gameObject handle the input that is received? I want the individual game object to act on the input that was received so how would I do this exactly?

Would I need to do something like this? If so, how can I also pass the struct that inputMapper uses that holds the information on the actions and states?

inputMapper.AddCallBack(gameObject.HandleInputFunctionPointer, 0);

P.S. I'm rusty with function pointers so that could be my issue.

Advertisement

There are multiple approaches to this problem, I'll list some here, just to give you something to think over.

When I was doing my very first prototypes that actually used input, I usually did a hard-coded check with an input class. This class, when detected something, called a function directly on an object that was derived from my PlayerObject class. It was really amateur, rude, but worked fine since I only had to worry if a button was up or down...

Later on, I decided to use this callback system. So, I had a function that read the input keys from configuration data somewhere (like JumpKey: KB_KEY_SPACE). With those configurable key values loaded, I registered callback functions on my input class.

The one I use on most of my projects today is a simple event system. Whenever a change is made on input (Let's say KB_KEY_SPACE is pressed), an event is generated (class EvtJump: public EvtInput) and sent to every registered event listeners. It has an more flexible implementation than simple callbacks, but some would argue it is less optimized. I wouldn't worry this much about it since mostly you'll have only a handful of events to handle every frame.

This last one is specially preferable since it is easier to create different input types like press and hold. With callbacks I usually messed my entire code up to do simple evaluations in order to call the correct callback...

I'm not saying it is the best approach to every case, but it suits my needs on most cases. Specially because I also use events on things in the game physics simulation and overall logic.

If you want some help on this approach, just shout


The one I use on most of my projects today is a simple event system. Whenever a change is made on input (Let's say KB_KEY_SPACE is pressed), an event is generated (class EvtJump: public EvtInput) and sent to every registered event listeners. It has an more flexible implementation than simple callbacks, but some would argue it is less optimized. I wouldn't worry this much about it since mostly you'll have only a handful of events to handle every frame.
This last one is specially preferable since it is easier to create different input types like press and hold. With callbacks I usually messed my entire code up to do simple evaluations in order to call the correct callback...

I've never programmed event systems before. I've used C# events with winforms and understood the concept, but I never sat down and tried to create something similar using c++.

My goal at this point is to have a system where if I press up, my character will move up. If I hold up and right, my character will move diagonally. Do you think you can post a sample piece of code using your event system just so I can see how you're handling the presses at the object level?

I'm interested in how other people have solved this issue.

Im not very good at explaining, so let me show you how i did with my game engine


// .h

class CVortez3DEngine {

	// some stuffs removed...

public:
	virtual void OnChar(UINT Ch){}
	virtual void OnKeyDown(UINT KeyPressed){}
	virtual void OnKeyUp(UINT KeyReleased){}
public:
	virtual void OnMouseDown(int ButtonPressed, int x, int y){}
	virtual void OnMouseUp(int ButtonPressed, int x, int y){}
	virtual void OnMouseMove(int x, int y);
	virtual void OnMouseMove(int x, int y, int XDisp, int YDisp){}
	virtual void OnMouseRoll(int RollCount){}
}

class CVortez3DEngineExt : public CVortez3DEngine {

	// some stuffs removed...

public:
	void OnChar(UINT Ch);
	void OnKeyDown(UINT KeyPressed);
	void OnKeyUp(UINT KeyReleased);
public:
	void OnMouseDown(int ButtonPressed, int x, int y);
	void OnMouseUp(int ButtonPressed, int x, int y);
	void OnMouseMove(int x, int y);
	void OnMouseMove(int x, int y, int XDisp, int YDisp);
	void OnMouseRoll(int RollCount);
};

// .cpp

CVortez3DEngine *pVortez3DEngine = NULL;

// ...

//-----------------------------------------------------------------------------
// Process the windows messages
//-----------------------------------------------------------------------------
LRESULT CALLBACK MsgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch(uMsg)
	{
	//Process quit message
	case WM_DESTROY:
	case WM_CLOSE:
		PostQuitMessage(0);
		break;	

	// some stuffs removed...

	// Keyboard messages, for text...
	case WM_CHAR:
		pVortez3DEngine->OnChar((UINT)wParam);
		break;	

	// Keyboard messages, , for keys...
	case WM_KEYDOWN:
		if(wParam == VK_ESCAPE && pVortez3DEngine->GetUseEscKeyToQuit()){
			PostQuitMessage(0);
			break;
		}
		pKeyState[wParam] = true;
		pVortez3DEngine->OnKeyDown((UINT)wParam);
		break;	
	case WM_KEYUP:
		pKeyState[wParam] = false;
		pVortez3DEngine->OnKeyUp((UINT)wParam);
		break;

	// Mouse move messages
	case WM_MOUSEMOVE:
		pVortez3DEngine->OnMouseMove(LOWORD(lParam), HIWORD(lParam));
		break;
	
	// Mouse buttons messages
	case WM_LBUTTONDOWN:
		pMouseBtnState[LEFT_MOUSE_BUTTON] = true;
		pVortez3DEngine->OnMouseDown(LEFT_MOUSE_BUTTON, (int)LOWORD(lParam), (int)HIWORD(lParam));
		break;
	case WM_LBUTTONUP:
		pMouseBtnState[LEFT_MOUSE_BUTTON] = false;
		pVortez3DEngine->OnMouseUp(LEFT_MOUSE_BUTTON, (int)LOWORD(lParam), (int)HIWORD(lParam));
		break;
	case WM_MBUTTONDOWN:
		pMouseBtnState[MIDDLE_MOUSE_BUTTON] = true;
		pVortez3DEngine->OnMouseDown(MIDDLE_MOUSE_BUTTON, (int)LOWORD(lParam), (int)HIWORD(lParam));
		break;
	case WM_MBUTTONUP:
		pMouseBtnState[MIDDLE_MOUSE_BUTTON] = false;
		pVortez3DEngine->OnMouseUp(MIDDLE_MOUSE_BUTTON, (int)LOWORD(lParam), (int)HIWORD(lParam));
		break;
	case WM_RBUTTONDOWN:
		pMouseBtnState[RIGHT_MOUSE_BUTTON] = true;
		pVortez3DEngine->OnMouseDown(RIGHT_MOUSE_BUTTON, (int)LOWORD(lParam), (int)HIWORD(lParam));
		break;
	case WM_RBUTTONUP:
		pMouseBtnState[RIGHT_MOUSE_BUTTON] = false;
		pVortez3DEngine->OnMouseUp(RIGHT_MOUSE_BUTTON, (int)LOWORD(lParam), (int)HIWORD(lParam));
		break;

	//Mouse wheel message
	case WM_MOUSEWHEEL:
		{
			int RollCount = (signed short)HIWORD(wParam) / WHEEL_DELTA;
			pVortez3DEngine->OnMouseRoll(RollCount);
		}
		break;
	// Process other messages
	default: 
		OnCustomMessage(uMsg, wParam, lParam);
		break;
	}

	//Return result to windows
    return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

//////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////

// int the file using CVortez3DEngineExt

//-----------------------------------------------------------------------------
// OnKeyDown event...
//-----------------------------------------------------------------------------
void CVortez3DEngineExt::OnKeyDown(UINT KeyPressed)
{

}

//-----------------------------------------------------------------------------
// OnMouseDown event...
//-----------------------------------------------------------------------------
void CVortez3DEngineExt::OnMouseDown(int ButtonPressed, int x, int y)
{

}

//-----------------------------------------------------------------------------
// OnMouseUp event...
//-----------------------------------------------------------------------------
void CVortez3DEngineExt::OnMouseUp(int ButtonPressed, int x, int y)
{

}

//-----------------------------------------------------------------------------
// OnMouseMove event...
//-----------------------------------------------------------------------------
void CVortez3DEngineExt::OnMouseMove(int x, int y, int XDisp, int YDisp)
{

}

All im doing is creating virtual functions, overriding them later with another class, and react to OnMouseMove(), OnKeyDown() ect...

If you don't declare them in the CVortez3DEngineExt class, it will use the default ones (empty functions) supplyed in CVortez3DEngine, so, you don't have to use all the events if you wish, they are optional, that's the great part about it.

Did you have the objects that needed to handle input register themselves to that class as a listener object and have that class somehow notify the objects that need to react on the input when the event was triggered?


Did you have the objects that needed to handle input register themselves to that class as a listener object and have that class somehow notify the objects that need to react on the input when the event was triggered?

I dont really understand what you mean by that. All im doing is to respond to windows inputs messages in the message pump of my application.

If i was to use the class "as it is" ie. CVortez3DEngine(), nothing would happen because

virtual void OnMouseDown(int ButtonPressed, int x, int y)

{

}

is an empty function.

The key here is the virtual keyword. When i extend the class CVortez3DEngine() using inheritance, i have the option to "override" those functions. Than mean if i do this

class CMyGame : public CVortez3DEngine {

public:

void OnMouseDown(int ButtonPressed, int x, int y);

}

now 2 things happen.

First, CMyGame can do everything that CVortez3DEngine can do.

Secondly, i created another function with the exact same name and arguments, but without using the virtual keyword.

That mean, when the call to OnMouseDown is made in the message pump, it dosen't call the empty function anymore, it will call the later one from CMyGame.

That might seem a bit confusing, but it's not that hard, maybe my example is a little bit overwhelming for your current programming skill, that's normal,

i was a beginer too a few years ago.

I understand the virtual functions you're using. What I'm trying to figure out is if you have something similar to this:

CVortez3DEngineExt::OnChar(UINT c)
{
Game::playerObject.HandleInput(c);
}

or if it's more like


CVortez3DEngineExt.TriggerEvent(OnChar('A'));

//player object gets a message notifying it of this event to handle input (event delegation? haven't programmed this before so not sure how it would look
playerObject.HandleInputEvent(CVortez3DEngineExt::SomeInputStruct);
//playerObject.HandleInputEvent would then look at the struct and do stuff based on that

I want to do something similar to the second code box I just wrote out. I want the player class to do something based on input. I don't want the input class to tell the player class what to do per say

To be honest i don't have anything special to handle inputs like your talking about, i just use the value. I guess for mapping keys an input handler would come in handy, yea.

Inputs are not events.
I reiterate from that post, game objects do not handle their own inputs. A callback system in which game objects assign handlers for certain inputs is not only a flawed approach in scope but convoluted and hard to debug/manage.


L. Spiro

I restore Nintendo 64 video-game OST’s into HD! https://www.youtube.com/channel/UCCtX_wedtZ5BoyQBXEhnVZw/playlists?view=1&sort=lad&flow=grid


I've never programmed event systems before. I've used C# events with winforms and understood the concept, but I never sat down and tried to create something similar using c++.

My goal at this point is to have a system where if I press up, my character will move up. If I hold up and right, my character will move diagonally. Do you think you can post a sample piece of code using your event system just so I can see how you're handling the presses at the object level?

I'm interested in how other people have solved this issue.

Sure thing, I'll scavenge my old code and post here soon. It'll probably be delayed a little, I am removing Ubuntu from all my machines due to those Amazon features. It's taking me quite some time...

This topic is closed to new replies.

Advertisement