Explaining (and making the leap) to ECS in Unity

MY.GAMES
MY.GAMES
Published in
8 min readFeb 28, 2023

It might be a little tricky when first switching to the Entity Component System from your usual framework, but once you realize the benefits, it’ll be hard to go back. ECS: how it works, why it’s necessary, popular implementations, real-life examples!

While looking for new ways to rapidly develop prototypes, we remembered a wonderful approach: the Entity Component System (ECS). Much has been said about it, but little has been shown.

My name is Andrey Boronnikov, and I work on the experimental projects team at MY.GAMES. In this article, we’ll talk about the Data Orientation System, and where Unity is heading in general. Then, we’ll dive into how ECS works, why it’s necessary, who uses it, its most popular implementations — and, of course, we’ll look at examples showing the practical application of ECS in game development.

So, let’s begin.

The future of Unity

The most vigorous discussions on ECS generally began once Unity Technologies announced intentions to integrate this approach into game development with their engine as deeply as possible. But why did they decide to do this in the first place?

Unity Technologies is known for its quest to develop a universal game-creation engine: suitable for everything from casual match-3 titles to photorealistic AAA projects.

Accordingly, Unity strives to cater to everyone’s needs. They want to give people the ability to work with their engine as efficiently and comfortably as possible. The focus is now primarily on two issues:

  1. Game development speed
  2. Game optimization

To a great extent, game development speed is actually directly correlated with a particular studio’s development pipelines. But, with the second point, Unity is actively trying to help us, the developer community, with game optimization.

So how can we optimize games?

Unity has identified three main areas ripe for game optimization:

  • Optimizing calculations: the fewer mathematical operations the system carries out, the faster the game runs.
  • Distributing calculations into several separate channels: here, execution runs simultaneously in two or more streams, thereby speeding up the game.
  • Correct data handling: this reduces the number of databus requests, and hence, reduces processor load.

This is all taken into account in Unity and there are several software solutions forming the Data-Oriented Tech Stack. This is a set of technologies focusing on using the capabilities of modern hardware to the maximum. Let’s take a more detailed look at some of these aspects:

  • Burst Compiler — this new LLVM-based backend compilation technology turns C# code into highly optimized machine code. It was released as a Unity package and integrated into the engine using a package manager.
  • To simplify the development of multithreaded code, Unity released a library called Job System. It allows developers to more safely work with multithreaded code.
  • But for the most part, in this article, we’ll talk about working with data. For this, Unity is trying to use, refine, and implement an architectural pattern, like the Entity Component System. Unity has tried to implement a set of optimizations and improvements, but whether they’ve managed to do so is still debatable.

In order to fully understand the situation, let’s look deeper at ECS.

So what exactly is ECS?

As we’ve already mentioned, ECS consists of three concepts: Entities, Components, and Systems. These three entities represent the operation of the entire game architecture.

An Entity exists in the game world, similar to a GameObject, if we compare it to Unity. But in an ECS, it’s essentially just a null pointer to a set of Components bound to a particular Entity. Components are a set of data: for example, acceleration for calculating gravity, or coordinates for a position.

All logic is contained in Systems: these are classes that perform some specific task, but in a particular way.

A Gravity System is the part of the game responsible for the impact of gravitational acceleration on objects. It accesses the game space, which stores all our Entities, and requests only those that contain Gravity components. In other words, Gravity is a component that says that our object is affected by gravity.

(We can compare it to SQL queries, where we indicate only specific sets of data when forming a query.)

After that, the system receives a set of Entities and then carries out the necessary mathematical operations. In the diagram, these are changes in the object’s coordinates under the influence of gravity. The system adds the result to the Position component, and transfers control to the next system, which in turn also addresses all Entities that match its requests.

Thus, the entire game cycle depends on the sequential transfer of control from one system to another, and we can accurately trace the entire path of data processing. Generally, systems may not interact with each other and may not know about each other, but they communicate through events or components that are transferred to the common game world container. One of my colleagues compared this to data buses. When we leave a message on the common bus, systems that need messages of a particular type process it. In my opinion, this is the most coherent explanation of the communication principle between systems.

