1
\$\begingroup\$

Based on a question I previously asked here regarding to decoupling input from other components of an entity I developed a small component architecture on top of XNA to understand how everything works and plays together. Basically, my architecture has the following components/services/systems:

Game

This is the root of any game, responsible for ticking the game loop (updating, drawing) as well as instantiating all systems.

GameModule

A game module responsible for a certain aspect of the game, such as UI (UIModule), input (InputModule), animations (SpriteAnimationModule), scenes (SceneStackModule) etc.

Scenes

Holds the current state of the game. Basically an entity manager. :)

Entity

Entity which only holds a collection of components and very basic world space information such as position, rotation etc.

Component

A component of an entity, which can be anything (SpriteComponent, ScriptComponent, ControllerComponent)...


However now I seem to have a problem of the responsibilities between my game systems (modules) and the components, which are not aware of each other. For instance, I have an UIModule instantiated in my game, which I need to update/render before any other systems in order to appropriately handle input events on the UI. For this, I'd need to loop through all UIComponents - which are unfortunately encapsulated within the current scene. Which in turn is only accessible via the SceneStackModule (LIFO collection for scenes). So I have to either make my UIModule aware of the concept of scenes (by making the SceneStackModule a dependency of the UIModule), create an UIModule for each scene or make the UIModule a dependency and register the UIComponent twice (once in the entity, once in the module).


Approach 1

public class UIModule : GraphicalModuleBase {
    public UIModule(SceneStackModule sceneStack) { }

    public void Draw(GameTime gameTime) {
        var uiComponents = SelectUIComponentsFromEntities();
        uiComponents.Draw(gameTime);
    }
}

Approach 2

public class MainScene : Scene {

    public UIModule UIModule { get; } = new UIModule();

    public void Draw(GameTime gameTime) {
        var uiComponents = SelectUIComponentsFromEntities();
        uiModule.Draw(gameTime, uiComponents);
    }
}

Approach 3

public class UIComponent : ComponentBase {
    public UIComponent(UIModule uiModule) {
        uiModule.RegisterComponent(this);
    }
}

All three approaches are not very nice (why does the UI need to be aware of scenes, why no components need modules as dependencies or why do I have to declare my 'main render logic' (UI before everything else) a part of every scene?). So I either clearly missed something here or that's another nasty part of game development. :/

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

The typical approach one - and the one taken to an extreme end by the "ECS" pattern that's hyped these days - is to have your module itself manage the components.

That is, UIModule shouldn't have to iterate through the scene to find all UIComponent instances. Instead, the module should just have a list of the components it cares about. It can easily then loop through those components.

There are many ways that the UIModule could find out about those components. One popularized in many ECS frameworks relies on there being a global event when a component or an entity is created. The UIModule could then listen for that event and register the components/entities it cares about.

Another way is to actually move component creation outside of the core entity manager. In this approach, a module would tell the entity manager about component factories. The UIModule would create a UIComponentFactory and then register that with the core entity manager (possibly in response to a RegisterComponentFactories event so there isn't a hard dependency). The UIComponentFactory identifies the type of component it knows how to create. Since UIModule creates and owns the factory, it's trivial to let the two objects share access to the list of live UIComponent instances.

Sample pseudo-code implementation of the factory approach (my preferred approach for simpler architectures):

clsss UIComponent;
class UIComponentFactory;

class UIModule
{
  unique_ptr<UIComponentFactory> _factory;
  vector<UIComponent*> _components;

public:
  UIModule(GlobalModule& global)
  {
    _factory = make_unique<UIComponentFactory>(this);
    global.Listen<RegisterFactoriesEvent >(&UIModule::OnRegisterFactories);
  }

  void OnRegisterFactories(RegisterFactoriesEvent& ev)
  {
    ev.factories.register(_factory.get());
  }

  void UpdateUI()
  {
    for (auto component : _components)
      Draw(component);
  }
};

class UIComponentFactory : AbstractFactory<UIComponent>
{
  UIModule& _module;

public:
  UIComponentFactory(UIModule& module) : _module(module) {}

  unique_ptr<UIComponent> Create() override
  {
    auto component = make_unique<UIComponent>(_module);
    _module._components.push_back(component.get());
    return component;
  }
};

Obviously there's a bit more to a real implementation, but that's the gist of it. Someone has to tell the core entity manager which components actually exist and how to create them. The factory pattern is perfect for the job. The factory can also register components with associated modules. You'll also want the factory involved in component death and such to make sure they get deregistered too, of course.

A variant of the above that's also used by ECS patterns a lot is to have a "Component Mapper" that owns the actual components. The UIModule can then query the UIComponentMapper to efficiently grab a list of all UIComponent instances.

Now, the idea of a SceneStack doesn't directly fit in here, but I'm not at all a fan of such a stack. Just let there be a list of active scenes without any stack notion; a scene is a scene and there could be one, many, or even no active scenes. Useful for picture-in-picture modes, background views, editors, complex menus, etc.

You can give each "scene" its own list of registered component factories, such that a module has to re-register a factory for each scene created. The module might choose to register a different factory instance per scene or it might share a factory across scenes. It might even register different types of factory for the same components depending on the scene, e.g. whether the scene is being created in "editor mode" or not.

That can even go so far as to have completely separate set of modules for each scene. That turns into the Space and Subspace approach used by a number of engines. A scene/space can itself be composite, after all; not all scenes need physics, for instance (e.g. if your menus are scenes composed of UI objects).

Part of what I'm getting at above is that component-based design is not just for your "entities". Your entity manager itself can be a composite of factories. Your scenes are already composites of entities and can also be composites of modules. Your engine is a composite of modules. Quite a few other concepts in a game can be (usefully) built as composites. You can go overboard and need to be mindful of whether you actually need a particular thing to be composite (it's not even a universal fact that your entities need to be composable!), but certainly be mindful that composition is a general software pattern and not just a trick for game objects.

\$\endgroup\$
3
  • \$\begingroup\$ Wow, thank you very much for your comprehensive answer. I did as you suggested and introduced some kind of 'Entity Processor' as an equvalent to a game module / game system. Whenever an entity gets added to the entity manager, I resolve the required processors and add them to the entity manager too, which will then loop through it and execute their strategy. One thing I'm yet not sure about is what to do when an component gets removed. Observer pattern? Plain old events triggered from the entity? \$\endgroup\$
    – artganify
    Jan 24, 2016 at 18:16
  • \$\begingroup\$ @artganify: there are plenty of solutions there. The common ECS approach is to just have global ComponentAdded and ComponentRemoved events that your Systems listen for. A similar variant is for your Entity to have a list of not only their Components, but also the ComponentFactory used to create that Component, so the factory can now have a Destroy method called for any component instance it created. With the right allocation approach, you can even make that "automatic" without storing explicit factory references (e.g., see which page owns the component and link factories to pages). \$\endgroup\$ Jan 24, 2016 at 21:49
  • \$\begingroup\$ @artganify: I suggest the gamedev.net forums for more open discussion, or the chat feature on this site. I avoid giving private help; if you have a question then chances are that plenty of other people have the same one, so if I spend time answering it I want it somewhere public and indexable by Google. Also, there are plenty of other people that can give the same (or much better) advice than I can, so asking in public is more beneficial for you as well. :) \$\endgroup\$ Jan 24, 2016 at 22:49

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .