Technical Talk: “Flexing” the Muscles of ECS

Over the years while working on hobbyist projects and most recently Code 9, I’ve come to really favor the ECS (Entity-Component System) approach in programming. It certainly has its own set of pitfalls separate from traditional OO-programming, but much like any methodology it has its advantages when used correctly. If you aren’t familiar with the two paradigms, I suggest looking them up; for the sake of brevity, I won’t explain them in depth here. Instead, I want to show an example of how I use ECS to share functionality in Code 9.

Some paradigms, such as OO, struggle with shared functionality or “multiple inheritance” issues. While the component based nature of ECS is supposed to help alleviate that, it can come at the cost of having “component bloat” or, rather, being too abstract. Though components enable you to be very specific about what goes where, it doesn’t mean you should try to make everything as interchangeable or vanilla as posssible.

One thing I’ve done that hasn’t quite panned out is a general-purpose UI functionality. Essentially, I wanted something that could arrange different position-based objects based on a list of coordinates. When an entity possessed a UI-based component(s), the UI-based system could absorb said entity and do the necessary work. Unfortunately, trying to make something generic that plays nice with all of the different options (Sprites, Text fields, actual entities, etc.) has proven to be very difficult. One approach I tried was to tie everything into a typedef that would structurally unify with all compatible types (much like how Haxe Iterators/Iterables work):

typedef AurBoundable
    public var x:Float;
    public var y:Float;
    public var width:Float;
    public var height:Float;

class UIComp implements AurComponent
    public var data:Array<AurBoundable>;

Unfortunately, data access modifiers unique to a certain class made type unification a bit tricky. At the end of the day, going “vanilla” actually slowed me down. The functionality has served its purposes for the time being and I may make another pass over it sometime in the distant future, but right now I’m not looking to use it for any new functionality. Hard-coding distinct menus and grids from scratch as new components looks to be quicker.

I now realize that the specificity of components–and system logic–largely has to do with what data it relates to and where it is. And the more specific a component’s purpose, the more informed you can be about how to set up.

When I was trying to add visual effects to the numeric slots for Code 9, I originally inserted the code directly into the slot-based logic. But as I started exploring visual effects for other field elements, I realized the code would be useful in several different places. Was it worth making the code more “vanilla” though? The visual effect in question was taking an animation frame from the entity and flaring it over top of it, gradually scaling and fading it out. The input parameters were values to control scaling and fading, as well as a reference/index to the animation frame to flare. The key piece of data here is the frame index, which would refer to the animation sheet in the following component:

class DrawComp implements AurComponent
    public var should_draw:Bool;
    public var tilesheet:Tilesheet;  // Animation sheet
    public var ts_index:Int;
    public var draw_x:Float;
    public var draw_y:Float;

With that in mind, it made sense that I could simply create a new component to work on top of/off of the DrawComp class:

class DrawPulseComp implements AurComponent
    public var data:Array<Sprite>;
    public var fade_rate:Float;
    public var scale_rate:Float;

The fade and scale rates are self-explanatory. The sprite list is to house all the extra sprites that are currently being flared over top of the main sprite. For the purpose laid out, this component fits the bill and is easily usable between different entities.

Working with entity-component systems over the last few years certainly hasn’t been a “free pass” on all programming problems, but the flexibility it provides is exciting when utilized correctly.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s