ECS Architecture

Posted On: 2019-10-21

By Mark

I have recently been looking at Unity's new ECS architecture, to see if it might be useful for solving the save issue I described in last week's post. This is not the first time I have looked at it (I even mentioned it in a previous post), but, each time I consider it, I am tempted by its strengths and concerned by its limitations.

What is ECS?

For those that are unfamiliar, ECS (Entity Component System) is a software architecture pattern that enforces the separation of data and behavior. In ECS, each item that the program is representing (such as a character in a game) is an "entity". The entity itself has no data or behavior - instead, it is associated with multiple components, each of which contains a small piece of data about that entity (basically the adjectives describing that character, such as how tall or how fast they are). These components contain only the data about the entity - they do not contain any implementation of behavior (basically the verbs of the character, such as running or jumping).

Behavior is implemented only using systems, each one of which is written such that it can be completely dissociated from any particular entity. Thus, when implementing a mechanic (such as character movement) a system must be created such that, for every entity, it will always behave correctly (such as characters can run, but walls cannot). Typically this is accomplished by associating each system with a subset of relevant components (such as movement speed and player input.) The system then only affects those components, ignoring all the rest (so, for example, characters with the "player input" component can move, while walls, which do not have that component, do not).

The best example for this is (imho) actually a game. The delightful rule-bending puzzle game Baba is You can be clearly described using ECS terminology*. If you are unfamiliar with the game, please watch the trailer - it's about a minute long (no sound required) and succinctly describes the game's core mechanics much better than words can.

In Baba is You, each entity (such as Baba or the Rock) is associated with a word ("BABA" or "ROCK", respectively). All objects with that word are grouped together, so that they have the same rules (thus "Rock is Push" means that any rock can be pushed). In ECS terms, those rules are enity-component associations: when "Rock is Push", that means that any entity with the "Rock" component also has the "Push" component. The logic of the game, then, is like the systems of ECS: based on the components on each entity, it performs specific changes to the entity each turn. When any entity moves into an entity with the "Push" component, it (the entity with the push component) is pushed out of the way.

Perhaps the clearest example is the "You" component: anything with the "You" component responds to player input. Thus, while "Baba is You" the player controls Baba, but when "Rock is You" the player controls the rock. Importantly, if both "Baba is You" and "Rock is You", then the player controls both Baba and the rock, simultaneously. The system doesn't care which entity has the "You" component, or even how many entities do; it simply updates them so that when the player pushes up, any (and every) entity with the "You" component moves up. Relatedly the (soft) lose condition for the game is when no character has the "You" component: the game will continue even if that happens, but, since nothing responds to your input, you probably can't win anymore (thankfully, the undo and restart mechanics are implemented so they work even without a "You").

Architectural Benefits of ECS

Now that it is (hopefully) clear what ECS is, I'll describe what advantages I would gain if I used it. I will be focused on just the ECS implementation specific to Unity (as that is the one I have researched most extensively) and how ECS could improve my project's architecture*. Additionally, since most of these are deep enough topics to warrant blog posts of their own, I will try to describe them succinctly, focusing on generalities rather than details.

Limitations of ECS

For all its benefits, ECS has significant limitations as well. I will use the same approach as the previous section - a succinct list of limitations, focused on what most affects my project.

The Choice

Every time I investigate ECS, I am always faced with a choice. Do I fully commit to ECS, converting my existing code and accepting all its limitations? Do I try to use it in hybrid mode, and incur the costs of mapping data between ECS and Unity's normal architecture? Or do I simply admire it from afar, knowing that the costs of using it do not justify the benefits it will provide? Considering my current problem is a maintainability issue (each feature I implement requires new code to save its values) it seems that the hybrid approach would simply be just trading one problem for another. Thus, I am left with the choice between using ECS fully (including converting all my existing code) or not at all. For now, I am leaning into the "not at all" option - but the cost/benefit for that choice may change, depending on what (if any) alternatives I am able to identify.

Conclusion

ECS is fascinating, providing many architectural benefits with steep costs. For projects that need the performance, ECS is certainly beneficial, but even for other projects, it may still be quite useful - provided that they can cope with all the down sides.