Jump to content

  • Log In with Google      Sign In   
  • Create Account

FREE SOFTWARE GIVEAWAY

We have 4 x Pro Licences (valued at $59 each) for 2d modular animation software Spriter to give away in this Thursday's GDNet Direct email newsletter.


Read more in this forum topic or make sure you're signed up (from the right-hand sidebar on the homepage) and read Thursday's newsletter to get in the running!


Game Engine :: Entity Component Design - Handling Input


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
12 replies to this topic

#1 Tomsen1410   Members   -  Reputation: 155

Like
0Likes
Like

Posted 15 July 2014 - 11:47 AM

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!


Edited by Tomsen1410, 15 July 2014 - 11:49 AM.


Sponsor:

#2 phil_t   Crossbones+   -  Reputation: 4109

Like
6Likes
Like

Posted 15 July 2014 - 12:37 PM

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. 


Edited by phil_t, 15 July 2014 - 12:37 PM.


#3 L. Spiro   Crossbones+   -  Reputation: 14411

Like
5Likes
Like

Posted 15 July 2014 - 03:27 PM

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
It is amazing how often people try to be unique, and yet they are always trying to make others be like them. - L. Spiro 2011
I spent most of my life learning the courage it takes to go out and get what I want. Now that I have it, I am not sure exactly what it is that I want. - L. Spiro 2013
I went to my local Subway once to find some guy yelling at the staff. When someone finally came to take my order and asked, “May I help you?”, I replied, “Yeah, I’ll have one asshole to go.”
L. Spiro Engine: http://lspiroengine.com
L. Spiro Engine Forums: http://lspiroengine.com/forums

#4 Tomsen1410   Members   -  Reputation: 155

Like
0Likes
Like

Posted 20 July 2014 - 10:12 AM

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?

Edited by Tomsen1410, 20 July 2014 - 10:13 AM.


#5 L. Spiro   Crossbones+   -  Reputation: 14411

Like
2Likes
Like

Posted 21 July 2014 - 09:37 PM

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
It is amazing how often people try to be unique, and yet they are always trying to make others be like them. - L. Spiro 2011
I spent most of my life learning the courage it takes to go out and get what I want. Now that I have it, I am not sure exactly what it is that I want. - L. Spiro 2013
I went to my local Subway once to find some guy yelling at the staff. When someone finally came to take my order and asked, “May I help you?”, I replied, “Yeah, I’ll have one asshole to go.”
L. Spiro Engine: http://lspiroengine.com
L. Spiro Engine Forums: http://lspiroengine.com/forums

#6 haegarr   Crossbones+   -  Reputation: 4602

Like
2Likes
Like

Posted 22 July 2014 - 01:56 AM

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.


#7 Tomsen1410   Members   -  Reputation: 155

Like
0Likes
Like

Posted 27 August 2014 - 02:59 PM

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.



#8 haegarr   Crossbones+   -  Reputation: 4602

Like
0Likes
Like

Posted 27 August 2014 - 04:32 PM

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.


Edited by haegarr, 27 August 2014 - 04:34 PM.


#9 Tomsen1410   Members   -  Reputation: 155

Like
0Likes
Like

Posted 28 August 2014 - 07:35 AM

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.


Edited by Tomsen1410, 28 August 2014 - 08:22 AM.


#10 haegarr   Crossbones+   -  Reputation: 4602

Like
0Likes
Like

Posted 28 August 2014 - 11:35 AM

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.



#11 crancran   Members   -  Reputation: 416

Like
0Likes
Like

Posted 04 September 2014 - 11:07 PM

I prefer to leverage game states as a means to orchestrate chunks of behavior that must coordinate with a broad range of engine subsystems.  

 

For example, you may have a cutscene/movie subsystem that knows how to play avi files and a game state is responsible for checking whether the player has watched the intro movie and if not, instructs the movie subsystem to play it; otherwise transition to the login screen.  The login state is responsible for preparing the login UI with the UI subsystem, perhaps loading the game's theme music and waiting for the user to enter their credentials.  Our entire game menu/settings system which can be invoked from the login screen, character select screen, and while in game is all driven inside the game state system by pushing that state onto the active state list.

 

Another simple example of the game state system at work is when a user transitions from one map to another.  You simply signal that you need to transition to a loading state, when you enter the loading state you know which map you need to load and so you can display an appropriate loading screen.  During the update loop of this state, perhaps various things are being performed, specific tasks completion statuses being checked and when loading has finished, you pop the state which removes the loading screen and returns the player back to the game play state.  Such a transition disables input to the controllers and perhaps other aspects.

 

