Jump to content

  • Log In with Google      Sign In   
  • Create Account

Entity/Component system - game state, gui ... external communication


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
13 replies to this topic

#1 Juliean   GDNet+   -  Reputation: 2754

Like
1Likes
Like

Posted 12 May 2013 - 10:59 AM

Hello,

 

I've been employing a component and system based entity system (much like EntityX) for my engine, and been using it in my latest study project to develop game logic. Things work well when everything can go encapulsated inside of the systems, but I'm a bit concerned about how to handle information that needs to be delivered outside of the systems. For example, I handle the game-state via a state machine, which sets up the systems and also the game gui. What if I now need to share deliver information from the systems to the gui? For example (since I'm developing a tower defense game) I want to disable the "send wave" button when clicking it, and re-enable it when the wave is over. The wave-game-logic itself is handled by this systems update method:

 

SpawnSystem::SpawnSystem(const std::wstring& stLevel): m_time(0.0), m_currentSpawn(0), m_numAlive(0), m_currentWave(0), m_bActiveWave(false)
{
    // load level data from file
    LevelLoader levelLoader(m_units, m_paths, m_waves);
    levelLoader.Load(L"Game/Level/" + stLevel + L"/Level.axm");

    m_maxWaves = m_waves.Size();
    m_pWave = m_waves.Get(0);
}

void SpawnSystem::Init(ecs::MessageManager& messageManager)
{
    // subscribe to messages
    messageManager.Subscribe<UnitDied>(*this);
    messageManager.Subscribe<UnitReachedGoal>(*this);
    messageManager.Subscribe<StartWave>(*this);
}

void SpawnSystem::Update(ecs::EntityManager& entityManager, ecs::MessageManager& messageManager, double dt)
{
    // check if wave is active
    if(m_bActiveWave)
    {
        m_time += dt;

        // are there units left to spawn?
        if( m_currentSpawn < m_pWave->spawns.size() )
        {
            // iteratively spawn units ...
            for(Wave::Spawns::const_iterator ii = m_pWave->spawns.begin() + m_currentSpawn; ii != m_pWave->spawns.end(); ++ii)
            {
                // ... until there is one that will be spawned at a later time
                // it is assumed unit containers is orderered
                if(ii->time > m_time)
                    break;
                else
                {
                    // spawn unit
                    ecs::Entity& unit = entityManager.CreateEntity();
	                unit.AttachComponent<ecs::Actor>();
                    unit.AttachComponent<ecs::ModelComponent>(L"Game/Meshes/Cube.x", 0);
                    unit.AttachComponent<ecs::Direction>(0.0f, 0.0f, 0.0f);
                    unit.AttachComponent<Movement>(2.0f, ii->path.vStartPos);
                    unit.AttachComponent<Unit>(ii->unit);
                    ecs::Position* pPos = unit.AttachComponent<ecs::Position>(ii->path.vStartPos);

                    // create offset bounding box
                    math::AABB* pBox = new math::AABB(.0f, .5f, .0f, 0.5f, 0.5f, 0.5f);
	                pBox->m_vCenter += pPos->m_v;
	                unit.AttachComponent<ecs::Bounding>(*pBox);

                    unit.AttachComponent<Pathfind>(ii->path);

                    // increment spawn and alive counter
                    m_currentSpawn++;
                    m_numAlive++;

                }
            }
        }
        else
        {
            // check if there are no more units alive
            if(!m_numAlive)
            {
                // deactive and reset
                m_bActiveWave = false;
                m_time = 0.0f;
                m_currentSpawn = 0;
                   
                // increment wave counter
                m_currentWave++;

                // inform other systems that wave is over
                messageManager.DeliverMessage<FinishedWave>(m_currentWave, m_maxWaves);

                // check whether this was the last wave
                if(m_currentWave >= m_maxWaves)
                    SigWinGame(); // ???
                else
                {
                    // aquire next wave
                    m_pWave = m_waves.Get(m_currentWave);
                    SigWaveOver(); // ???
                }
            }
        }
    }
}

void SpawnSystem::ReceiveMessage(const ecs::BaseMessage& message)
{
    // reduce alive counter if unit died or reached its goal
    if(message.Convert<UnitDied>() || message.Convert<UnitReachedGoal>())
        m_numAlive--;
    // set active if wave start is requestes
    else if(message.Convert<StartWave>())
        m_bActiveWave = true;
}

 

My first idea, as you can see in the "???" marked lines, I've been using signals to tranfer this information to the main game state. However, I've been wondering if there were any better ideas on how to do this. How are you dealing with this kind of "system out" information? In is obviously no problem, I'd just use the message manager to send a message. But out appears more complicated, since I can't just use the message manager to inform any possible class, therefore I'd need to tie it to a certain implementation (which is obviously not a good thing). Any input from you guys?



Sponsor:

#2 phil_t   Crossbones+   -  Reputation: 4109

Like
2Likes
Like

Posted 12 May 2013 - 11:17 AM

You could have some interface through which you query/set the current level's state, which "IsWaveActive" would be a part of. SpawnSystem would be given a reference to this interface, as would the game GUI.

 

Or, in keeping with your message system, have an IsWaveActive message that the SpawnSystem will respond to (does your message system support returning values?). 


Edited by phil_t, 12 May 2013 - 03:09 PM.


#3 Juliean   GDNet+   -  Reputation: 2754

Like
0Likes
Like

Posted 12 May 2013 - 03:58 PM

You could have some interface through which you query/set the current level's state, which "IsWaveActive" would be a part of. SpawnSystem would be given a reference to this interface, as would the game GUI.

 

Ah, didn't think of that before, sounds worth it, but lets first talk about that:

 

Or, in keeping with your message system, have an IsWaveActive message that the SpawnSystem will respond to (does your message system support returning values?).

 

No, since the message might be delivered to many systems, there is no sensical way for having any response to that message. For example, the "unit died" message is needed by the spawn system, and also the player system to update his amount of gold etc... the messaging system is just meant for "in" type messages, and for inter-system communication, really.

 

So I'd much rather stick with your first option. Does that sound good?

 

class GameGuiQuery :
    public IGameStateQuery
{
    Signal1<bool> SigWaveStateChange;
    Signal1<size_t> SigChangePlayerGold;

    void OnSetWaveState(bool bActive)
    {
        if(m_bActive != bActive)
        {
            SigWaveStateChange(bActive);
            m_bActive = bActive;
        }
    }

    void OnChangePlayerGold(size_t gold)
    {
        SigChangePlayerGold(gold);
    }

    bool m_bActive;
};

I'd then pass a "IGameStateQuery"-ptr to the systems like the spawn system, or the player system (for the gold). I'd externally create a GameGuiQuery-object, where the gui hooks the signals to be informed of any change on the state (things like the wave state won't change that often). Does this sound okay, or do you have any tips on how to improve this, or any different ideas?



#4 phil_t   Crossbones+   -  Reputation: 4109

Like
1Likes
Like

Posted 12 May 2013 - 05:56 PM

That's fine, although I might prefer polling (e.g. the button/gui system asks if a wave is active each update cycle, rather than receiving a message). It's more robust as there's no chance for things to get out of sync then. (With your way, how does the button get its original state? Perhaps it assumes no waves are active - but then what if you are restoring a saved game where a wave is active?)

 

 

I'm not saying you should do this for your messaging system (as it sounds like you've design it quite differently than mine), but in mine messages have a Handled property. This can be set by a receiver of the message, which can then put in a return value, so to speak. (I generally only have one piece of logic that is interested in a particular message - I haven't yet come across the need to broadcast messages to multiple systems/entities/etc...)



#5 Juliean   GDNet+   -  Reputation: 2754

Like
0Likes
Like

Posted 13 May 2013 - 02:34 AM

That's fine, although I might prefer polling (e.g. the button/gui system asks if a wave is active each update cycle, rather than receiving a message). It's more robust as there's no chance for things to get out of sync then. (With your way, how does the button get its original state? Perhaps it assumes no waves are active - but then what if you are restoring a saved game where a wave is active?)

 

Well thats (unintentionally) a bad example, since I'm only going to allow saves in between waves, but I get the idea.

 

I'm not saying you should do this for your messaging system (as it sounds like you've design it quite differently than mine), but in mine messages have a Handled property. This can be set by a receiver of the message, which can then put in a return value, so to speak. (I generally only have one piece of logic that is interested in a particular message - I haven't yet come across the need to broadcast messages to multiple systems/entities/etc...)

 

Thats interesting though, only with this limited functionality I've got a few messages that need to be sent to multiple systems. For example the "UnitDied" - message. This is sent by the UnitSystem after a units life reaches 0. First of all the spawn systems needs to be informed about this, to decrement the counter of active units. Then the player-system needs it to earn the player gold for the killed unit... probably more to follow. You really havn't come across such a situation?



#6 phil_t   Crossbones+   -  Reputation: 4109

Like
1Likes
Like

Posted 13 May 2013 - 05:40 PM

You really havn't come across such a situation?

 

No, but I've only shipped one game that uses an entity-component-system framework, and it wasn't that large. It certainly doesn't seem unreasonable to broadcast it to multiple systems though. The other game I shipped (which didn't use an ECS), would probably need this functionality.

 

 

First of all the spawn systems needs to be informed about this, to decrement the counter of active units.

 

Again, I have a thing against duplication/caching of state. So instead of having an active unit counter that is added to when a unit is created and subtracted from (via UnitDied message) when a unit dies, I might instead have logic in the spawn system to calculate the current number of active units each frame (or possibly this could be done in another system that is already enumerating through all active entities anyway). That way if I ever added new places in the code where units could be added/killed I wouldn't need to worry/think about updating this number (also coming back to the (irrelevant) save game thing here, I wouldn't need to write any special code to set the "active unit counter" on loading a saved game).


