I'm currently learning about "Entity Component System". After reading many tutorials and forum threads I still wonder where rendering logic has to go. I am not talking about actual OpenGL/DirectX-Rendering code that, for example, takes a sprite and renders it. What I mean is the logic that decides which sprite to render.
A visible entity requires three aspects:
- Evaluating the AI (changing position, state, ...)
- Evaluating the rendering state. For example which sprite cycle to use when the entity is walking, climbing, getting hit, ...
- Actually rendering the sprite
Most tutorials propose to use something like an AISystem (1.) for logic and a RenderSystem (3.) to show a sprite (cycle) that is defined in a RenderComponent. What they do not say is where the RenderComponent is updated. It is my understanding that just putting (2.) into (1.), thus mixing character logic with rendering logic, would be bad design.
The straight-forward solution would be to add a specific render logic system for each enemy. So for example for a Gumba, I could add a GumbaLogicSystem, a GumbaRenderLogicSystem and for actual rendering, a generic SpriteRenderSystem that all sprite based entities use. However, this means creating two components* and two systems for every entity type, which does not seem to be a good solution.
Is there a good solution that separates character logic and rendering logic while keeping the number of systems small?
Thank you
(* = in a simple approach, a system processes an entity depending on its component. In order to have the GumbaRenderLogicSystem work on the entity, it needs a GumbaRenderingLogicComponent in order to be recognized by this system. The same is true for the character logic.)
Edit1: I am aware that ECS is an abstract pattern. My question aims at best practices on how to keep the number of systems small. My example is motivated from game programming but not restricted to this area. Let me explain with a more abstract example:
Imagine I had some entity that is visible. In a hierarchy based architecture I would have something like:
- SomeModel (inherits from AbstractModel)
- SomeController (inherits from AbstractController)
- SomeRenderer (inherits from PixelRenderer which in turn inherits from AbstractRenderer).
In ECS I would need a whole bunch of new classes:
- SomeModelSpecificDataComponent (i.e. data that is specific to this semantic entity)
- SomeModelSystem (that does the logic)
- SomeModelSpecificRenderComponent (i.e. rendering data that is specific to this semantic entity)
- SomeModelSpecificRendererLogicSystem (system that decides how to render)
- PixelRendererSystem
Is there any way I can reduce the number of new system that need to be introduced? One solution might be to add "agents" or "behavior objects": a general RenderingComponent is attached an instance of some "RenderingAgent" class that has a single method "Update" which is called in the RenderSystem. So technically the component does not contain logic itself. I fear this might be overengineered, though.
After thinking this through for a while and many discussions I realized, that my way of thinking might have been wrong. What I described would in fact be true for the vanilla ECS approach.
The only way to prevent an explosion of systems and components is to come up with a proper abstraction of your game elements, such that the various demands of a rendering can be described, instead of being programmed.
What I mean is that, for example, a sprite engine must allow sufficient abstraction, that you can express various animations and states only by descriptions that are stored in the rendering component that is evaluated by the rendering system. What you also need to do is to properly split your solution into reusable parts.
In this way, ECS forces you more than other patterns to really think about a good architecture.