Game Engine Dev, Explained for Non-programmers: Entities! Components! ...System?
This series can be read out of order, but here are some navigation links for your convenience:
<<Introduction | <Previous post | Next post>
At this point, I’ve got all my tools set up; you might expect me to start talking about drawing stuff to the screen, or collecting the player’s input. But I already made my test app! I know I can do all that! I wanted to kick off the real engine project by building one of the many systems that would’ve been too much work for a test app, and what better place to start than the engine’s beating heart: the Entity system!
Do I know what this is?
Maybe. If you’ve ever used an engine like Unity or Unreal before, you’ll be familiar with ‘Entities’ (a.k.a. ‘GameObjects’ or ‘Actors’) in this sense. It’s an extremely common design pattern that many game engines use to organize and represent almost everything in the game world. My own system takes inspiration from those, while mixing in some ideas from Game Maker. I’ll tell you exactly how it works later, but before we talk about the “how”…
Why do this?
Let’s talk a bit about software architecture. There are a great many ways to think about it, but in this case it’s helpful to imagine it as a frame, a scaffold. What I’m doing here is building a core part of the thing that will make it easy to gradually construct a game by adding and tweaking parts while keeping the overall program smooth and performant.
Philosophies when it comes to this stuff are almost as numerous as the arguments surrounding them. To give you an idea of what I mean, here are a few ‘programming paradigms’ that one can follow:
Imperative and Declarative Programming: These are sort of ur-paradigms, and most other paradigms will fall under either umbrella:
Imperative programming centers around the idea of telling the computer what to do, usually as a list of instructions, which, as we saw in the previous post, is pretty close to how things work under the hood. C is the classic example of an imperative language.
Declarative programming is a bit stranger; the idea is that you’re describing, very precisely, your program’s problem domain, in the same way that one would “declare” that 1+1=2. Instead of incanting “do this, then this, then that!”, you incant “given this, it is so!”. “Running” your program is then just a matter of solving the equation with the given parameters. This is closer to pure mathematics than engineering, and this style tends to be a significantly less popular, but some declarative ideas have been enormously influential, even for imperative work. Lisp is the classic example of a declarative language.
Object-Oriented Programming (OOP): An incredibly popular imperative style. Due to how, er, storied its history is, it’s a bit difficult to nail down exactly how people use this term nowadays, but the basic idea is that your code is divided up into ‘objects’. Each object has data, as well as ‘methods’ (little bits of code that can be called to tell the object to do something). There’s a lot of neat stuff you can do with objects, but OOP has been criticized for encouraging programmers to come up with complicated object structures when a simple list of instructions would do fine. Java, C#, C++, Python, and many more popular languages are considered Object-Oriented.
Functional Programming: A style of declarative programming that has seen some (relative) success. The style is based around functions, specifically ‘pure’ functions. A function is just a bit of code that takes an input and produces an output, but the idea of a ‘pure’ function is that it doesn’t ‘remember’ anything, it can’t read or write to any memory outside of itself. This guarantees that the function will always produce the same output if given the same input, and that it won’t silently affect any part of my program without my knowledge. A purely functional style constructs a whole program entirely out of pure functions, a very declarative way of doing things, but pure functions can still be very valuable as part of an imperative program and many ideas from Functional programming have spread all over the place. Haskell is probably the most well-known functional language.
Data-Oriented Programming (a.k.a. Data-Oriented Design, DOD): An imperative philosophy that has gained popularity recently in response to perceived failures of the OOP style. The Data-Oriented philosophy encourages programmers to think more about hardware and memory (see the previous post for more details about this), and to envision their programs as machines purpose-built for funneling and processing large amounts of data in an efficient manner. Jai and ODIN are very much being built under this paradigm. To give a concrete example of DOD, say I’m making my little game with bouncy balls and whatnot:
If I were doing things OOP-style, I might conjure up a “ball” object, give it all the data and behaviors a ball would need, then stick a new one wherever whenever I needed it. When I want all the balls to do something, I’d define that behavior on the object, then go looking for each of them and tell them “do this thing you know how to do”.
If I were thinking of things from a DOD perspective, I’d say: “I’m gonna need about 100 bouncy balls!”. I’d figure out the exact way to lay out all the data about 100 balls would need, and bundle it in a way that makes it easy for me and the CPU to access. When I wanted the balls to do something, I’d make a big ol’ “process_balls” function that would churn through all that data and make it behave like about 100 bouncy balls.
Personally, I always try to do what makes sense for the task at hand, and my Entity system is designed to offer me the flexibility to do so. As the core around which most game features will be built, this is, uh, a pretty important thing for it to have.
And now, the how
To understand the reasoning behind the design of my engine’s Entity system, let’s take a look at how it’s design compares to the way I’d typically do things in Game Maker, with the help of some familiar faces from ANTONBLAST!
GM tends to push the user into an OOP way of doing things. You create “Objects”, and each has events that get triggered when something happens (my own system uses something very similar in this regard, so we’ll talk about it later). You’re also encouraged to use a very OOP technique for code reuse: Inheritance.
How a paradigm handles code reuse is core to its identity, one might even say its in a sense what defines a paradigm. Having to repeat yourself is time-consuming and tedious, so programmers are always finding ways to avoid it.
A classic example is functions themselves. I described them as bits of code that take an input and produce an output, but their real power is that you can ‘call’ them from anywhere, over and over, with just one line of code per call. If I have a complicated bit of behavior that I need to use often, I can put it into a function, then just call that function. If it takes no input, its practically as if I just copy-pasted that bit of code all over the place (but I can change every instance of it just by changing the function!). If it does take input, even better! I can make the function do something slightly different whenever I use it just by changing its input.
So, inheritance, what is it? Basically, it’s a way for objects to share functionality amongst themselves by “inheriting” it from a parent object. Confused? Let’s take the example of our three buddies1:
Pippo is a simple enemy that walks around. The player can bounce on him to knock him over, and can also hit him to send him flying.
Ballbuster is a more dangerous enemy that also walks around. When he sees the player, he’ll charge at him. Like Pippo, he’ll go flying when the player hits him.
Mosquito doesn’t do much, just floats in place, but the player can also hit him and bounce off of him.
You’ll notice that these are all enemies, and they all have behaviors in common. Instead of rewriting those common behaviors three times, I can put all the methods and data needed for them into a parent “Enemy” object, and have objects for each of the three enemies that inherit from it (any behavior specific to an individual enemy gets put in that individual enemy’s object).
But wait! Pippo and Ballbuster walk around, while the mosquito just floats in place! Where do I put the walking behavior? Well, inheritance trees can go more than one layer deep, so we can do something like this:
And now, say I want as version of Pippo that’s mostly identical, but who sheds feathers when he gets hit? Instead of copying Pippo somewhere, I can just keep extending things!
As you can see, objects and inheritance trees can be pretty powerful, and a natural way of modeling game mechanics like enemies, but as a project’s size and complexity starts to grow, they can run into some problems. Take this guy, for example:
He’s a big totem guy that the player can stand on top of. He spits stuff. Nice guy. Now, question for you, is this guy a block, or an enemy? Stuff will collide with him, just like a block, but he also attacks the player, and can be stunned, just like an enemy. You can’t inherit from multiple parents (for complicated technical reasons), so you kind of have to pick one.
Usually, in cases like these, I have to pick the most relevant parent, and cobble together the other behavior as best I can. The other option is to rethink the inheritance tree structure, but rearranging the tree can be time-consuming and annoying, and will often cause problems for other objects.
Here are some other issues that crop up with inheritance:
Say I have 10 objects. A handful of them want behavior A, a slightly different handful want behavior B, and another handful also want behavior C. Even if I’m aware of all this ahead of time, which I’m likely not, there isn’t an obvious way to make an inheritance tree that sorts the behaviors in a neat way. Making a parent with all three behaviors is doable, but would require shutting off certain behaviors in many of the children, which is annoying to do and keep track of, especially if it has to be done after-the-fact.
If a bunch of objects all want behaviors A, B, and C, but a few only want behaviors A and B, well, tough luck, they’re also getting C. I can turn it off, but this often still introduces complexity and performance overhead to objects for behaviors they aren’t even using.
Methods on parents can be inherited or ‘overriden’ by child objects, which involves the child either replacing or adding on to what a method does. This is an important and useful feature, but it introduces performance overhead related to having to retrieve all these parent and child methods while the game is running.
Once inheritance trees start to reach a certain depth they get… annoying. Did I put that behavior in this object or that one? Should this new thing be a child of this or should I put it further down? I need to do all these things in order on this object but skip step 3 on this other object, do I split steps 1 and 2 into their own method? Oops, it’s now 3 months later and I don’t remember which part of the sequence is in which method, fun…
Ok, so… what, then? This design pattern, built directly into almost every major modern language as a first class feature, used by most software out there, present in nearly every tutorial, is it just dumb and bad?
Uh, yeah, kinda.
I’m not the only one who thinks this, a lot of engineers have been distancing themselves from the excesses of OOP over the past decade and more, despite its ubiquity. But what’s the alternative? Personally, I’m not quite ready to jump fully off the OOP train, but if I’m gonna be avoiding inheritance for the most part, how am I gonna handle code reuse? This is where a compositional approach comes in.
Go on then
Let’s take a look, finally, at how my Entity system works:
Everything in the world is represented by an Entity. An Entity can represent anything: the player, a tree, an enemy, the sky, etc.
On their own, Entities do nothing, are nothing. Their existence is defined by the set of Components attached to them.
Components are little bundles of data that can be combined together modularly to form an entity. Components can be many different things. They might be, for example:
A set of coordinates that tells you where an Entity is in the world.
A set of physics parameters, like gravity and friction, which can be used to move an Entity around.
A set of rendering parameters, used to render an animated sprite.
So, to give a complete example, say I wanted to make an Entity that represented a gremlin enemy. I’d attach a position and movement Component, so that I can move it around; a collider Component, so that it can collide with things; a hitbox Component, so that it can hit things with attacks; and I’d make a special new “gremlin” Component that would be used for anything specific to the gremlin.
In addition to all that, Components can react to a standardized set of Events, just like Game Maker objects. The most common Events would be `init`, which is triggered just for a specific Component when it is newly added to an Entity, and `update`, which is triggered once per frame2. Put simply, I can define behavior for each specific component that will trigger in response to these events.
For the most part, this preserves a lot of what I like about Game Maker’s system while getting around the pitfalls of inheritance. Rather than bundling behaviors into parents, behaviors can be modularized into their own component and freely included or excluded depending on what a specific Entity needs. Take the totem guy from earlier: instead of agonizing over whether he should be an enemy or a block, I can just slap a bounce component and a block collision component on him and call it a day!3
The way the component system is designed under the hood also has performance benefits. Rather than going to each entity, scattered around in memory willy-nilly, and tracking down their methods, every component of a given type is kept in one block which the engine processes all at once. Since components are usually pretty small, processing them all in one chunk like this can be extremely quick. “Entities”, under the hood, are actually just like little address books that components sometimes use to get related components from a different chunk. Neat!
Hmm… still doesn’t seem very DOD
Well… yeah. If you were paying attention to the bouncy ball example, I’ll admit this isn’t quite that. I’m making a bit of a compromise… but hear me out. A lot of my work days look like me:
Getting a design for a new enemy, or block, or level mechanism.
Spinning up a new object that inherits from the relevant parent.
Writing up any new behaviors that object might need.
Revising and polishing that specific object.
And I haven’t quite found a way to map that workflow, in my head, to something that doesn’t end up looking at least a little bit OOP. The Entity system, combined with a dash of code generation and some (perfectly kosher) syntactic tricks, allows me to quickly spin up work environments that feel just like what I’m used to, but better.
But, and here’s the beauty of making my own engine, I don’t have to use the entity system for everything. If a new mechanic or system seems like it would benefit from a less OOP-like approach, there’s nothing stopping me from taking it in that case. So… we’ll see. Maybe, at the end of a long project with this engine, I’ll find that I hardly needed any Entities at all. But for now, I’d like to have some familiar ground.
Anything else?
Not really, though I’d be remiss to mention one last thing. You might have heard of “Entity Component System” architectures, or ECS. Despite the name, my thing is… not that. Rather than being Event-driven like mine, entities in an ECS system use things called “Systems” (brilliant nomenclature, I know), which are these things which identify entities which have certain sets of components on them and run code on them. It’s not a bad idea, but it’s never struck me as quite how I want things done.
At the moment, the Entity system is mostly done, but without other systems it’s like a heart with no legs. Or arms. Or pulse.
…
There are a few big things I need to get done before I really stress test it, the first of which we’ll be seeing next week: The Game Loop.
See you then!
<<Introduction | <Previous post | Next post>
Fun fact: The explanation that follows is real! This is actually how these enemies were planned out and built to work in the game’s actual codebase. It’s obviously a little simplified though.
We’ll talk more about the ‘game loop’ at a late date, but I’ll give a quick explainer here if you don’t know what I mean by ‘frames’: Like film, video games run at certain frame rate, often 60 frames per second. For every frame, the game will update the state of the world (e.g. moving objects will move a tiny bit, animations will go to their next frame, etc.) then render the result onto your screen. At a high enough frame rate, this creates the illusion of a smoothly moving world.
You might be wondering: some components (like, say, a position component) are gonna get shared around a lot, won’t that get tedious? But hey, there’s nothing stopping me from making functions that add a bunch of components at once, bundling them together, sort of like a pseudo-parent. I can even have components automatically add other components that they require in order to work, further cutting down on boilerplate code. The key here is that I’m not forced to keep behaviors in one bundle. If, in certain cases, I want to split them up, or make a different bundle, it’s trivial to do so.