11
\$\begingroup\$

Okey, what I know so far; The entity contains a component(data-storage) which holds information like; - Texture/sprite - Shader - etc

And then I have a renderer system which draws all this. But what I don't understand is how the renderer should be designed. Should I have one component for each "visual type". One component without shader, one with shader, etc?

Just need some input on whats the "correct way" to do this. Tips and pitfalls to watch out for.

\$\endgroup\$
1
  • 2
    \$\begingroup\$ Try not to make things too generic. It would seem strange to have an entity with a Shader component and not a Sprite component, so maybe the Shader should be part of the Sprite component. Naturally you will then only need one rendering system. \$\endgroup\$ Oct 3, 2012 at 8:11

4 Answers 4

8
\$\begingroup\$

This is a difficult question to answer because everyone has their own idea about how an entity component system should be structured. The best I can do is share with you some of the things I have found to be most useful for me.

Entity

I take the fat-class approach to ECS, probably because I find extreme methods of programming to be highly inefficient (in terms of human productivity). To that end, an entity to me is an abstract class to be inherited by more specialized classes. The entity has a number of virtual properties and a simple flag that tells me whether or not this entity should exist. So relative to your question about a render system, this is what the Entity looks like:

public abstract class Entity {
    public bool IsAlive = true;
    public virtual SpatialComponent   Spatial   { get; set; }
    public virtual ImageComponent     Image     { get; set; }
    public virtual AnimationComponent Animation { get; set; }
    public virtual InputComponent     Input     { get; set; }
}

Components

Components are "stupid" in that they don't do or know anything. They have no references to other components, and they typically have no functions (I work in C#, so I use properties to handle getters/setters - if they do have functions, they are based around retrieving data that they hold).

Systems

Systems are less "stupid", but are still dumb automatons. They have no context of the overall system, have no references to other systems and hold no data except for a few buffers that they may need to do their individual processing. Depending on the system, it may have a specialized Update, or Draw method, or in some cases, both.

Interfaces

Interfaces are a key structure in my system. They are used to define what a System can process, and what an Entity is capable of. The Interfaces that are relevant for rendering are: IRenderable and IAnimatable.

The interfaces simply tell the system which components are available. For example, the rendering system needs to know the bounding box of the entity and the image to draw. In my case, that would be the SpatialComponent and the ImageComponent. So it looks like this:

public interface IRenderable {
    SpatialComponent Component { get; }
    ImageComponent   Image     { get; }
}

The RenderingSystem

So how does the rendering system draw an entity? It's actually quite simple, so I'll just show you the stripped down class to give you an idea:

public class RenderSystem {
    private SpriteBatch batch;
    public RenderSystem(SpriteBatch batch) {
        this.batch = batch;
    }
    public void Draw(List<IRenderable> list) {
        foreach(IRenderable obj in list) {
            this.batch.draw(
                obj.Image.Texture,
                obj.Spatial.Position,
                obj.Image.Source,
                Color.White);
        }
    }
}

Looking at the class, the render system doesn't even know what an Entity is. All it knows about is IRenderable and it is simply given a list of them to draw.

How It All Works

It may help to also understand how I create new game objects and how I feed them to the systems.

Creating Entities

All game objects inherit from Entity, and any applicable interfaces that describe what that game object can do. Just about everything that is animated on screen looks like this:

public class MyAnimatedWidget : Entity, IRenderable, IAnimatable {}

Feeding the Systems

I keep a list of all entities that exist in the game world in a single list called List<Entity> gameObjects. Each frame, I then sift through that list and copy object references to more lists based on interface type, such as List<IRenderable> renderableObjects, and List<IAnimatable> animatableObjects. This way, if different systems need to process the same entity, they can. Then I simply hand those lists to each one of the systems Update or Draw methods and let the systems do their work.

Animation

You might be curious how the animation system works. In my case you may want to see the IAnimatable interface:

public interface IAnimatable {
    public AnimationComponent Animation { get; }
    public ImageComponent Image         { get; set; }
}

The key thing to notice here is the ImageComponent aspect of the IAnimatable interface is not read-only; it has a setter.

As you may have guessed, the animation component just holds data about the animation; a list of frames (which are image components), the current frame, the number of frames per second to be drawn, the elapsed time since the last frame increment, and other options.

The animation system takes advantage of the rendering system and image component relationship. It simply changes the image component of the entity as it increments the animation's frame. That way, the animation is rendered indirectly by the rendering system.

\$\endgroup\$
2
  • \$\begingroup\$ I should probably note that I don't really know if this is even close to what people are calling an entity-component system. In my attempt to implement a composition-based design, I found myself falling into this pattern. \$\endgroup\$
    – Cypher
    Oct 3, 2012 at 18:40
  • \$\begingroup\$ Interesting! I'm not too keen on the abstract class for your Entity but the IRenderable interface is a good idea! \$\endgroup\$ Oct 5, 2012 at 10:59
5
\$\begingroup\$

See this answer to see the kind of system I'm talking about.

The component should contain the details for what to draw and how to draw it. The rendering system will take those details and draw the entity in the way specified by the component. Only if you were to use significantly different drawing technologies would you have separate components for separate styles.

\$\endgroup\$
3
\$\begingroup\$

