Don’t get me wrong, I’m not against object-oriented programming (OOP). More often than not, it’s a valuable method for modeling data and logic in games. However, because of its near-total dominance of thinking regarding programming in games, I often find myself playing the anti-OOP advocate in conversations at work.
You see, there are times when designs that look “right” when viewed through an OOP lens turn out to be ill-suited to solving certain problems in game programming. One of the major causes of this is the tendency of OOP to encourage thinking of problems in terms of single data objects. Often this works because handling multiple instances of an object is a natural extension once you know how to handle a single instance. This isn’t always the case however and it’s common for OOP-oriented programmers (OOPOP?) to overlook cases in which it would be better to think of processing data as a gestalt as opposed to as lists of stand-alone objects.
To clarify this idea, I’ll share a recent experience with some code at work.
The story that follows is a familiar one. We, the programmers, had adopted a fully-featured game engine with a suitably large codebase. The engine’s 3D features were, as to be expected, top-notch. The engine’s 2D feature were not quite so developed. It mainly consisted of a set of functions to draw sprites onto a 2D HUD at particular positions, scales, rotations, etc. It had no useful UI features like buttons or other widgets nor could it handle hierarchies of sprites. This meant that any sort of menus beyond the simplest single-depth affairs would be impossibly clumsy to implement.
So, like any good programmer, one of our number set about extending the engine’s 2D HUD system with our own hierarchal sprite management system as well as some basic widgets. Throw in some runtime tools for tweaking UI layouts and we were golden. At least, we were until we started implementing some of the more complicated menus in the game.
You might not notice it at first, but most game menus actually have significant amounts of information being displayed at once. Unchecked, these menus can result in surprisingly large numbers of sprite renders. And that’s what we had in our project. Lot’s of sprites being rendered at once and our framerate tanking on our menus. Heavy menus? I thought the game itself was supposed to be the heavy part? What happened?
Well, having the hierarchal sprite system written in a scripting language didn’t help performance, but that’s a straight-forward transcription away from being solved. More to the root of the problem was the design of the hierarchal sprite system itself. You see, it was almost text-book OOP with “sprite” being the core object. Sprites were independent objects capable of rendering themselves The first thing the programmer probably did was write code to make sure a single sprite could render itself. Then, he made sure they could be connected hierarchically, deriving their absolute screen position based on their relative offset from their parent.
For the most part, that approach makes sense, until you look carefully at how sprites were actually being used. Almost every sprite we used was the child of another sprite in a multi-level child-parent hierarchy. We would render our root sprites and expect the render command to travel down to child sprites through the hierarchy. So far, so good. But, every sprite was designed to be able to calculate its own absolute screen position. To do this, each sprite would query its parent’s absolute position and add its own relative offset. These absolute position calculations therefore involved climbing back up the hierarchy to the root. This operation was performed by each child and would only become more expensive as we went deeper down the hierarchy with each depth requiring longer trips back to the root to calculate screen positions.
“Wow”, you may say. “That behavior is horrid.” And you would be right. If you’re already traveling down a hierarchy, it makes sense to calculate values like absolute screen position and propagate them downwards as you perform the traversal. But, it was surprisingly non-obvious from the OOP perspective. Had the programmer who implemented the sprite system thought in terms of how hierarchies of sprites should be rendered as opposed to how single sprites should render themselves, I imagine he would have written very different code.
Object-oriented programming has a powerful tendency to encourage thinking in terms of single objects and assuming that the handling of multiple instances of said objects is a trivial extension. For a lot of high-level game logic, this is probably a correct assumption. But, in cases where you need to process large amounts of data, this sort of single-instance thinking can be debilitating. While swearing off object-oriented programming would be akin to throwing the baby out with the bathwater, it’s good practice to occasionally question your own thought process and approach problems from a different angle.