Entity-Component System

Several years ago, after graduating from the Guildhall at SMU, I began writing my own game engine based on the rendering engine I had developed as part of the curriculum. This has been the basis for nearly every indie/hobby game I’ve released since, from Arc Aether Anomalies back in 2008 to Super Win the Game last year, and it’s something that I am constantly growing.

I’ve written about the motivation for rolling my own tech in the past, (notably in the “About” text in You Have to Win the Game), so I’m going to gloss over that today and instead focus on one of the biggest changes I’ve made to my engine over the last couple of years: the inclusion of an entity-component system.

Let’s jump back in time to May 2011. I had recently rolled off the  Duke Nukem Forever team and was looking forward to having some time to start working on a new side project. This became The Understory, an ambitious mix of first-person dungeon crawling, procedural level generation, and Metroidvania item progression. I worked on it for about four months before realizing that (1) the game was ridiculously overscoped, and (2) I had coded myself into a corner and would have to rewrite a lot of stuff if I were to make any more progress. I abandoned The Understory, though some of its ideas would come back in the untitled dungeon crawler I worked on in late 2013 (also abandoned) and now in Gunmetal Arcadia.

A month or two later, I decided to start up a new project, one that would be smaller in scope, and in which I could tackle some of the development problems I had encountered on The Understory. This would become You Have to Win the Game.

Right from the start, I knew I needed a better plan for authoring game entities. In The Understory, I had tried to write a classic OO-style entity hierarchy, with all my entities being derived from an abstract base class and virtual functions everywhere to differentiate things from each other. This failed spectacularly, as I ended up wedging in “bool IsPlayer()” sorts of functions all over the place. I needed a more flexible system, and the entity-component model felt like a good choice.

It took me a while to get a good feel for the scope of a component. Some of my early tests had physical properties (position, velocity, acceleration, etc.) each split out into their own components. In retrospect, this was obviously silly, as these properties are wholly dependent on each other and should all exist in the same component.

In You Have to Win, I had some minimal hooks to allow myself to initialize components from markup in a few very specific cases. This allowed me to define templates for things like bouncing hazards and bullet shooters. I could then instantiate these templates via another markup sheet that would define the entity’s position in a room. I had a rudimentary level editor built into the game with which I could draw maps, but entity placement had to be done through external text files and build scripts. Tuning rooms was a slow, tedious process.

When I started working on Super Win, it quickly became clear that the editor I had developed for YHtWtG would no longer suffice, and in building a new one in C#, I was also able to tackle the problem of placing entities directly within the editor. This also led me to find a more general solution for the problems of saving and loading entity data, culminating in a unified serialization path for all entities.

In Super Win, I began moving in a direction of having all components be entirely defined by XML markup. (In Gunmetal Arcadia, this is now the only way to define a component.) Eventually, the amount of markup text I was having to parse just to start up the game became a bottleneck. To address this, I wrote a new binary file format containing nested key/value pairs and a command line tool to convert XML or INI files to this format. This removed the bottleneck and allowed XML markup to continue to be a feasible option for defining components.

In Gunmetal Arcadia, I’ve begun abstracting away large parts of the XML markup behind a visual editor interface. I had already done a little bit of this in Super Win, specifying sprite sheets and entity positions automatically under the hood, but for this game, I’m trying to completely eliminate the large amounts of markup required to script NPCs and enemies. As I mentioned in last week’s post, one of my eventual goals is to have a visual interface for every component, such that I never have to think about the markup at all, but I’m not yet sure that’s a reasonable goal for Gunmetal.

One of the hurdles I’ve yet to completely overcome is the notion of scripting game logic with markup. I have a pair of related tags, <action> and <query>, which are recognized by a number of different bits of code and which allow me to, in essence, make function calls or branch on conditions in a script-like manner. This served its purpose well on Super Win, and I’ve reused it a few times already in Gunmetal, but the internal syntax for these tags is often inconsistent and lacks any sort of reflection to clarify their usage. In the future, I hope to standardize these in some way, at the very least by enforcing a consistent syntax, but also — if I’m really dreaming big — possibly by going so far as to provide a C-like scripting language that could either be translated into known tags or converted to bytecode and processed directly.

It’s clear that my entity-component system is not finished, but it’s getting better with every game and is becoming a fundamental, defining part of my game engine. I haven’t really talked about the actual code here, but as I’ve gotten accustomed to programming within this environment, I’ve made a number of improvements to facilitate safe, fast, concise access of components and their properties from wherever I need them. I can refer to components or entities by generic handles that abstract away the actual pointers and gracefully deal with problems like objects being deleted while something else is still trying to reference them. I can resolve references to entities or components of a specific type given either an entity or component point or a generic handle. This flexibility has been invaluable for writing the sort of code that entity-component systems naturally encourage, the sort of code that doesn’t care specifically what it’s looking at but knows how to deal with particular types of components which may or may not exist.