The key reason to separate logic into components is to create a set of data that when combined in an entity they produce useful, reusable behavior. For example separating a Entity into a PhysicsComponent and RenderComponent makes sense as it's likely that not all entities will have Physics and some entities might not have Sprite.

In order to answer your question you need to look at your architecture and ask yourself two questions:

  1. Does it make sense to have a Shader without a Texture
  2. Will separating Shader from Texture allow me to avoid code duplication?

When splitting a component it's important to ask this question, if the answer to 1. is yes then you probably have a good candidate for creating two separate components, one with a Shader and one with Texture. The answer to 2. is usually yes for components like Position where multiple components can use position.

For example, both Physics and Audio might use the same position, rather then both components storing duplicate positions you refactor them into one PositionComponent and require that entities that use PhysicsComponent/AudioComponent also have a PositionComponent.

Based on the information you've given us it doesn't seem like your RenderComponent is a good candidate for splitting into a TextureComponent and ShaderComponent as shader's are wholly dependent on Texture's and nothing else.

Assuming you're using something similar to T-Machine: Entity Systems a sample implementation of a RenderComponent & RenderSystem in C++ would look something like this:

struct RenderComponent {
    Texture* textureData;
    Shader* shaderData;
};

class RenderSystem {
    public:
        RenderSystem(EntityManager& manager) :
            m_manager(manager) {
            // Initialize Window, rendering context, etc...
        }

        void update() {
            // Get all the entities with RenderComponent
            std::vector<RenderComponent>& components = m_manager.getComponents<RenderComponent>();

            for(auto component = components.begin(); entity != components.end(); ++components) {
                // Do something with the texture
                doSomethingWithTexture(component->textureData);

                // Do something with the shader if it's not null
                if(component->shaderData != nullptr) {
                    doSomethingWithShader(component->shaderData);
                }
            }
        }
    private:
        EntityManager& m_manager;
}
\$\endgroup\$
15
  • \$\begingroup\$ That's completely wrong. The whole point of components is to separate them from entities, not make render systems search through entities to find them. Render systems should fully control their own data. P.S. Don't put std::vector (especially with instance data) in loops, that's awful (slow) C++. \$\endgroup\$
    – snake5
    Oct 3, 2012 at 13:07
  • \$\begingroup\$ @snake5 you are correct on both counts. I typed the code off the top of my head and there were some problems, thanks for pointing them out. I've fixed the affected code to be less slow and to correctly use entity system idioms. \$\endgroup\$
    – Jake Woods
    Oct 3, 2012 at 13:51
  • 2
    \$\begingroup\$ @snake5 You aren't recalculating data every frame, getComponents returns a vector owned by m_manager that is already known and only changes when you add/remove components. It's an advantage when you have a system that wants to use multiple components of the same entity, for example a PhysicsSystem that wants to use PositionComponent and PhysicsComponent. Other systems will probably want the position and by having a PositionComponent you don't have duplicate data. Primarily it solves the problem of how do components communicate. \$\endgroup\$
    – Jake Woods
    Oct 3, 2012 at 14:36
  • 5
    \$\begingroup\$ @snake5 The question isn't about how the EC system should be laid out or it's performance. The question is about setting up the render system. There are multiple ways to structure an EC system, don't get caught up on the performance issues of one over another here. The OP is likely using a completely different EC structure than either of your answers. The code provided in this answer is just meant to better show the example, not to be criticized for it's performance. If the question were about performance than perhaps this would make the answer "not useful", but it's not. \$\endgroup\$
    – House
    Oct 3, 2012 at 14:41
  • 2
    \$\begingroup\$ I much prefer the design laid out in this answer than in Cyphers. It's very similar to the one I use. Smaller components are better imo, even if they have only one or two variables. They should define an aspect of an entity, like my "Damagable" component would have 2, maybe 4 variables (max and current for each health and armour). These comments are getting long, let's move to chat if you want to discuss more. \$\endgroup\$ Oct 3, 2012 at 19:45
2
\$\begingroup\$

Pitfall #1: overdesigned code. Think about whether you really need every thing you implement because you're going to have to live with it for quite some time.

Pitfall #2: too many objects. I wouldn't use a system with too many objects (one for each type, subtype and whatever) because it just makes automated processing harder. In my opinion, it's much nicer to have each object control a certain feature set (as opposed to one feature). For example, making components for each bit of data included in rendering (texture component, shader component) is too divided - you'd usually need to have all those components together anyway, wouldn't you agree?

Pitfall #3: too strict external control. Prefer changing names to shader/texture objects because objects can change with renderer/texture type/shader format/whatever. Names are simple identifiers - it's up to the renderer to decide what to make out of those. One day you might want to have materials instead of plain shaders (add shaders, textures and blend modes from data, for example). With a text-based interface, it's much easier to implement that.

As for the renderer, it can be a simple interface that creates/destroys/maintains/renders objects created by components. The most primitive representation of it could be something like this:

class Renderer {
    function Draw() { ... }
    function AddSprite( ... ) { ... return sprite; }
    function RemoveSprite( sprite ) { ... }
    ...
};

This would allow you to manage these objects from your components and keep them far enough to allow you render them any way you want.

\$\endgroup\$

You must log in to answer this question.

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