Player Input Management

Started by
6 comments, last by Danicco 10 years, 9 months ago

Hi,

I'm coding the input part of my engine and I'm looking for the best consensus on how it's done, if there is one, if there isn't, any good ways to handle it.

I was doing at first, for tests:


if (inputClass.IsKeyPressed(Keys.UpArrow))
{
    //Do something
}

But after noticing the unviability since the player must be able to define his keys, I changed to "actions" like this:


if (inputClass.IsAction(PLAYER_MOVEUP))
{
    //Do something
}

And I'd map each of my game actions to the keyboard keys or mouse, but now I'm trying to figure how can I handle multiple commands and maybe if it's a good idea to use a separate thread for it.

I'm thinking of a list of a "Command" class that keeps cleaning the keys after X time has passed, and creating functions such as:


if (inputClass.IsActionSequence(PLAYER_MOVEDOWN, PLAYER_MOVEDOWN_FORWARD, PLAYER_MOVEFORWARD))
{
   //Do something - Hadouken!
}

But I'm not sure if this is the best approach. Depending on the game I'd have to check for information on if the key's been just pressed, or it's being held, so I'd need to save each KeyState as well and I still thinking how it could work with the above example of action sequences.

Also, after reading a few posts in the net, I've found about the polling and callback methods, but not about which method is better or any sort of discussion about it (though I haven't looked that hard). Or about how to handle the delta time and the key time.

