Unity ECS at BGSjam 12
September 12, 2018
I made a “game” at BGSjam 12 and learned a bit about the in-development ECS system coming to Unity.
Prior to my now fairly stable dev platform choice that is Unity, I was working on creating my own little game engine in C++. I’d done a bit of research into engine tech, and became fascinated by data-driven design in games via Entity Component Systems. I managed to create one that kinda worked alright (though it was single threaded, kinda antithetical to the performance gains an ECS can get you), but I ditched it in favor of using an existing engine.
You can make an engine for your game, but you could also just make your game.
-Somebody smarter than me in 2014
Cut to 2018 and developers at Unity are hard at work making ECS a (or THE) reasonable method for creating games with the engine. I wanted to take a dive into the Unity ECS waters and took advantage of BGSjam 12 to do just that.
Instead of the usual approach to attaching components to GameObjects either via code or in the editor, the ECS system is (almost) entirely code-driven. Entities in your game are created from defined collections of Component types, called
World class serves as an entity manager, and handles all the adding, editing and removal of Entities and their associated Components. The Components are then acted on by Systems, which are automatically loaded at startup. This was kind of unclear to me working off their documentation, but once I figured it out it was actually pretty nice.
Another thing that was weird to me was the idea of injection. I’d never really even heard of or used dependency injection prior, so I didn’t quite get what the docs meant when they said that Components acted on by a System were injected at compile-time. Basically you create a
struct that contains a read-only integer specifying the number of Component sets the System will be able to act on, followed by
ComponentDataArray<> objects, which serve as generic collections of a particular component type. You follow this struct definition up with a private member of that struct type, and you flag it with
[Inject]. Apparently when the game starts this flag instructs the compiler to populate that member struct with references to every Component in the World that match that set.
So, if you have entities in your World that use Position and Foobar Components and want to act on them with a System, you define a struct that has a readonly
int for the Length, a
<Foobar>, and then add a single
[Inject] private Struct m_struct to hold all the components. The game builds, the compiler sees that
[Inject] flag, and tells the World to find every entity with a Position and Foobar component, and adds them to those lists.
If that wasn’t cool enough, it also ensures that those
ComponentDataArray lists match 1 to 1 with each other. As you iterate through
m_struct you know for a fact that the
Foobar components of
m_player match on a per-entity basis.
By default, the base
ComponentSystem class Systems inherit from all run on the main thread. There is a
JobComponentSystem though, structured and implemented slightly differently, that lets you multithread your systems. This means that you could spin up a job that uses X threads, letting you operate on a set of Components across multiple threads. That gets fast fast. And creating these kinds of Systems was pretty easy too. It’s how I’m handling the moving blue arrows and the spinning knockoff Saturn logos.
I think my big takeaways were there’s still a long way to go before this system is ready for prime time. The kinds of types you can use in a component are limited to blittable ones. Editor implementation is limited at best (there’s an Entity debugger window, and that’s about it). Providing entities with a mesh and material involves creating an empty gameobject in the scene with a component that holds the data. And entities don’t show up in the heirarchy of the editor at all. That said, all of these things make sense, and some of them will undoubtedly get better over time.
The hardest thing about using the ECS system is, well, using it. I’ve got something like three years of training my brain to think about game dev in terms of GameObjects and Monobehaviours that requires serious undoing before I can use this new framework effectively. That’s no small task, but I think it’s worth it in the long run. I don’t know if my next project will be 100% ECS, but it’ll likely have some element using it.
If you’re interested, you can grab the source code for my ECS DDR experiment on GitLab, and you can get a Windows 64-bit build on itch.io. Be warned: it’s bad jam code at best, and not a game at all. Also, I kept putting things in a “Testing” namespace for some reason. No idea why.