1
\$\begingroup\$

When creating ECS based game engine, do you (or can I) create multiple rendering systems?

Let's say I have an Entity class which is the basic unit in my engine, all it can do is add, remove and get components attached to it.

I have a Component class which is just a class where other components can derive from and holds data that can be used to define an Entity.

And I have a RenderSystem with a Draw method that accepts the Entity to be drawn.

My questions is, do I have to (or can I) create different derivations of RenderSystem to satisfy different rendering?

One example is when rendering sprites do I have to create a separate SpriteRenderSystem and with animations do I have to create AnimationRenderSystem? Also I have a World class which manages everything, with Entity & Component managers and Systems in it. That class has an Update and Draw method. So do I have to check if the Entity being processed is of a specific type SpriteEntity or AnimationEntity and use a specific renderer?

// World class
...
void Draw()
{
    // Loop through all entities
    for (...)
    {
        // Check for specific entities
        if (entities[i] is SpriteEntity)
        {
            // Call specific sprite renderer
            _spriteRendererSystem.Draw(entities[i]);
        }
        else if (entities[i] is AnimationEntity)
        {
            // Call specific animation renderer
            _animationRendererSystem.Draw(entities[i]);
        }
    }
}

Is this a good practice? Is this the right way? Is this fast? Thanks for any answer.

\$\endgroup\$
  • \$\begingroup\$ I can't really answer your question as i don't really know if it's good or bad practice as i never actually needed to do it. Most of the time i end up with a SpriteComponent that is actually used by my RenderSystem to draw my sprites. To handle, let's say animations, i have a AnimationSystem that actually changes the sprite in my SpriteComponent regarding the current frame. So in the end, my RenderSystem only works with a SpriteComponent and others systems are allowed to actually modify the SpriteComponent, like the animation system for instance. \$\endgroup\$ – nathan Dec 5 '16 at 8:15
  • \$\begingroup\$ I think you should think your components as atomic as possible. Animation is not really related to rendering. That's the beauty of such a system i believe. \$\endgroup\$ – nathan Dec 5 '16 at 8:15
1
\$\begingroup\$

What you should ask yourself is what is the actual difference between the two types?

An animated entity typically has both some form of a skeleton of bones which are animated through various animation tracks which a non-animated Sprite likely does not.

That would mean for animated entities, their skeletal pose needs to be properly set based on the current frame's time advancement, physics, and perhaps other external input factors. Perhaps there are multiple animations at play which need to be blended. All of these tasks are performed by a separate AnimationSystem and generates a final skeletal pose for rendering.

The render system then takes your mesh plus the pose and draws the final result. How the render system acquires the skeletal pose can occur multiple ways, through the animation component or even by asking for it to the animation system.

As to the render loop, my caution is to avoid if-else blocks inside a for-loop if you can. That means, try to decouple the data storage about renderable entities by somehow storing a separate list of those which are animated. This way you can easily deduce those which you need poses for and set that information.

void Draw(float deltaTime) {
  // for each animated entity, acquire the pose data and store it on the
  // renderable element.
  for( int i = 0; i < animatedEntities.size(); ++i ) {
    uint64_t entityId = animatedEntities[i];
    applyAnimatedPose( entityId, animationSystem.getSkeletalPose( entityId ) );
  }

  // draw all renderables in the batch
  std::vector<uint64_t>& renderables = getBatchRenderables();
  for ( int i = 0; i < renderables.size(); ++i ) {
    uint64_t entityId = renderables[i];
    draw( entityId );
  }
}

The psuedo code illustrates that its best to small incremental updates to data on very explicit subsets. This allows for better cache locality, thus improving performance. But more importantly, when operating on like subsets of data, this has the added benefit to avoid branching (if-else statements) inside tight loops, thus adding yet another performance benefit.

At the end, I show to iterate over all applicable renderables. That may mean to exclude those culled by the frustum, those which aren't visible or applicable to the scene's environment, etc. The returned list is properly sequenced so that the call to draw simply adds a specific draw command to the driver's batch list.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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