About a year ago, I wrote a blog post in which I discussed my change of heart regarding the way I wanted to program, and why object-oriented programming wasn't consistent with that desire in that it over-complicated problems and abstracted away what the computer was doing. Despite this change in heart, I still had the tendency to think of programming problems in terms of objects as opposed to thinking about the what the computer must do in order to solve programming problems; this is, of course, due to the fact that I had been thinking in an object-oriented fashion for such a long time.
This is embodied most by my game's entity system. Initially, when this project was first introduced to Handmade Network, I was using standard C++ polymorphism to store different types of entities. After I had moved towards writing almost in pure C (it was still compiled as C++, but I was only taking advantage of a few semantic C++ features, like function overloading), I was still doing polymorphism, but I was instead doing it manually (as opposed to using C++ language features to do it, which consists of employing virtual functions, inheritance, etc.). The word "entity", at this point, meant effectively anything you could see on the screen that needed to be stored in a map's file on disk. The player was an entity, the tile maps were entities, the trees were entities, and the particle generators were entities. It isn't too difficult to point out the fact that the word "entity" was nebulous enough that it meant almost
nothing. My reasoning for treating all of these different in-game objects as the same type was that they had commonalities. I might've said something like: "They all need a position and they all need to be rendered; obviously
they need to be stored as the same type!"
There was distinction between structures that a map consisted of, however; I also had "collision bodies" which were more specific than entities. They specifically referred to static physics objects that were arbitrary convex polygons; they were used to make the player run into (and land on) things. One might ask the following: "If the word 'entity' has a definition that has become so broad, why aren't collision bodies considered entities?" I asked the same question, and it made me feel as if my code was "unclean" or "poorly structured".
Over the months, I'd have trouble wanting to prototype, say, a new kind of object in the world, because the growing pains of my polymorphic entity system were becoming increasingly apparent. To add a new kind of entity, I'd have to make a new struct, add an enumeration value, potentially write a set of switch cases in several functions, ensure that the entity's data would be saved and loaded properly from disk in my map loading and saving functions, and add functionality in the map editor to ensure that when the new type of entity was added, it'd call the right entity initialization function.
. That's a hell of a lot
to do to prototype a game mechanic, and it definitely showed in my failure to produce many interesting game mechanics in an entire year. It has to be said, though, that heavily object-oriented languages make this sort of polymorphic system really easy to implement.
In C++, instead of just having a million switch statements, you'd start with an Entity class. Entity would have bunch of pure virtual functions. Then, you'd have a bunch of derived classes for each type of these entities; in each of these derived classes you'd write to all of the pure virtual functions and then, whenever that virtual function is called, at run-time the program will just look up the right function to call, and then you never have to touch those pieces of code in which those virtual functions is called! Every single type is encapsulated nicely into a little box, which makes for a really clean
Well, no... That's all kinds of wrong
. See, what implementing a polymorphic system in pure C has taught me is that a system like this is doing lots and lots
of unnecessary work: When I'm searching through all of the entities to find the player entity, why am I even checking tile maps if they're a player? Why am I checking static objects if they're a player? When I'm trying to render all of the entities, why do I have to check the type of each entity I'm trying to render so I can call the right function?
This demonstrates an important point. When heavily object-oriented languages hide away that type checking and type-based function call differentiation, that work isn't going away, it's simply being done for the programmer (as opposed to being done by the programmer). It's still happening, and it's still as much a part of the program as it would be if it were implemented manually, it's just no longer under the programmer's control: It has been moved away from the eyes of the programmer. So, in terms of what's actually running on the hardware when one runs the program's executable, the problem isn't actually clean or elegant, it simply provides the illusion of being clean and elegant
. The superficial layer of the program, the code, appears clean and elegant, but the literal instructions that the CPU is following to get something done are not, at all, "clean" or "elegant".
The job of a programmer is to transform data in a useful, effective, and efficient manner... right? That task is all done with processors. If what the processor is doing when it runs a program is not very robust, effective, and efficient... is the program well-programmed? The answer should be obvious: No.
This thinking led me back to my entity system. Why am I having to differentiate the functions I call based on the specific type of an entity? Heck, why am I even storing this data together? Why is the player right next to the tile-maps in memory which are right next to the particle generators and static objects in memory when they are, in fact, completely different? Even if the data were similar, they're handled completely differently!
The answer that I've come to regarding those questions is the following: Differentiation is okay. It's necessary. It's useful. Overgeneralization, on the other hand, is none of those. Generalizing something that can be made general can definitely be useful, but the programmer shouldn't stretch to generalize something; if something specific has to happen for specific data, why would anything else need to be written?
It's with this mindset that I've begun to narrow definitions within my program. "Entity" now means something far more specific: It is a sprite with physics with movement (good for the player, the old man in the game, other living things, etc.). I can still differentiate a bit depending on the particular type of entity, but now, that differentiation is far less wide-spread. It is also not polymorphic; if some kind of in-game object needs data that isn't supported by what the entity structure provides, then it's not an entity, but something else. Following from this, I've separated static objects to be something entirely separate from anything else. I've done the same with particle generators, collectibles, and water (among other in-game objects).
All of these in-game objects are now neat, fixed-size packages of data, and with this arrangement of data, there are already massive benefits. Now, all entities can be stored together as one big block of memory, and when I update entities, I can sweep through them all and update each one. I don't need to check what type they are, because now I can make more assumptions: An entity must meet this specific definition and must be updated in this way, so given a big block of entities, I'll sweep through them and update them in the best way possible. The same methodology applies to, say, static objects or collectibles. Additionally, to save all of the entities, I can just grab that big block of data and write it to a file- no type-differentiation is necessary! There are bound to be more benefits, I'm sure, and I'm bound to dig them up as I continue to work with this system, but these are the initially obvious ones.
So, the new entity system in my game contains a series of distinct sets of objects; each object in a given set is extremely similar to another object of the same set. There can be a bit of differentiation within these sets, but the definition of an object in each of these sets is far more narrow than my old definition of "entity". I'll be writing code in this system to be specialized, not general... and that's perfectly okay
Thanks for reading, everyone!