Anyway, there's a lot of possibilities and I'd like to ask for sources of information (updated, if there's any chance of some techniques I find around the net to be outdated already - happened a lot with graphics for me) about this from more experienced programmers.

Thanks!

Advertisement

if (inputClass.IsActionSequence(PLAYER_MOVEDOWN, PLAYER_MOVEDOWN_FORWARD, PLAYER_MOVEFORWARD))
{
//Do something - Hadouken!
}

If I decided to change the button sequence of the fighters' move during development at some point, the above code looks more read-able to me.

Sounds like a complex game. Have you considered using a more generic event system with an event bus?

The IO module could listen to the key events and store them in one keySequence object with a sequence map which is ordered and with the key identifier and the current state of the key (or a ring buffer of 5 key / key state pairs).

Now you'd have two options:

- fire a generic keySequenceChangeEvent on the event bus and let a complex eventListener react to the event

- let the IO module analyze the keySequence (create unique hash keys and lookup an event constant in a map), fire a specific userActionEvent and write several specialized (and more simple) eventListeners for those events

Guess in both cases that architecture would allow you to couple the modules more loosely and be more flexible later on. Several modules could react to a keyEvent without putting it all in the one "do something" block. They just need to have access to the event bus.

Given enough eyeballs, all mysteries are shallow.

MeAndVR

Jason Gregory's great book Game Engine Architecture has a section on combo detectors. I think I would map the raw mouse/key/gamepad input to game controls, such as move forward, up, punch. The combo detector listens to the input mapper. If the detector finds a match, it pushes the complex event to the players controller.

This would allow things like having the same sequence do different moves for each character(by giving different characters different controllers), or unlocking special moves(adding a new sequence to the button detector).

Jason Gregory's great book Game Engine Architecture has a section on combo detectors. I think I would map the raw mouse/key/gamepad input to game controls, such as move forward, up, punch. The combo detector listens to the input mapper. If the detector finds a match, it pushes the complex event to the players controller.

This would allow things like having the same sequence do different moves for each character(by giving different characters different controllers), or unlocking special moves(adding a new sequence to the button detector).

Sounds like a complex game. Have you considered using a more generic event system with an event bus?

The IO module could listen to the key events and store them in one keySequence object with a sequence map which is ordered and with the key identifier and the current state of the key (or a ring buffer of 5 key / key state pairs).

Now you'd have two options:

- fire a generic keySequenceChangeEvent on the event bus and let a complex eventListener react to the event

- let the IO module analyze the keySequence (create unique hash keys and lookup an event constant in a map), fire a specific userActionEvent and write several specialized (and more simple) eventListeners for those events

Guess in both cases that architecture would allow you to couple the modules more loosely and be more flexible later on. Several modules could react to a keyEvent without putting it all in the one "do something" block. They just need to have access to the event bus.

That's the original idea, I'll be having a class to deal with specific IO depending on the OS, they'd store the inputs and each input would be mapped to an action, and how I will deal with them is what I'm still having trouble with.

I don't know if it's high cost to constantly check, every game cycle in the main while loop, for all of the player's input.

For example, taking the game Street Fighter, should I do this?


ActionSequence* characterMoves = new ActionSequence[10];
//Fill the moves from a resource file in the array

while(gameIsRunning == true)
{
    //for every frame
    for(int i = 0; i < 10; i++)
    {
        //for every move
        if(characterMoves[i].Match(myInputSequence))
        {
            //if it matches the input, do the action
            characterMoves[i].CallAction();
        }
    }
}

Or is there a better way, say, maybe checking only when there's a new input? Or do developers put some time between each of these calls so it doesn't weight too much on performance?

There's also contexts, so I have to check if the game's on the menu mode, character select mode, or fighting mode for example. I'm not sure if it's a good idea to move these contexts and actions to a resource file or hard code them such as:


//hard coded mode I learned from some books a long time ago
GameState gameState;

switch(gameState)
{
    case (GameState::Title_Screen)
    {
        //check only for some actions
        break;
    }
    case (GameState::Character_Select)
    {
        break;
    }
    //etc
}

Or:


GameContext* gameContexts = new GameContext[3];
//Load from a resource file, say, "Title_Screen", "Character_Select" and "Fighting Mode"
//Each gameContext has an array of actions allowed, and they call other actions/functions

GameContext* currentContext = gameContexts[0]; //starts at Title_Screen

for(int i = 0; i < currentContext.ActionsLength; i++)
{
    if(currentContext.Actions[i].Match(myInputSequence))
    {
        currentContext.Actions[i].Call(); //I still need to figure how to map functions based on a string from a resource file, but that's the idea
    }
}

I'm guessing the second way may be better for developing, since if I want to add a new GameState I'd just add a few more lines to a resource file and call certain actions (and code them), but I also think it might be heavier on performance or code-wise (it could become unnecessarily complicated).

And lastly, it's about the KeyStates, my only experience developing games are from mini-games I made using XNA, and the way I learned was to have two "KeyState" variables, one from the previous frame and one from the current one, and they'd be updated each frame like this:


enum KeyState { Pressed, Released }
class Key
{
    KeyState previousKeyState;
    KeyState currentKeyState;
    char KeyValue;
}

//In my main loop
for(int i = 0; i < myInput.Keys.Length; i++)
{
    //moving the current state to the previous in the current frame
    myInput.GetKey(i).previousKeyState == myInput.GetKey(i).currentKeyState;
}

if(myInput.GetKeyState(Keys::UpArrow).previousKeyState == KeyState::Released 
   && 
   myInput.GetKeyState(Keys::UpArrow).currentKeyState == KeyState::Pressed) 
{
    //The player has *JUST* pressed the key
}
else if(myInput.GetKeyState(Keys::UpArrow).previousKeyState == KeyState::Pressed 
   && 
   myInput.GetKeyState(Keys::UpArrow).currentKeyState == KeyState::Pressed) 
{
    //The player is holding the key
}
else if(myInput.GetKeyState(Keys::UpArrow).previousKeyState == KeyState::Pressed 
   && 
   myInput.GetKeyState(Keys::UpArrow).currentKeyState == KeyState::Released) 
{
    //The player *JUST* released it
}

If I were to do like this, I wouldn't deal with "keys" but "actions" instead, however this is kinda old and I'm not sure if that's the best way to handle it.

I'm having some trouble because most of the stuff I learned some time ago seems outdated, I didn't knew or check my sources release dates and this was really painful with graphics and scene management, so I'm studying a little bit more about input before outlining my functions and defining them.

Sorry about the length of this post, any advice is highly appreciated!

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 unsure.png ... 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.

Given enough eyeballs, all mysteries are shallow.

MeAndVR

And lastly, it's about the KeyStates, my only experience developing games are from mini-games I made using XNA, and the way I learned was to have two "KeyState" variables, one from the previous frame and one from the current one, and they'd be updated each frame like this:

For complex combos it isn't enough; use state machines which, at every time step, can easily change state according to key press and release events and timeouts (hold a key for N frames, consider simultaneous any keys that are pressed within N frames of each other, etc.).

Omae Wa Mou Shindeiru

Thank you for the replies, I'll be leaving the input check for each object (such as the player, or some movable object he has access to), and will leave my InputClass just to manage the input from the OS and fill the arrays of keys, so it'll be easier to make each object manage itself.

Using timestamps will hopefully solve all input requirements any game may require.

Thanks again for all the replies!

This topic is closed to new replies.

Advertisement