Game Engine :: Entity Component Design - Handling Input

Started by
11 comments, last by L. Spiro 9 years, 7 months ago

Hi,

first of all: great site!

I am currently creating a game engine for educational purposes.

ECS Design Pattern

The first design pattern I've included is the Entity/Component/System pattern. Therefore I've got a base SINGLETON class called cEngine, which contains all systems(PhysicsSystem, GraphicsSystem, etc.) so it can update them on each frame.


void cEngine::Run() 
{
    while( m_Run ) 
    {
        m_Timer.Update();
        for(int i = 0; i < m_Systems.size(); ++i)
        m_Systems.at(i)->Update();
        //NOTE: I will add a SystemManager to update them in the proper order
    }
}

Also, I can create new entities(actually just IDs) and add different components(position, velocity, renderable, ...), which then get saved in the right system. So for example, the position and the velocity components will be saved in the physics system, as this one is responsible for modifying those values. Other systems, however, may have a pointer to those components, because they may need those values(the graphic system for example needs the positional information, but won't modify it).

State Design Pattern

The second design pattern I use is a state system:


class iState 
{
    virtual void Init() {}
    virtual void Quit() {}
    virtual void Update() {}
    virtual void UpdatePause() {}
    virtual void Obscuring() {} //call when another state gets pushed
    virtual void Revealed() {} //call when upper state gets poped
private:
    bool m_isActive; //true, when it is at the top of the state stack
};

The user will be able to create customable states(overwrite the virtual functions), which then get pushed onto the StateStack. Those functions will be called properly for every state by the StateManager class.


class cStateManager 
{
    void Init();
    void Update();
    void Quit(); 

    void PushState( iState* state );
    void PopState();
    void ChangeState( iState* state );
private:
    std::vector<iState*> m_stateStack;
};

Questions

  1. First of all: Is this the right choice? In my opinion this design is pretty good, as it is easy for the user AND it SHOULD have good performance. However, I appreciate suggestions!

  2. My second question is related to Input Handling. My design would've been a SINGLETON Input class.

    
    class cInput
    {
        void Update();
    
        bool wasKeyPressed(int key);
        bool wasKeyReleased(int key);
    
        bool isKeyDown(int key);
        bool isKeyUp()int key);
    
    private:
        //...
    }

Once again, the engine class would Update() this class every frame, so it would always have the right key information. The huge benefit by not handling this class as a system, but as a segregated singleton class is, that the user simply can create a new state, create some entities in it, and easily handle input in the virtual Update() function of that state. E.g. something like this:


//overwriting virtual state functions
void Init(){
    id = cEngine->AddEntity();
}

void Update(){
    //add some components, bla bla...

    //simple input handling
    if(cInput->IsKeyDown(KEY_W)){
        //do something...
    }

    if(cInput->WasKeyPressed(KEY_A)){
        //do something
    }
}

As I've already said, in my opinion this "hybrid" design, which does not see the Input as another ECS system, but as a "independent" class is better and much more comfortable for the user than a "input component". However, I haven't seen this pattern on the internet, which makes me a little bit insecure. Did I miss one huge benefit of an input component? Or a huge disadvantage with this approach?

I would really appreciate any answers, as this bothers me for a few days now. Thanks!

Advertisement

For input handling, I thought this article was pretty good.

You seem to emphasize that cEngine and cInput are singletons as if that's a good thing. What benefit does it give you? (hint: none).


The huge benefit by not handling this class as a system, but as a segregated singleton class is, that the user simply can create a new state, create some entities in it, and easily handle input in the virtual Update() function of that state.

It seems like you've chosen this design because you want to have a bunch of entity logic in your "states", correct? If your game logic instead was all in the systems and/or on scripts attached to the entities, then you wouldn't need this. Input could be expressed as components that map the actual input to some "action", and then another component that maps "action" to some code to run/script/etc (and then a system that operates over this). I'm not saying that's necessarily a better way (it might be overkill for your scenario), but it would be more flexible.

The second design pattern I use is a state system:

