What do you have in place already? With the appoach I suggested you could have several modules ...
a scene module, a render module, an input module etc. ... maybe make an abstract Module class that takes the eventbus in the constructor.
int main ( int argc, char** argv )
{
...
IEventBus *pEventBus = new CDefaultEventBus();
IModuleSettings *pModuleSettings = new CDefaultModuleSettings(settingsFilename);
pModuleSettings->init(argc, argv);
IModuleGamestate *pModuleGamestate = new CDefaultModuleGamestate(pEventBus);
pModuleGamestate->init();
IModuleScene *pModuleScene = new CDefaultModuleScene(pEventBus);
pModuleScene->init(pModuleSettings->getSceneSettings());
IModuleInput *pModuleInput = new CDefaultModuleInput(pEventBus);
// Uses settings from ini unless there is an override from commandline arguments
pModuleScene->init(pModuleSettings->getInputSettings());
IModuleRender *pModuleRender = new CDefaultModuleRender(pEventBus);
pModuleRender->init(pModuleSettings->getRenderSettings());
while (pModuleGamestate->isRunning()) {
...
pModuleScene->update();
pModuleRender->update();
}
return 0;
}
Then pretty much anything can be an eventListener ...
in CDefaultModuleScene there could be something like this:
bool CDefaultModelScene::addEntity(IEntity *pEntity) {
...
pScenegraph->register(pEntity);
pEventBus->addEventListener(pEntity);
...
}
The eventBus would iterate over all registered EventListeners and call their onEvent Method.
So the Player entity could manage it's own state ...
void CPlayer::onEvent(IBusEvent *pEvent) {
...
if (IModuleSettings::PLAYER_ACTION_EVENT == pEvent->getType()) {
if (!this->isIdle()) {
// Just to show what other uses the eventBus can have ...
pEventBus->fireEvent(new CPlayerActionRefusedEvent(pEvent));
} else {
switch (pEvent->getEventId()) {
case IModuleSettings::RIGHT_RELEASED:
setMovingRight(false);
break;
case IModuleSettings::COMBO_TURNINGFISTJUMP_STARTED:
startAnimation(CPlayer::TURNINGFISTJUMP);
break;
...
}
}
}
// like that you can also listen for other events ... like collision events that the scene manager fires ... and handle those the same way
else if (IModuleSettings::COLLISION_EVENT == pEvent->getType()) {
if (getID() == pEvent->getParamMap()->get(CCollisionEvent::PARAM_OBJECT_1)
|| getID() == pEvent->getParamMap()->get(CCollisionEvent::PARAM_OBJECT_2)) {
startAnimation(CPlayer::DYING);
}
}
}
Then the scene module would probably just iterate over all the entities and call their update function (unless the SceneGraph stores the position). Obviously Player would inherit update from IEntity here ...
void CPlayer::update(int timeElapsed) {
...
if (!isIdle()) {
...
updateAnimationState(timeElapsed);
if (canMove && isMovingRight()) {
updateLocation(timeElapsed, IEntity::DIRECTION_RIGHT);
} else if (canMove && isMovingLeft()) {
updateLocation(timeElapsed, IEntity::DIRECTION_left);
}
...
}
}
That means you don't have to poll anything ... the events on the event bus change the animation / player state instantly.
The trick is turning the key events into usable eventbus events. I remember previous / current keystate maps and lists ... a better solution might look something like this:
The Callback method that updated the list now calls a different Method of the input management module. The input manager stores only the last 4, 5 or 6 changes in key states.
class CModuleInput {
...
ISequenceEventIDMap *pSequenceEventIDMap;
IActionEventTypeMap *pActionEventTypeMap;
ICircularBuffer* pCircularBuffer;
...
void handleKeyEvent(char keyValue, KeyState keyState) {
pCircularBuffer.push(new CKeyValueStatePair(keyValue, keyState);
IBusEvent *pBusEvent = 0;
for (int nrConsideredKeys = pCircularBuffer->size(); i > 0; i--) {
int sequenceID = bufferToSequenceID(pCircularBuffer, nrConsideredKeys);
int eventID = pSequenceEventIDMap->get(sequenceID);
if (eventID != IModuleSettings::UNKNOWN) {
pBusEvent = new CDefaultBusEvent();
pBusEvent->setType(pActionEventTypeMap->get(eventID));
pBusEvent->setEventID(eventID);
pEventBus->fireEvent(pBusEvent);
break;
}
}
}
Now if there is a sequence like press down, press right, release down, release right bufferToSequenceID needs to return a unique ID for that sequence ... it will have a higher priority than the ID for just releasing right because the code looks for long sequences first.
The case: just released the right key would be detected if there is no other combo in the sequence until "bufferToSequenceID(pCircularBuffer, 2);" is called.
You might want to fire CTimeElapsed events and remove old CKeyValueStatePair from the the circular buffer ... to make sure the sequences need to be entered fast enough.
You might need to know if modifyer keys is pressed at some point ... but event then you can let listeners keep track of that ... and/or store it in a gamestate module for example.
Making the controls configurable would work by populating the lookup maps with the data from the settings module.
There are probably 1000s of errors. I don't really know C++ and I am not typing this in an IDE or anything ... I hope you get the idea.
Of course optimizations make sense ... not all entities need to look at and discard all events. You could have several eventBusses ... or several specialized fireXYZEvent Methods.