Edited by phil_t, 14 May 2013 - 12:14 AM.


#7 Juliean   GDNet+   -  Reputation: 2754

Like
1Likes
Like

Posted 14 May 2013 - 12:04 PM

Again, I have a thing against duplication/caching of state. So instead of having an active unit counter that is added to when a unit is created and subtracted from (via UnitDied message) when a unit dies, I might instead have logic in the spawn system to calculate the current number of active units each frame (or possibly this could be done in another system that is already enumerating through all active entities anyway). That way if I ever added new places in the code where units could be added/killed I wouldn't need to worry/think about updating this number (also coming back to the (irrelevant) save game thing here, I wouldn't need to write any special code to set the "active unit counter" on loading a saved game).

 

I somehow start to see this asa very convenient. I actually tried my solution with the polling approach you suggest, and while it still worked, I did think it was a bit "ugly", as in non-engine-conformant (this also holds true for my old signal-based query solution, just to clarify). So I actually went ahead and implemented a system similar to what you described you did with your messages, just aside from my regular messaging system. I now have an additional struct called "Query", and I can issue such a query to the manager (currently the system manager, think I'm going to implement a "QueryManager" later). One system can respond to this via overloading the "RecieveQuery"-function. Only real difference is that only one system can respond to a query, and "RecieveQuery" being const, can't be used to transfer information into the system.

 

What do you say to this? I like this solution, as it offers a way to implement the "interface" into the engine itself rather than the game (reusable code is what, at least I, want, but I quess I'm not alone on this ;) ), plus it won't matter now for the game whether I cache or create state on demand. Only downside I see is that my actual implemented gui is now more depending on the entity-system as it was before with my IGameQuery - interface, but since I'm building the game on this entity/component-system anyway, this shouldn't matter, should it? Here is the code I've got now:

 

void StateCtrl::Update(void)
{
    ecs::GoldQuery query;
    if( m_pSystems->Query(query) )
    {
        m_goldLabel->SetText( conv::ToText(query.gold) );
    }
}

bool PlayerSystem::RecieveQuery(ecs::BaseQuery& query) const
{
    if(GoldQuery* pGold = query.Convert<GoldQuery>() )
    {
        if(m_pPlayer)
        {
            pGold->gold = m_pPlayer->GetComponent<Player>()->m_gold;
            return true;
        }

        return false;
    }

    assert(false); // should never reach this line
}

Any more thoughts on how to possibly improve this?


Edited by Juliean, 14 May 2013 - 12:05 PM.


#8 BeerNutts   Crossbones+   -  Reputation: 3018

Like
2Likes
Like

Posted 14 May 2013 - 02:50 PM

Just a general observation about your systems.  It seems the system's hold some amount of data in them.  Typically, I would think systems would only act upon data held within components.  So, systems could react to signals, but manipulate the data stored in some component.

 

Obviously, it will work fine, but I just thought that was something that should be steered clear of when developing an entity component system.


My Gamedev Journal: 2D Game Making, the Easy Way

---(Old Blog, still has good info): 2dGameMaking
-----
"No one ever posts on that message board; it's too crowded." - Yoga Berra (sorta)

#9 Juliean   GDNet+   -  Reputation: 2754

Like
0Likes
Like

Posted 14 May 2013 - 03:15 PM

Obviously, it will work fine, but I just thought that was something that should be steered clear of when developing an entity component system.

 

I didn't think about that, don't even recall reading about it. In fact, I would think of it as too restricting, if systems where completely disallowed of holding any data. Though this is a special case, since this system doesn't even act upon components (it just creates entities), some of my systems hold some kind of data, be it a pointer to a resource pool, or such. Like this system, some of them don't even just work iteratively upon components. Is there a good reason why systems should work as you described? So far I've been (mis)using systems also for tasks like this, which I could surely move into its own non-system class, but, that wouldn't make much sense, at least to me...



#10 BeerNutts   Crossbones+   -  Reputation: 3018

Like
1Likes
Like

Posted 14 May 2013 - 03:59 PM

Obviously, it will work fine, but I just thought that was something that should be steered clear of when developing an entity component system.

 

I didn't think about that, don't even recall reading about it. In fact, I would think of it as too restricting, if systems where completely disallowed of holding any data. Though this is a special case, since this system doesn't even act upon components (it just creates entities), some of my systems hold some kind of data, be it a pointer to a resource pool, or such. Like this system, some of them don't even just work iteratively upon components. Is there a good reason why systems should work as you described? So far I've been (mis)using systems also for tasks like this, which I could surely move into its own non-system class, but, that wouldn't make much sense, at least to me...

 

There are normal class functions that aren't "systems" in the ECS sense, but sit outside the ECS.  IMO, those can have member variables themselves.  When I think of Systems in an ECS sense, I think of functions whose sole purpose is to operate on a component or multiple components within an entity or multiple entities.  However, I've also moved past passing messages around via components too, so I'm of the "components only hold data" group, and allow systems communicate between components if needed.

 

It's just a different way of looking at it.  i can't say one way is right or wrong as I don't have enough experience with it myself to say.


My Gamedev Journal: 2D Game Making, the Easy Way

---(Old Blog, still has good info): 2dGameMaking
-----
"No one ever posts on that message board; it's too crowded." - Yoga Berra (sorta)

#11 phil_t   Crossbones+   -  Reputation: 4109

Like
1Likes
Like

Posted 14 May 2013 - 09:39 PM

Well, I hold onto some state in some of my systems. For instance, the render system keeps track of information necessary to map a particular "aspect" component to an instance of a model that is inserted into the render tree. Similarly for the physics system that maps a physics component <--> physics library I'm using. This is mostly transient state (internal bookkeeping for the systems, essentially) that I don't see as belonging in any component (as that would tie a component to a specific renderer).

I don't see any compelling reason to disallow holding state in systems, with the exception that it is nice if the entire game state can be serialized just by serializing all the entities and their components. If the systems hold important game state, then you need to allow them to participate too.

(I know I keep coming back to the save game scenario. But being able to serialize/deserialize your game state is actually useful for more than just save games. Being able to quickly get back into a certain scenario to debug a particular problem is one example).

Edited by phil_t, 14 May 2013 - 09:46 PM.


#12 phil_t   Crossbones+   -  Reputation: 4109

Like
1Likes
Like

Posted 14 May 2013 - 09:56 PM

Only downside I see is that my actual implemented gui is now more depending on the entity-system as it was before with my IGameQuery - interface, but since I'm building the game on this entity/component-system anyway, this shouldn't matter, should it?

 

Yeah, if this is a problem for you, it is certainly possible to add another abstraction layer (e.g. an implementation of IGameQuery that does the actual SystemManager->Query call, to decouple the UI from the fact that an ECS is being used).



#13 Juliean   GDNet+   -  Reputation: 2754

Like
0Likes
Like

Posted 15 May 2013 - 03:07 AM

There are normal class functions that aren't "systems" in the ECS sense, but sit outside the ECS. IMO, those can have member variables themselves. When I think of Systems in an ECS sense, I think of functions whose sole purpose is to operate on a component or multiple components within an entity or multiple entities. However, I've also moved past passing messages around via components too, so I'm of the "components only hold data" group, and allow systems communicate between components if needed.

 

I'm going the "component only data" route too (except some getters and setters for a few components to simplify "caching" via dirty flags for performance optimizations), but for systems, even if you think about systems sole task is to implement component behaviour, you'd still need some internal state, at least IMHO. Those things you'd pass in via constructor or the update method in non-components-based systems, you are probably supposed to store them in the system, unless you want to introduce unnecessary complexity. Like phil_t said, things like render and physics mapping/syncing will need a pointer to the render/physics components. It wouldn't make sense in my point of view to store those in the component, too, and even if, you'd eigther need an external message system when adding a physics component, or you'd still have to store it in the system nevertheless. I'm not an expert too, but from my experience, it is most convenient if you allow systems to hold state, and perform tasks that do not strikly invoke iterating over an n-set of components, too. You could put this into an external class, but that does nothing than introduce overhead of probably having seperate classes that perform exactly the way the systems does, but just so you have to store them externally and probably have their own message system, etc... .

 

I don't see any compelling reason to disallow holding state in systems, with the exception that it is nice if the entire game state can be serialized just by serializing all the entities and their components. If the systems hold important game state, then you need to allow them to participate too.

 

Talking about that, what do you say to game content? Like levels, etc...? What would you rather prefer, storing things like unit and tower "prototypes" as entity/components, or rather as seperate structs? As for the game level itself, I think it makes most sense to store that in entity/component - format, but I'm a bit unsure which would turn out to be the most usefull in the end (especially thinking about possibly larger projects):

 

// component approach
<Towers>
<Entity>
<Tower dmg="5" cost="50" range="10.0" speed="0.5" />
<Model mesh="Game/Meshes/Tower.x" material="0" />
</Entity>
</Towers>

// direct approach
<Towers>
<Proto dmg="5" cost="50" range="10.0" speed="0.5" mesh="Game/Meshes/Tower.x" material="0" />
</Towers

Yeah, if this is a problem for you, it is certainly possible to add another abstraction layer (e.g. an implementation of IGameQuery that does the actual SystemManager->Query call, to decouple the UI from the fact that an ECS is being used).

 

Well, its not really a problem for me, I rather enjoy the unified interface for communicating with the systems, for now. That saids, thanks so far for all the input.



#14 phil_t   Crossbones+   -  Reputation: 4109

Like
0Likes
Like

Posted 15 May 2013 - 02:14 PM

Talking about that, what do you say to game content? Like levels, etc...? What would you rather prefer, storing things like unit and tower "prototypes" as entity/components, or rather as seperate structs? As for the game level itself, I think it makes most sense to store that in entity/component - format, but I'm a bit unsure which would turn out to be the most usefull in the end (especially thinking about possibly larger projects):

The way I have things now is that I have prototypes. A prototype has a name, a list of filled-out Components, and an optional parent prototype (so I have prototype inheritance, so to speak). From a prototype I can instantiate an entity (and that entity's Components will be cloned from the prototype's Components).

In one of my projects (the smaller one), I have something like your "direct approach" above. Basically a text file that defines the level. It basically has a list of prototype names followed by relevant some values for them. It doesn't map at all to Components, so it isn't really a scalable solution, but it works for this small game. Each time I add a new Component, or some new functionality to an existing component, I also have to go change the level load/save code to figure out how I will express the new concept in the text file. So it's not a great solution, but it does allow me to make tweaks directly to the text file if I want (and I have some extra info in there that doesn't correspond to any entity/component).

In my other project (a much larger, open world type game), I have a complete in-game editor. The "levels" are saved in a non-readable binary format, which is basically just a dump of all the entities and their components. Then I simply reconstitute the level by deserializing it. I still use prototypes heavily (e.g. from within the in-game editor, for instance to create a new NPC or something). Prototypes also come into play when serializing/deserializing. For an entity, I don't serialize Components that are identical to one in its prototype (so each entity keeps track of the prototype it came from). This saves a bunch of space.
So, other than for static game content (terrain, rivers, etc...), the game world is basically just an initial save game (indistinguishable, actually). I don't know if this will turn out to be a good solution (the game isn't very far on), but it does make things easy. I do obviously have to be cognizant of keeping the "save game" working as I make changes to components during development. So each Component needs to be versioned.

Edited by phil_t, 15 May 2013 - 02:18 PM.





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