UpdatePause() is superfluous. Pausing is not implemented this way.
There are 2 types of timers: actual and virtual.
When you update a cTimer object (which has both types of time values in it) you pass a boolean that indicates whether or not the virtual time values should be updated as well. If not, these time values stop increasing, they naturally produce a 0 delta since the last frame, and all objects (the objects in the scene) stop moving. Meanwhile menu items and graphics effects use the actual time values and continue moving.


First of all: Is this the right choice? In my opinion this design is pretty good, as it is easy for the user AND it SHOULD have good performance. However, I appreciate suggestions!

It is fairly close to what I do (the most obvious exception being in relation to pausing).
General Game/Engine Structure
Passing Data Between Game States
I also use a stack of states internally, which I did not mention there (except in reply to a comment), but I have yet to find a use for it. It certainly has solid theoretical uses, such as handling the mandatory home menu screen on Nintendo Wii, etc., so I keep it around.

You would also want to implement the following:
		/**
		 * If a state is not on top of the state stack, it is not ticked normally.  If the state flags
		 *	itself as non-interruptable, however, it will be ticked every frame regardless of whether
		 *	it is on top or not.
		 *
		 * \return Return true to make this state interruptable.  The default behavior is true.  States
		 *	that need to run online or continuously in the background for any reason should return false.
		 */
		virtual bool __stdcall		iState::Interruptable() const { return true; }

My design would've been a SINGLETON Input class.

If you are using singletons you are doing it wrong.
First you should be running your game on a thread separate from the input thread in order not to cause input delays.
Then each window (which will likely be only 1) should have some kind of input eater which gets and stores input data along with timestamps.
On the game thread for each logical update you eat from that the proper amount of inputs required to bring your input-processing up-to-date with your current logical timestamp.
This means if the input thread has logged input from time 0 to time 64 and your logical steps are 32 units each, the first logical step reads input from 0 to 31, the next reads from 32 to 63, and 64 will be read on the next logical update.
I have described this system in detail many times.
http://www.gamedev.net/topic/641102-frame-independent-movement-and-slowdowns/#entry5049391

Once the game thread has the correct input for each logical tick, it begins processing, translating, a lot of the things mentioned in the article to which phil_t linked (translating, etc.), etc.
And it is done at a specific time in the game loop.

cInput as a class violates the single-responsibility principle.
Of course the “manager” type class that lives on the window on the input thread that catches all the WM_* messages will know what all types of inputs are (mouse, keyboard, controllers, etc.) but it is just a data-storage mechanism.
When it comes time to process this data you pass it off to cKeyboard, cMouse, cController, cTouch, etc.

There is a lot more to processing input than just checking if keys are down—you have to determine if a key was just pressed or just released (as if this logical update), you have to keep a backlog of (for example) 5 seconds worth of keys (or the last 100 keyboard events, last 300 mouse events, etc.), you have to be able to tell how long a key is/was being pressed, and in the case of mouse moves and touch events you have to keep a log of all the drags and then use splines to smooth them out and interpolate between the last 2 events (at least as an option).

Clearly cInput is not made for this; this workload should be divided between classes designed to do it all (cKeyboard, cMouse, etc.)


Once again, the engine class would Update() this class every frame, so it would always have the right key information.

That’s not how you get accurate and up-to-date key information. Read above, and keep in mind that in any case you are going to be using a fixed time-step, either from the start or later once you realize all the problems with not doing so.
Since inputs are only processed on logical updates, which are slowed typically to around 30 times per second (the rule is to use the lowest speed that gives you a stable physics/game simulation and input handling (bearing in mind that most twitch shooters are updating logic at only around 30-40 times per second, so input performance is the lesser concern between them)), the concept of “updating every frame to ensure correct input” doesn’t make sense.


The huge benefit by not handling this class as a system, but as a segregated singleton class is, that the user simply can create a new state, create some entities in it, and easily handle input in the virtual Update() function of that state.