As for event detection/dispatch, the observer pattern is simple and yet powerful.  Most of my subsystems expose some type of listener interface that I just implement where needed.  It's a very simple solution to implement, has minimal overhead and allows you to focus your energy in other places rather than loosing momentum designing something that is likely overkill for where you are at right now.  Then when an event is fired, the class holding the listeners simply iterates the listeners, calls some virtual method and the listener implementation fires it's logic.  :)

 

Good luck!



#12 haegarr   Crossbones+   -  Reputation: 4602

Like
0Likes
Like

Posted 05 September 2014 - 01:26 AM


As for event detection/dispatch, the observer pattern is simple and yet powerful.  Most of my subsystems expose some type of listener interface that I just implement where needed.  It's a very simple solution to implement, has minimal overhead and allows you to focus your energy in other places rather than loosing momentum designing something that is likely overkill for where you are at right now.  Then when an event is fired, the class holding the listeners simply iterates the listeners, calls some virtual method and the listener implementation fires it's logic.

I second this, but with an emphasize on "where needed".

 

Simultaneously I'm used to mention that a game usually runs continuously in a well ordered game loop, opposed to desktop applications. As a result, asynchronous input processing (asynchronous is meant here w.r.t. to the update() of a listening sub-system) means to memorize any relevant input changes locally, because a meaningful reaction can be made only when the own update() is called the next time. However, what "meaningful reaction" that is can also be determined inside update() only, because it is required to consider not only input but the entire state of the world, and that is valid only when the game loop calls the said update() method.

 

So question is, whether input listening is actually needed in a given use case, or whether it is even perhaps counter-productive. Patterns are fine in general, but as every tool, they have their use cases. Just "where needed"...



#13 L. Spiro   Crossbones+   -  Reputation: 14411

Like
0Likes
Like

Posted 05 September 2014 - 08:46 AM

I prefer to leverage game states as a means to orchestrate chunks of behavior that must coordinate with a broad range of engine subsystems.  
 
For example, you may have a cutscene/movie subsystem that knows how to play avi files and a game state is responsible for checking whether the player has watched the intro movie and if not, instructs the movie subsystem to play it; otherwise transition to the login screen.  The login state is responsible for preparing the login UI with the UI subsystem, perhaps loading the game's theme music and waiting for the user to enter their credentials.  Our entire game menu/settings system which can be invoked from the login screen, character select screen, and while in game is all driven inside the game state system by pushing that state onto the active state list.
 
Another simple example of the game state system at work is when a user transitions from one map to another.  You simply signal that you need to transition to a loading state, when you enter the loading state you know which map you need to load and so you can display an appropriate loading screen.  During the update loop of this state, perhaps various things are being performed, specific tasks completion statuses being checked and when loading has finished, you pop the state which removes the loading screen and returns the player back to the game play state.  Such a transition disables input to the controllers and perhaps other aspects.

Basically this, although the reason I support stacks of states is to allow the lower states in the stack to maintain their current “state” while the top state interjects something on top, such as when your controller battery is about to die on a Nintendo Wii U.
Rather than going from a CGamePlayState to CLoading on top, then back to the original CGamePlayState, it’s better to go to an entirely new instance of a CGamePlayState so that you can easily release memory between stages etc. The loading screen would still be pushed on top of the CGamePlayState, but it would be at the start of a new instance rather than a singular massive multi-stage–encompassing instance.



As for event detection/dispatch, the observer pattern is simple and yet powerful.  Most of my subsystems expose some type of listener interface that I just implement where needed.  It's a very simple solution to implement, has minimal overhead and allows you to focus your energy in other places rather than loosing momentum designing something that is likely overkill for where you are at right now.  Then when an event is fired, the class holding the listeners simply iterates the listeners, calls some virtual method and the listener implementation fires it's logic.

As mentioned by haegarr, there is a place for the observer pattern, but input has to be handled at a specific time within the game loop, not just immediately as the inputs are received. Inputs are not events.
I’ve discussed this exhaustively and posted links above.


L. Spiro
It is amazing how often people try to be unique, and yet they are always trying to make others be like them. - L. Spiro 2011
I spent most of my life learning the courage it takes to go out and get what I want. Now that I have it, I am not sure exactly what it is that I want. - L. Spiro 2013
I went to my local Subway once to find some guy yelling at the staff. When someone finally came to take my order and asked, “May I help you?”, I replied, “Yeah, I’ll have one asshole to go.”
L. Spiro Engine: http://lspiroengine.com
L. Spiro Engine Forums: http://lspiroengine.com/forums




Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS