Separation of Concerns

Maxim Zaks
4 min readSep 15, 2015

I am involved with an ECS implementation for Unity 3D called Entitas-CSharp.

Recently we got following question in the chat and with agreement of the person asking I would like to give an answer in this blog post.

First of all here is the question:

I have a question about how you’d put together a particular thing using Entitas. It’s long, apologies in advance.

Here’s how I’ve dealt with spawning enemies in a simple procedural game in the past (in Unity):

Procedural game with (say) 20+ enemy types. Each enemy type is represented by a prefab with rendering components (including meshes or sprites) and lots of monobehaviours attached, each with specific values assigned to the monobehaviours’ public fields. Some monobehaviours are attached to all these enemy prefabs (CanMove, CanBeKilled), some prefabs have special monobehaviours not included in the others (CanHeal).

There’s a scriptable object containing a list of EnemyLikelihood objects. Each of these objects has a reference to one of the enemy prefabs, a minimum level number (the enemy can only appear on this level and higher) and a relative likelihood (number used to determine how likely this enemy is to spawn relative to other enemies).

At the start of each level some game controller code with a reference to the scriptable object steps through the EnemyLikelihood objects, and uses that data to spawn the associated enemy (or not). It repeatedly steps through the list until enough enemies have been spawned for the given level (higher levels need more enemies).

In this arrangement it seems convenient to have enemy prefabs pre-populated with monobehaviours as a way to express the ‘recipe’ for a given enemy type. But from what I’ve seen this approach seems at odds with the Entitas way of doing things. I’m sure there’s a smart solution but I’m having a hard time imagining a convenient way of achieving a similar result.

Thanks for reading this far. Any pointers would be very much appreciated!

The main mantra of every ECS framework is separation of state and behaviour. So if we follow the first paragraph:

Each enemy type is represented by a prefab with rendering components (including meshes or sprites) and lots of monobehaviours attached, each with specific values assigned to the monobehaviours’ public fields. Some monobehaviours are attached to all these enemy prefabs (CanMove, CanBeKilled), some prefabs have special monobehaviours not included in the others (CanHeal)

I would recomend to keep only the rendering components on the prefab. We need prefab to render things. But I would strongly recommend keeping the state that you need for the game logic on entities as components. This way we can separate. When we want to change the appearance of the game object, we can change the prefab without fearing that we might brake some game logic along side.

CanMove, CanBeKilled, CanHeal are logical things that you might want to query for. For example: give me all entities which CanHeal, than use them in a system which will heal those entities over time. This is the strength of ECS approach. This system would be very easy to test. If you have this data on the prefab testing the healing process would involve instantiating a game object which is a 3D model in the first place.

There’s a scriptable object containing a list of EnemyLikelihood objects. Each of these objects has a reference to one of the enemy prefabs, a minimum level number (the enemy can only appear on this level and higher) and a relative likelihood (number used to determine how likely this enemy is to spawn relative to other enemies).

This feels like configuration to me. In the blog post “Games, Data and Entitas” I discussed importance of keeping configuration separate from runtime objects. We can see prefabs as configuration, but it is really inflexible with lots of baggage.

I would recommend keeping configs as separate data, which is easy to load from disk or server. For rapid iteration and balancing in the Unity Editor I would recommend a custom editor script which can reload config at runtime and restart the level. Having an editor window for editing the config can add even more pleasant experience.

At the start of each level some game controller code with a reference to the scriptable object steps through the EnemyLikelihood objects, and uses that data to spawn the associated enemy (or not). It repeatedly steps through the list until enough enemies have been spawned for the given level (higher levels need more enemies).

This is a typical System (Initialise system, or reactive system if you will) which does only one thing. The important detail here is, that it should be able to read config and create entities with needed components. Than another system might observe the entities which just got created (ReactiveSystem) and instantiate prefabs, creating a visual representation in the scene. We might want to link entity to corresponding game object. This is also described in “Games, Data and Entitas”.

In this arrangement it seems convenient to have enemy prefabs pre-populated with monobehaviours as a way to express the ‘recipe’ for a given enemy type. But from what I’ve seen this approach seems at odds with the Entitas way of doing things. I’m sure there’s a smart solution but I’m having a hard time imagining a convenient way of achieving a similar result.

Theoretically we can keep the prefabs as ‘recipe’ instantiate the game objects and than transform all the data from prefabs to Entitas components which you can add to an entity. However I think than we loose the most important part which makes every ECS valuable — Separation of Concerns.

Separation of concerns lets us separate game logic from rendering. Therefore let us test it and replace it with some other behaviour. This might seems like an overkill in the beginning, but it pays of in the long run.

--

--

Maxim Zaks

Tells computers how to waste electricity. Hopefully in efficient, or at least useful way.