Jump to content
  • Advertisement
Sign in to follow this  
Juliean

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

This topic is 2038 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

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?

Share this post


Link to post
Share on other sites
Advertisement

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

Share this post


Link to post
Share on other sites

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?

Share this post


Link to post
Share on other sites

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...)

Share this post


Link to post
Share on other sites

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?

Share this post


Link to post
Share on other sites

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

Share this post


Link to post
Share on other sites

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

Share this post


Link to post
Share on other sites

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.

Share this post


Link to post
Share on other sites

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

Share this post


Link to post
Share on other sites

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.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!