Overview of the Basic ECS Structures


Posted by Isaac Lichter on June 15, 2020

Hello everyone! Over the past two weeks, I have developed most of a framework for implementing Entity-Component-System architecture in Destination: Sol. Here’s a breakdown of what I’ve done so far:

My first sub-project was making a way to handle damage. First, an entity is assigned a Health component. The component contains information about the maximum and current health that the entity has. When the entity receives damage, like when it gets hit by a projectile, a Damage Event is sent to the entity. That event is then processed by the Damage System, which subtracts an amount of health from the entity equal to the damage amount. To ensure that the system works properly, I created three unit tests.

Developing the health/damage system was relatively smooth. There was just one issue: when I ran the tests all at once, they didn’t pass. When they were run individually, though, they each passed. The issue turned out to be that the three tests were sharing information about the systems registered to process events, but they were also each independently registering the Damage System. That meant that there were three copies of the Damage System, so the Damage Event was processed three times, resulting in triple damage. The tests all passed once that issue was fixed, even when run together.

My next goal was more challenging. I had to figure out how the receiveForce() and handleContact() methods worked so that I could refactor them. My first thought was to combine the impact handling from handleContact() with receiveForce(). After all, force is force. However, I had failed to account for the difference between a continuous force and an impulse (a sudden force).

Force gradually changes the velocity of a body over time. An impulse, on the other hand, creates an instantaneous change. When two objects collide, their change in velocity isn’t a slow acceleration. Force, on the other hand, causes a body’s velocity to incrementally increase.

Once I cleared that up, I created Force and Impulse events to represent the two interactions. But the way that I designed it didn’t work with the existing physics engine. I had assumed that I would be processing the velocity change myself. However, Destination: Sol uses libGDX to handle its physics.

That led to the current structure. In order to work with that framework, I made a Contact Event that gets called when the physics engine is about to handle a contact. Afterwards, the impact of that contact is processed by an Impulse Event. Forces are handled by a Force Event unless it is immune to force. The pull request was recently merged.

I also made a small component for entities that don’t need so much processing power. For example, an asteroid that is far off screen doesn’t need to be checked to see if it has collided with anything else. Such entities are put into stasis. That PR can be found here.

My next goal was a removal system. I created two events: A Destroy Event, for when an entity gets destroyed, and a Removal-For-Optimization Event, for when an entity no longer needs to exist. I then made a system to provide a default way to handle those events. I also made tests for both events.

The last pull request is for the first half of my current project, which is about graphics handling. There are two types of objects: the physical objects that require the graphics, and the graphical objects themselves. To represent the way that the physical object relates to the graphics, I have created a Drawable component. Every frame, the Drawable Update System sends an Update Event that will cause the graphics to be updated.

My current task is to figure out exactly how to design the structure for the graphics. Once I do that, the only core element left will be a representation of the physics engine’s Body.

Representing the Body presents a problem. In ECS architecture, the information about an Entity is defined in its Components. Components can only have specific data types, and should not contain any of the processing logic. The issue with libGDX’s Body is that it contains a mix of information and logic, and also isn’t one of the allowable data types. It can’t just be ignored, though - the Body is the main way that the physics engine interacts with entities. This problem is the biggest obstacle that I need to tackle.

Once I accomplish those two goals, I’ll be able to start making use of the new structures. This week, I intend to refactor asteroids. Next week, I will do the same for the Transcendent object, which is the object that represents a player when they go through a StarPort. I’ll post again about my progress in two weeks.