Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

My entity/component system is happily humming along and, despite some performance concerns I initially had, everything is working fine.

However, I've realized that I missed a crucial point when starting this thing: how do you handle different screens?

At the moment, I have a GameManager class which owns a component manager and entity manager. When I create an entity, the entity manager assigns it an ID and makes sure it's tracked.

When I modify the components that are assigned to an entity. an UpdateEntity method is called, which alerts each of the systems that they may need to add or remove the entity from their respective entity lists.

A problem with this is that the collection of entities operated on by each system is determined solely by the individual Systems, typically based on a "required component" filter. (An entity has to have a Renderable component to be rendered, for instance.)

In this situation, I can't just keep collections of entities per screen and only Update/Draw those collections. They'd have to either be added and removed depending on their applicability to the current screen, which would cause their associated components to be removed, or enable/disable entities in a group per screen to hide what's not supposed to be visible.

These approaches seem like really, really crappy kludges.

What's a good way to handle this? A pretty straightforward way that comes to mind is to create a separate GameManager (which in my implementation owns all of the systems, entities, etc.) per screen, which means that everything outside of the device context would be duplicated. That's bothersome because some things are always visible, or I might want to continue to display the game under a translucent menu window.

Another option would be to add a "layer" key to the GameManager class, which could be checked against a displayable layer stack held by the game manager. *System.Draw() would be called for each active layer, in the required order as determined by the stack. When the systems request an iterator for their respective entity collections, it would be pre-filtered to a (cached) set of those entities that participate in the active layer. Those collections could be updated from the same UpdateEntity event that's already used to maintain each system's entity collections.

Still, kinda feels like a hack. If I've coded myself into a corner, feel free to throw tomatoes as long as they're labeled with a helpful suggestion.

Hooray for learning curves.

share|improve this question
    
Add an active flag or screenID to the entity? Systems can check that before updating/rendering/whatever. –  Byte56 Sep 12 '12 at 23:39
    
@Byte56 that had occurred to me, but it's adding a field to an item that's supposed to be a key, and not much else. Then again, this is probably one of those cases where the pattern gets in the way, and I just need to fix the damn thing. If this were a database, having an "Entity" table with some data like screen allocation would be perfectly acceptable. –  David Lively Sep 12 '12 at 23:46
    
Meh, it's your system. You should only adhere to the pattern as long as it's useful to you. If you can make a simple little change like this to solve your problem, go for it. –  Byte56 Sep 12 '12 at 23:49
    
@Byte56 well, I wrote this rev as a learning exercise, so I'm more interested in how to "correctly" attack this at the architecture level. –  David Lively Sep 13 '12 at 0:23
    
Wound up adding a cached, filtered "ActiveEntities" collection to the base system class that's updated when the screen changes. Went ahead and added the screen/group id to the entity as well. –  David Lively Sep 13 '12 at 21:50
show 3 more comments

1 Answer

up vote 3 down vote accepted

After trying a few different approaches, I implemented the following, and it's working quite well.

Briefly:

I have a GameManager class which is responsible for maintaining systems, components and resources.

public class GameManager : Disposable
{
    public EntityManager EntityManager { get; private set; }
}

GameManager is the game's primary interface to the engine. It provides methods for creating entities, etc. The relevant part here is EntityManager (abbreviated, relevant methods shown):

public class EntityManager
{
    public List<IEntity> Entities { get; private set; }
    internal Dictionary<string, EntityCache> Caches { get; private set; }

    private Channel _nextChannel = null;

    internal EntityCache CreateCache(string name, params Type[] filter)
    {
        name = name.Trim().ToLower();

        if (Caches.ContainsKey(name))
            throw new Exception("A cache of the same name already exists");

        var cache = new EntityCache();
        cache.Filter = filter;

        Caches.Add(name, cache);

        return cache;
    }

    private bool MatchesFilter(IEntity entity, IEnumerable<Type> filter)
    {
        return filter.All(t => entity.Is(t));
    }

    internal void PreUpdate(float dt)
    {
        if (_nextChannel != null)
        {
            foreach (var kv in Caches)
            {

                var cache = kv.Value;

                cache.ActiveItems.Clear();
                cache.ActiveItems.AddRange(
                    cache.Items.Where(
                        (entity) =>
                        {
                            var enable = !entity.Is<Channel>() || entity.As<Channel>() == _nextChannel;
                            entity.Enabled = enable;
                            return enable;
                        }
                )
                );
            }
            _nextChannel = null;
        }

    }
    /// <summary>
    /// update the caches by adding or removing the specified entity, based on its assigned components
    /// </summary>
    /// <param name="entity"></param>
    internal void Update(IEntity entity)
    {
        if (!Entities.Contains(entity))
            Entities.Add(entity);

        if (Manager.Components.ContainsKey(entity))
        {
            var components = Manager.Components[entity];

            foreach (var cache in Caches.Values)
            {
                var matches = MatchesFilter(entity, cache.Filter);
                var contains = cache.Items.Contains(entity);

                var active = entity.Is<Channel>() ? (entity.As<Channel>() == _nextChannel) : true;

                if (matches && !contains)
                {
                    cache.Items.Add(entity);
                    if (active)
                        cache.ActiveItems.Add(entity);

                }
                else if (!matches && contains)
                {
                    cache.Items.Remove(entity);
                }

            }
        }
    }

    public void SetChannel(Channel channel)
    {
        _nextChannel = channel;
    }

}

EntityManager maintains filtered caches of entities:

internal class EntityCache
{
    /// <summary>
    /// list of component types that this filter requires
    /// </summary>
    public Type[] Filter { get; internal set; } 

    /// <summary>
    /// all items in the system that match this filter (set by EntityManager)
    /// </summary>
    public List<IEntity> Items { get; private set; }

    /// <summary>
    /// Items that are active (should be updated, drawn, etc.)
    /// </summary>
    public List<IEntity> ActiveItems { get; private set; }

    internal EntityCache()
    {
        Items = new List<IEntity>();
        ActiveItems = new List<IEntity>();
    }
}

Also, a Channel component is used to group entities into groups/channels/screens. (Channel exposes a ChannelId property used to separate groups.)

When EntityManager starts its Update cycle, it checks if a channel change request has been received. If so, it rebuilds the ActiveItems lists. Entities that have a Channel component attached are added to the ActiveItems collection if their channel is the same as the next selected channel. Entities that do not have a Channel component are always active (good for global input processors, debug displays, loggers, etc.)

Systems only operate on the ActiveItems collections during the Draw and Update cycles.

So, when initializing a game, I might do this:

var playChannel = new Channel("play");
var menuChannel = new Channel("menu");

var ship = GameManager.Create("ship");
...add ship rendering components...
ship.Add(playChannel);

var splashLogo = GameContent.Create("splash-screen-logo");    
...add logo rendering components...
splashLogo.Add(menuChannel);

Manager.EntityManager.SetChannel("menu");

The last line updates the ActiveItems collections, so on the next cycle, any entities with an attached Channel with ID "menu" will be drawn and updated, but everything else will be ignored. An input processor switches channels when required.

I also added a small serialization-friendly state machine which handles navigation between screens, but that's another topic.

I initially had each System maintain its own entity caches, but I found myself creating systems which required the same component filters, so those caches would be duplicated. Moving them into an EntityManager provided a centralized interface to all of the entities in the game, made implementation of the screen/channel system fairly trivial, and made it much easier to remove entities and components from the game when necessary.

Hope this helps someone!

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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