TGRCDev
Back to Blog

This article is a work in progress!

An Intro to ECS Architecture

Entity-Component-System is an architectural pattern that has existed within the games industry for over 25 years, but has only been explored in generalist game engines in the past decade by projects like the Bevy engine in Rust, the Arch framework in C#, and Unity's optional ECS feature. Where most game engines adopt the object-oriented philosophy, with rigid type hierarchies and nested game object trees, ECS describes game objects through a combination of components, which are just data classes with small amounts of relevant state, and large query functions that run across specific component sets.

Why?

TODO

Entities

Entities are simply a unique ID. More broadly, an entity is a thing. Your chair is an entity. A tree is an entity. This website is an entity. Anything that can be considered distinct from the objects around it can be thought of as an entity.

On their own, Entities don't do anything. What we need to do is describe them somehow.

Components

Components are objects that we attach to Entities in order to describe them. If you make a sandwich, it might have the Food component which lets you know that you can eat it, as well as the Item component so you can hold it. Your phone is also an Item with a Battery charge. A bed would have a SleepableSurface component to describe it as a place your character can sleep, as well as a Flammable component to enable it to catch on fire.

This may seem like unnecessarily obtuse ways to describe typical objects in our life, but what they provide us is a way to describe novel ideas through reusable modules. What if you wanted a bed made out of cookie? Just take the Food and SleepableSurface components!

In this, components describe objects in two ways: by whether or not a given component is present, and by what data that component has. For example, we know what objects can be held in our hands because they have the Item component, but we also know whether or not our character can pick it up by checking if it's weight attribute is light or heavy.

An entity can have any number of components, but only one of each (i.e. A sandwich can't have two Food components). An intuitive way to think of an ECS world is as a spreadsheet. Each row is an Entity, and each Component is a column. An empty cell means there's no component of that type attached to the entity.

EntityIdNameSleepableSurfaceFlammableFoodItemBattery
1Sandwichflavor: savoryweight: light
2Bedcomfy: yes
size: large
3Phoneweight: lightcharge_percent: 87

On their own, Components do not actually "do" anything. They're simply data containers that describe the entities that they're attached do. To make these entities function, we need to introduce the final, key concept within the ECS architecture.

Systems

Systems are functions that run on a subset of Entities, given a subset of Components. If you've ever used a relational database, then systems are a SELECT/UPDATE/DELETE statement.

For example, suppose we want to simulate that any device with a Battery component slowly loses charge over time. We would query the ECS world for all entities with the Battery component present, and reduce their charge_percentage by a small amount every frame.

void BatteryLoseCharge(float frameTime, List<Battery> query) {
	foreach(var battery in query) {
		// Remove 0.01% battery every second
		battery.charge_percentage -= 0.01 * frameTime;
	}
}

This system would run every single frame and update every single Battery component, but this isn't always the most effective or efficient way to do logic. What if we want to have logic that runs only at certain times, or on a single target entity? In the next article, we will dive into Events, how they can encapsulate more nuanced interactions and conditions, and how they can prevent messy coupling and support new components and mechanics easily.