I don’t get what you mean.
Using a singleton implies “only one” (and, again, singletons also imply incorrect design).
States should inherit from cState and yes inside their Update() method they can handle/distribute input as they please (though usually a common distribution class would be utilized to avoid rewriting code in each state), but that has nothing to do with singletons/globals.
When the user provides his or her own new states for a game, he or she must also inherit from cStateFactory and override cStateFactory::CreateState( u32 _u32Id ), utilizing the factory pattern to create regular instances (not global/singleton instances) of custom states that will be handed over to the state manager (which is a regular instance owned by cGame (or whatever, but there are no globals/singletons)).


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

Thank you for this great reply L. Spiro and phil_t!
I still have some questions, but first I want to make something clear:

I don’t get what you mean.
Using a singleton implies “only one” (and, again, singletons also imply incorrect design).
States should inherit from cState and yes inside their Update() method they can handle/distribute input as they please (though usually a common distribution class would be utilized to avoid rewriting code in each state), but that has nothing to do with singletons/globals.
When the user provides his or her own new states for a game, he or she must also inherit from cStateFactory and override cStateFactory::CreateState( u32 _u32Id ), utilizing the factory pattern to create regular instances (not global/singleton instances) of custom states that will be handed over to the state manager (which is a regular instance owned by cGame (or whatever, but there are no globals/singletons)).
I think you misunderstood me. I've never wanted states to be singletons. As you already said that would be just stupid^^
I meant that the singleton design for the INPUT class would be great, because then the user can get (and process) input information really easily inside of a state INSTANCE simply with Input->IsKeyUp(key) and thats it. Nevertheless, after reading your post, I now realize how much better your approach is.
Questions:
1.) So, you stated that the cTimer should have a virtual time value and an actual time value. I don't exactly know what a virtual timer is, but I guess it is similar to the actual timer, but in addition you can "manipulate" it(e.g. stop updating it).
Now to my question: Wouldn't it be better just to ignore the states, which aren't on top of the stack rather than processing them with the deltaTime value zero? Then you would save processing time or did I get the whole virtual-timer-thing wrong?
2.) What exactly do you mean with using a state of stack internally?
3.) Why is UpdatePaused() a bad approach? Maybe the user would like to process some other stuff(not the stuff in the original Update() function) when the state is not on the top of the stack?
4.) Why exactly are the logical updates only processed 30-40 times per second? Is this still true when the game runs at 60 fps? Then those "extra" frames wouldn't make much sense or do I miss something again?

Now to my question: Wouldn't it be better just to ignore the states, which aren't on top of the stack rather than processing them with the deltaTime value zero? Then you would save processing time or did I get the whole virtual-timer-thing wrong?

No. Just because they are paused it doesn’t mean there is no reason to run updates and render. What if you need the results of the bottom state to display the pause menu like in Conker’s Bad Fur Day?
As I said, if the state doesn’t update when it is not on top, iState::Interruptable() returns true.

What exactly do you mean with using a state of stack internally?

A stack of states. The same thing you have.

Why is UpdatePaused() a bad approach? Maybe the user would like to process some other stuff(not the stuff in the original Update() function) when the state is not on the top of the stack?

There will always be copy/pasted or otherwise rewritten code.
A state can be update in exactly 1 location. Never more. If they want to do something else for pausing they can simply branch inside the Update() call.

Why exactly are the logical updates only processed 30-40 times per second?

To reduce overhead.

Is this still true when the game runs at 60 fps?

Yes.

Then those "extra" frames wouldn't make much sense or do I miss something again?

Fixed-Time-Step Implementation


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 have some notes related back to the OP:


Also, I can create new entities(actually just IDs) and add different components(position, velocity, renderable, ...), which then get saved in the right system. So for example, the position and the velocity components will be saved in the physics system, as this one is responsible for modifying those values. Other systems, however, may have a pointer to those components, because they may need those values(the graphic system for example needs the positional information, but won't modify it).

I don't know exactly the matter of your game, but I advice to manage object placement separate:

a) Not all objects are dynamic;

