1
\$\begingroup\$

Im currently trying to setup my game and Ive started to implement input. It works like this:

I have an Input class who notifies the InputMapper whenever some key is pressed (or released or whatever). This InputMapper gets the keys pressed and returns an Action or a State depending on the current InputContext (Action and State are enums) (I used this article to get idea of how it should work).

Action here represents one-time actions like Jump while State represents continuous things like move_right.

I now attach an InputListenerto the InputMapper, which gets the Action or State whenever pressed.

In the game I use an entity-component-system. I have a ControlComponent, which should have variables that dictate what an entity should do and a KeyBoardSystem, which should set these variables. ControlComponent is then used by things like WalkSystem or JumpSystem.

My question now is: How would you implement these States and Actions in an entity-component-system? I know that States could be represented by booleans in the ControlComponent, but Actions? They could be events, but the ControlComponent doesnt (shouldnt?) have any method or know about the systems that use it.

Any help is appreciated.

\$\endgroup\$
  • \$\begingroup\$ I'm not sure if this would be useful, but Unity has an upcoming input system that seems to have some concepts in common with the article you cited. While Unity's approach isn't a strict ECS (their components have methods), their example might still provide some inspirations for how to break the problem into meaningful components / data objects. \$\endgroup\$ – DMGregory Mar 30 '17 at 22:24
1
\$\begingroup\$

What's the harm in allowing the input state for both actions & state to be managed by the input system directly and you merely expose methods for querying this state from other systems?

Your JumpSystem registers the Jump action with the InputSystem. Perhaps the registration is also accompanyed by the default key for SPACE. As a step in your game's startup, you load the player's mappings from a file to override the default input mappings with their custom ones.

At some future frame, your PlayerControllerSystem queries the InputSystem and checks whether the JUMP action is active. If the answer is true, then the player controller system initiates the jump sequence. This might include setting some booleans in the player controller component.

At the end of the frame, the InputSystem clears all actions and sets them back to inactive while leaving the current active status for States as-is so that carries over to the next frame.

In other words, I'm advocating polling input rather than pushing it. Why?

When input is pushed, now each system interested in input should cache those values so that later on when those systems update their state, they can react to that input. They shouldn't react to the input prior to because each system in the game loop typically must update in a deterministic order.

By polling input instead, we avoid having to force systems to maintain input state when it would be dispatched at the top of the game loop. Systems interested in current input state can obtain it when its needed for calculations and furthermore it allows that state to be cleared/managed cleanly from frame to frame.

UPDATE

I don't believe it makes sense to separate things like keyboard, mouse, and joystick into something called a system because these are technically input devices which should be managed by a system responsible for input.

That is why I mentioned an InputSystem, of which your InputMapper is a component of. The mapper is ultimately responsible for turning input events into actions or state changes.

Next, I mentioned PlayerControllerSystem because this is where input actions and state changes are translated into game world reactions. This system knows how to to turn the jump action trigger into a series of changes to game objects so that jump occurs. This might imply applying a boolean value on a component or coordinating the jump animation with the animation system and the movement/force needed to jump with the physics/movement systems.

Just like components, make sure not to create systems that are equally too granular in nature unless there really is a legit reason to do so.

This is a simplified example of my own engine:

class PlayerControllerSystem {
public:
  PlayerControllerSystem(InputSystem* pInput) {
    // Register an action in the category 'Movement' called 'Jump'
    // Assign the action to respond to the default key SPACE.
    mJumpAction = pInput->registerAction( "Movement", "Jump", Input::KC_SPACE );

    // Register a state in the category 'Movement' called 'Move Forward'
    // Assign the state to respond to the default key W.
    mForwardState = pInput->registerState( "Movement", "Move Forward", Input::KC_W );
    ....
  }

  void update(const FrameEvent& evt) {
    if ( mJumpAction.isActive() ) { 
      // initiate jump
    }
    if ( mForwardState.isActive() ) {
      // handle forward movement 
    }
  }
}

As you can see, I am not using events nor the observer pattern here. I believe its more efficient to simply have the input system gather and set state rather than dispatch to callbacks. There are most definitely places in your game loop where both synchronous and asynchronous events are useful, but I honestly do not believe that it makes sense to use them for input.

The system above shows how it tells the input system when the spacebar is pressed, toggle the mJumpAction as active. Internally, the input system maintains a data structure that relates Input::KC_SPACE as an action with the name Jump and category Movement.

What is the importance of this name/category? I use it to manage a tree display of category and named key mapping options users can change in a GUI screen inside the game.

During the update loop of that system, it uses the mJumpAction and mForwardState variables to check whether they're toggled.

Now your implementation of the input system might not return anything to the calling system if you'd rather not have variables. If so, all you simply need to do during the update loop would be to do something like:

if ( pInputSystem->isActionActive( "Jump" ) ) {
  // do jump logic
}

I prefer the variable approach for two reasons:

  1. I avoid having to use strings to lookup things during the update loop.
  2. I can have the input system return the same action/state should multiple systems register the same named/key-combo configuration and save memory.

Lastly, the input system registration could also be written to accept a sequence of values allowing for console game type reactions, like:

`A`, `A`, `B`, `Up`, `Down`, `Left`, `Right`.  

Much like how the input system determines when the key/state has been activated and sets it accordingly, the same holds true for sequences.

\$\endgroup\$
  • \$\begingroup\$ Thanks for the answer but I think Im confused. So I think you mean the KeyboardSystem with PlayerControllerSystem, right? Also I dont have an InputSystem, but rather the InputMapper, which converts raw input into actions and states. How would you implement your registration approach, would you do it with a simple Observer pattern and events, and how would you store the states and actions for the systems to be polled, simple arrays where a system would have to call if(activeInput.contains(JUMP)) //do stuff? Sorry if its obvious, just want to fully get your approach. \$\endgroup\$ – zebleckDAMM Mar 30 '17 at 21:15
  • \$\begingroup\$ @zebleckDAMM Please see my update to my post wrt your questions. \$\endgroup\$ – Naros Mar 31 '17 at 13:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.