The advantages of the ECS approach

First, one of the most important advantages of ECS is the low interdependence of code. Thanks to this, you can turn off unnecessary systems, features, and game mechanics, if the game logic requires it, and if everything else will work without interruptions. For example, if you turn off the system that kills players when their health depletes, then players will stop dying — this is similar to activating a cheat. And this applies to any game mechanic.

The second advantage, and the reason why ECS is used in game development so often, is the ability to combine entity properties.

When writing a game within classic object-oriented programming, you can inherit one class from another. For example, both Moveable and StaticObject classes, which describe moving objects and static ones, as well as InvisibleMoveable — an invisible moving object — are inherited from Renderable. If we need to implement other entities — bullets, asteroids, etc. — then we will inherit them directly from Moveable.

We may encounter various problems when scaling this tree, which will mess with the inheritance structure. But in ECS, we can just add components that need processing: we can create MoveableObject and add only the physical movement in the necessary direction and nothing before that. Or we can assign Asteroid and Bullet classes to an element, and it will be processed both as a bullet and as an asteroid because the bullet processing system will work separately from the asteroid processing system. This way, game designers enjoy greater variability and the ability to combine any necessary Entities.

So, to summarize the advantages of ECS:

  1. Low code interdependence
  2. Easy to write code for distributed computing
  3. Ability to combine properties of entities

Ok, but where and how is ECS used?

ECS architecture is found in many modern games. One of the first examples I’ll point to is Dungeon Siege. Here, the developers tried to organize the architecture in a way that would make it possible to create separate entities (spells, effects) for units so that these entities would combine with each other and be processed in a special way — sometimes even in a way that the developers did not initially envisage.

More recent examples include Operation Flashpoint, where the developers used ECS to implement network code. With ECS, it’s sufficient to simply transfer a set of entities with their components, and it will be automatically easier to sync up work with fellow developers.

Raid: Shadow Legends is an even more recent example. With this game, the developers used ECS in order to optimize development speed, demonstrating that it’s possible to develop a new feature without tossing out all the previously existing code — you just need to add a new system, add new components, and thus, a new feature is born. This workflow significantly speeds up the development process.

Looking at ECS in practice

Now, let’s move on to describing specific cases of using ECS.

Naturally, let’s start with Unity. For quite a long time, they’ve doggedly tried to develop their ECS, although currently, the results are far from ideal. Updates are released every six months which completely change the API, greatly slowing down full-fledged development of the system on the engine library.

Unity is trying to implement more efficient memory management, which is actually closer to Data-Oriented Design than ECS. Therefore, a special feature inside the Unity implementation is the use of Chunks, in which individual archetypes are placed. An archetype is a selection criterion for existing entities that contain a specific set of components.

In the picture above, we see entities A, B, and C, which are divided into two archetypes M and N. The system works in such a way so that when an entity’s archetype changes, it’s automatically pulled up and placed in the necessary Chunk so that the processor can focus on one at a time without jumping to individual elements.

There are a huge number of ECS implementations from the community, for example:

  1. Entitas
  2. Svelto.ECS
  3. Morpeh
  4. actors.unity
  5. DefaultEcs

Not quite a conclusion

So, we briefly examined how a typical engine-independent ECS system works. The most difficult thing is keeping your cool when you write using ECS, because it’s so easy to split the game into separate systems, and you lose the ability to turn them off one by one.

Therefore, it’s necessary to support code review, and be sure to exchange opinions with colleagues so that there is a common understanding. With experience, you’ll become more aware of when to separate certain systems and when to avoid it.

Two additional GameObjects with a list of currently running systems help find the root cause of problems during debugging. Although this is not a silver bullet, it’s still a very good way to optimize your development process because such code is very easily and quickly modifiable.

One final though: you may also face a «brain shift» problem when you’re first switching from your usual framework. It took me a week, and it was very difficult at first. But when you realize how to work with this architecture and how effective it is, I seriously doubt you’ll want to go back!

--

--

MY.GAMES
MY.GAMES

MY.GAMES is a leading European publisher and developer with over one billion registered users worldwide, headquartered in Amsterdam.