b) not all dynamic objects are physics controlled (in fact, usually the animation system drives objects more often than physics does; although constraints like parenting may do);

c) there is a need for spatially relation checks in many sub-systems: collision detection, proximity detection, perhaps sensing, ... all of them not interested in physics but just the placement.

In summary, I think these are reasons to have a sub-system that deals especially with placement and spatial relations, and that also physics is just a client of it.


1. First of all: Is this the right choice? In my opinion this design is pretty good, as it is easy for the user AND it SHOULD have good performance. However, I appreciate suggestions!

Both, ECS with sub-systems and game states as objects, are high level architectural decisions. They alone tell nothing about performance. Especially game states as such are called once or so per frame. Hardly a bottleneck. The real meaning comes with the solutions of the low level problems: How and how often are relations between components resolved? Are data of components stored in one or possibly more batches (SoA), and is consecutive processing possible? Things like those.

Nevertheless: IMHO using an ECS is a Good ThingTM, and providing objectized game states (if made correctly), too.


2. My second question is related to Input Handling. My design would've been a SINGLETON Input class.

As L. Spiro already has answered: In the general case your sub-systems need to be able to look not at the current state but the history as well.


... The huge benefit by not handling this class as a system, but as a segregated singleton class is, that the user simply can create a new state, create some entities in it, and easily handle input in the virtual Update() function of that state.

...

As I've already said, in my opinion this "hybrid" design, which does not see the Input as another ECS system, but as a "independent" class is better and much more comfortable for the user than a "input component".

Look at ECS as a tool so solve a specific kind of problem. Don't try to enforce it on every part of the game, just because it is there anyway. ECS is for game objects. Terrain, for example, doesn't fit very well. And input is much more away from game objects than terrain is.

However, ECS is the usage of a general concept especially for game objects. The general concept is "compositing" (besides "inheritance"). There is no reason to use compositing not also in other environments than game objects.
Coming back to input as components: After input is collected, timestamped, unified, and provided to the game loop, some sub-systems need to deal with it. When speaking of components as input processes, one usually mean something like "CharacterController". Such components are meaningful w.r.t. detecting input situations and mapping detected ones to abstract input, e.g. in the form of commands which are to be processed further by animation / physics or whatever. For example, the input situation "key W hold down" may yield in the command "forward move". This may seem to be overkill at the moment, but it has several advantages:
a) Different input situations can yield in the same command (keyword "alternative input");
b) it allows for customized input configurations;
c) it allows game objects to be controlled by other sources; e.g. the PC by a controller and an NPC by AI, and both sources send the same commands.

Thank you for your replies! Sorry I couldn't answer earlier but I didn't have much time.

Anyway, I've got a question relating @haegarr 's post:

The InputSystem gets the raw input data. The Controller components then map this data to "events" such as MoveForward, Attack, etc...

Now, how should those events get handled? Maybe the user would like to tell the PhysicsSystem to add a value to the entitiys position when the MoveForward event gets created. But maybe he would also like to create a new entity in the current state.

I'm a little bit confused, as I don't know if the events should get processed by the systems only or if the user should also be able to process them in the states Update() function...or maybe this is a completely wrong approach.

The InputSystem gets the raw input data. The Controller components then map this data to "events" such as MoveForward, Attack, etc...
Now, how should those events get handled? Maybe the user would like to tell the PhysicsSystem to add a value to the entitiys position when the MoveForward event gets created. But maybe he would also like to create a new entity in the current state.
I'm a little bit confused, as I don't know if the events should get processed by the systems only or if the user should also be able to process them in the states Update() function...or maybe this is a completely wrong approach.

The player announces her/his desire by generating the appropriate input situation with the given input devices. The controller investigates the current input situation accordingly to the input configuration, and detects the said desire. The controller then generates the belonging command. The command expresses still a desire, although translated from an input situation into a behavior.

How to process the behavior depends on many details. Let's use the example of "walk forward" and assume a skeleton based avatar, and look at one possible solution:

The Motion sub-system keeps two float variables for the player's avatar, namely "forward" and "sideways". The former variable defines a speed for forward (if greater than 0) or backward (if lesser than 0) movement, and standing if zero. The latter variable defines how much the movement path is bend to the right (if greater than 0) or to the left (if lesser than 0), or whether it is straight (if 0). The motion system gets / fetches the "walk forward" command and hence increases the "forward" variable from its current 0 up to 3 (using meters per second) within the next 1 second. For this we assume that the "walk forward" command is maintained longer than those second, simply because the input situation is hold.

The animation sub-system controls the skeleton of the player's avatar. It is able to blend animations. It uses the "forward" variable to blend between the animations "standing", "walking forward", "running forward", and "walking backwards". During the second in which the "forward" variable increases from 0 to 3, the initially valid animation "standing" is blended with "walking forward", so that later on when "forward" reached 3 the animation "standing" is weighted with 0 and "walking forward" with 100%.

The animation has tracks not only to define the pose of the skeleton, but also to alter the placement of the skeleton in the world. With each step in time elapsed when those animation track is advanced, the player's avatar is hence moved forward.

Thanks.

However, this didn't answer my question entirely.

I am still confused because of the game states. As I wrote in my first post I've implemented states, which have the following interface:


class iState{
...
     void Init() = 0;
     void Update() = 0;
     void Quit() = 0;
...
};

Now I've got two questions:

1.) What exactly should be in the Update() function of a state?

As I already have systems that manage the "technical" side of the game, I thought of states as a "container" for the game logic. Therefore the user checks if the player did lose, win, gets to the next level etc. in a states Update() function and handles those situations properly (maybe creating new Entities and so on).

I am still concerned about this though, because this doesn't seem like a neat solution.

2.) The second question is related to the first one and may be irrelevant, if the above approach with the states is wrong. How should I handle events created by the controller components?

I know that you've already answered this question, but what if the user doesn't want an event to be processed by a system, but rather by a state. Lets say the controller generates an event "AddEnemies" when pressing down a specific key. Well, this event has to be processed in the current states Update() function, because new entities have to be created. This has to happen in a state, as this is part of the game logic.

It seems me that your understanding of some of the terms used differs from my understanding...

1.) Usually what is called the player controller, or controller for short as used in posts above, controls the player's avatar. This purpose does not allow for adding entities to the world, simply because that action doesn't count to "controlling the avatar".

It is important to restrict the responsibility of classes to a single purpose, not only in this case but in general!

However, it is no problem to have more than one object that investigates the input. For example, the runtime system may investigate it to detect the "goto menu" input situation, and the player controller investigates it to detect avatar controlling input situations.

2.) Detecting occurrences that cause a new entity to spawn is usually integrated into the level. E.g. the player's avatar enters a zone, pushes a button, or does anything else that causes a script to be executed (be it directly of by a fired in-game event). The elements needed to interact accordingly are all part of the level, and hence also the spawning should be.

3.) Usually what is called the game state system controls the overall state of the game as an application. It transitions between states like "startup screen", "highscore", "menu", "settings", "gameplay", and what not. It is possible to have an own state for each game level, i.e. "gameplay level 1", "gameplay level 2", and so on, but that would make the state machine significantly more complex. If you think of the state "gameplay" then the game loop can be understood as the body of the state's update() function, or at least it is triggered from therein, because the game advances if and only if the current state is "gameplay" and it pauses if it is "game menu".

So, you're right if you feel that game states should not be directly responsible for e.g. game object creation.

4.) If you want to have a state "level loading", entered from "gameplay" and returning to "gameplay", then the transition to "level loading" should be caused by the level scripting. This script can (and perhaps should) be called at the beginning of the game loop to allow for a continuous checking. Although it can be understood as an extension to he "gameplay" game state, the script itself (i.e. the actual condition) should be part of the level.

5.) If you want a game state to investigate the input, e.g. the state "gameplay" to detect the invocation of the game menu, then do so. It's okay IMHO.

This topic is closed to new replies.

Advertisement