Game Development Essentials #1 - Don’t use inheritance for your game objects
unlikekinds.com
unlikekinds.com
This is how the Unreal Engine was architected and it makes it really easy to create new composite objects.
In contrast, when we did the first version of the MyMiniLife/FarmVille engine in flash, in the name of the speed, we made the mistake of not architecting the game object model in this way. We had to go through a painful refactor process 6-months after FarmVille's launch to fix-up the object model because we were duplicating behavior across objects that were really close to being the same...but not at the same time.
Couple other benefits of using this approach:
1/ You can let game designers dynamically create new objects without programmer intervention. Just let them mixup new components as they see fit through an editor.
2/ If you build a visual editor/CMS for your objects, you can do some pretty nifty stuff with custom editors/renderers for components. For example, the UnrealEditor at one point had a custom color picker for the ColorComponent that made it much easier to work with colors than entering R,G,B manually.
3/ If you build in serialization to your components at a low level, you can do things like have lazy deserialization of your components so that they are only instantiated/initialized once you access the component. This may not seem like a big deal at first but when you are working with tens of thousands of objects across millions of users, deserialization time ends up being significant.
However, if you're not very careful this approach won't scale past mobile games with a few dozen objects - too many iterations to update all the components, or to find one of a particular type (e.g. the physics component), killing your caches in the process (it's more complicated than that, but this is just the gist of it). A well known games company used this exact approach for their AAA engine (on a couple of games) and it was the no.1 reason for a sluggish frame rate. It took considerable effort to get some caching implemented so that there weren't as many indirect jumps around the code.
Splitting objects into components which are then kept physically close in memory and updated in batches is another story, but that almost anti-OOP in nature.
If we took all the advice offered by such articles, we'd all be using a procedural language with few features. ASM perhaps. Humans have various mental models when it comes to the world, technology, computing, and programming languages. Humans tend to make mistakes. The benefits of modern programming languages necessarily allow the human programmer to screw things up.
How about instead of "avoid X" we learn how to use X properly? We point out the disadvantages in certain situations rather than insisting on complete avoidance of X.
hythloday has the right idea here: http://news.ycombinator.com/item?id=3561182
It said "Don't use language feature X in this fairly specific situation and here's why:"
One thing I dont think the article mentioned is that polymorphic approaches are also often a lot slower, and this is crucial when working on platform hardware, especially the more constrained stuff.
All that said, some of the comments are correct; inheritence does have a place, but it should be used sparingly.
Personally, in C++, I have found that mixins solve this problem well: http://www.cs.umass.edu/~yannis/practical-fmtd.pdf
And for everyone who balks at multiple inheritance, I present to you some of my own code, which uses mixins and multiple inheritance: https://github.com/scotts/cellgen/blob/master/src/xformers.h... Probably baroque to an outsider, but it solved my problem well, and enabled me to have a maintainable code base that I could also play around with when testing new ideas.
More in this old thread: http://news.ycombinator.com/item?id=1992745
In short, use composition and if inheritance is what you want instead, it will become obvious soon enough.
In general, there is no such a rule that says: "HAS A" is wiser. It depends on the use case.
Are there any examples of using something like relational tables for game state?
Typically the approach is to push an "object graph" (actually it's usually a bunch of arrays) down a pipeline, for example animation → physics → rendering (for player walking), or collision → game mechanics → UI (for someone being shot). For cache coherency reasons, all the stages are done in a single operation, so where the data pipelines are too long to process in a single frame it's very common to defer processing to a later frame--for example a footfall event (caused by an animation) might trigger a sound effect on the next frame, because audio processing takes a relatively large amount of frame budget (which is OK because it has its own processor, and because we know that the next frame will come around in less than 16.7ms).
I think that pursuing a row-locked model is unlikely because of the amount of indirection involved--the Xenon (in the 360) and the PPE (in the PS3) are both in-order processors, so synchronous reads and writes to memory (obviously necessary for shared data) are absolutely destructive to performance.
[0] When I say "the high value of latency and the low value of fidelity", making a tradeoff involving a, say, 5% performance hit for safety or scalability is a no-brainer for most environments, e.g. a web app. In a game it can well mean the difference between being able to release the game, and having Sony and MSFT refuse to licence it because the framerate is too unstable.
Similarly, a strategy that involves throwing away user data if it becomes too large would be, let's say, eyebrow-raising in most industries, but it's very common to have fixed-size particle buffers and to simply retire old particles even if they haven't died naturally. This sort of "cheating" is pervasive and necessary in games programming, and honestly, it's part of the fun. :)
Consider what happens if your game character's arrow kills a monster while you are iterating over the list of entities. Do you remove it from the list right then? If other objects are depending on e.g. the number of monsters present, then you have a race condition where some objects will be updated before the monster was removed and some objects will be updated after the monster was removed and will thus see inconsistent views of the current state of the world... this can cause all kinds of fun, intermittent bugs!
This reeks of the fallacy that if I use method X really really poorly and I use method Y perfectly then obviously method Y is much better than method X. No you didn't discover that Y is better than X, what you found is good implementation beats bad implementation no matter how you assign the X and Y. So yeah, I am glad you found Y to be better than X, good for you. If only the rest of us were so lucky.
There is always a lot of debate in this industry over which way of doing things is "the best", and I don't think there's ever going to be a time where one "best way" of doing things will apply to all circumstances. Rarely are two problems in development exactly the same. How can anybody make the assertion that any solution will be a one-size-fits-all?
In regards to this example, I can see many instances where inheritance in games could be useful. Not every game needs as much flexibility as the author suggests.
As long as code does what it's supposed to do, as fast as it should be doing it, is well documented, and is understandable and clear, the path you take to get the job done is irrelevant.
That is the thing, the way our system got put together it isn't flexible. Weapon damage got separated from weapon animations, so now there is no way to dual wield or have shield bashes because there is no way to link damage to the weapon animation. That happens often in our system, things are sliced and diced so finely that you assemble everything out of these little blocks like lego but they are devoid of context so it is impossible say this is apart of that. That is fully a problem with our specific design not the general strategy.
Also it is rather slow. We store our entities in a database, so the ORM auto generates a 40 table join to check for all the different components we have(performance issues? like hell you say.) Also for some reason we keep our game objects devoid of all context so there is no way to tell if you have an npc or a potion without examining all the components it has and making a guess. Basically in our case the flexibility is a lie. Sure you can gin up any set of components you like but you have no clue how the system is going to react.Inheritance has its place in game dev though. It's just that your hiearchy needs to be designed in the space of what you are actually building: not vehicles, weapons, and monsters but renderers, physics, AI, GUIs, etc. And you want to figure out how that stuff is going to work before you break it up into classes.
This post is interesting, it kinda goes against what I think of when I think of OOP. I would have modeled it with inheritance of vehicles, weapons and monsters. But after reading this post I could see how that could be a bad idea.
But I'm curious in what you say. "Hierachy needs to be designed in the space of what you're actually building: renderers, physics, AI and GUIs." Do you care to explain this farther? Why those over the concrete objects? What do you even mean by it?
EDIT: Specifically how what do you mean by having inheritance of physics, renderers, AIs and GUIs?
EDIT II: Grammatical fixes.
When I was in college, I was taught OOP in Java via the obligatory inheritance examples: "So we have an Mammal class which has a 'walk' method, and then we have Cat and Dog classes which inherit from Animal, and have 'meow' and 'bark' methods."
This is a sadly ubiquitous anti-pattern which will cripple your ability to do OOP until you unlearn it. Never model the actual domain. Doesn't matter if it's a game ("okay, so the PlateArmor class inherits from the Armor class") or a CRM ("okay, so the Comment class will inherit from the FormattedContent class").
Instead, model how the program should work. How to do that is a bit beyond the scope of a comment, but a good start is to think about behaviours and interfaces that logically fit together.
(And, again, this tends to be a divisive issue. But I - and a lot of very good programmers whom I respect - strongly feel that the "Cat inherits from Mammal" example is exactly and precisely what you should never do. Despite the fact that it's how OOP is taught in most courses and books, seemingly.)
Some examples go as bad as: Square inherit Rectangle inherit Shape because "all squares are rectangles and all rectangles are shapes"......
1. Inheritance is IS-A. A Square ISN'T-A Rectangle because it breaks Rectangle's contract. If I give you a Rectangle object, you know that you can change the width without affecting the height. If I give you a Rectangle reference which happens to be a Square instance, setting different width and height will either cause a crash/exception, which is not what a Rectangle should do, or ignore one of the values and set the height equal to the width, which is also not what a Rectangle should do.
2. A bit subtle, maybe, but important. While your renderer probably has a method taking Shape objects, it is very unlikely that you have an interface somewhere that takes Rectangle objects. In that case, what you actually need out of this is some code reuse between Rectangle and Square, not type inheritance.
The "wrong" way (or what I view as the wrong way) tends to be common in academic settings, and (seemingly) among Java programmers. It is often contemptuous or wary of multiple inheritance, and it follows the Nygaard Classification[1] "A program execution is regarded as a physical model, simulating the behavior of either a real or imaginary part of the world."
This definition makes sense when you realise it was created by the inventors of Simula to describe their OOP simulation language. Unfortuantely, Simula was far more influential than it deserved, because OOP is terrible at simulating things[2]. When you create a class structure based on the actual simulation you (1) will have a bear of a time doing it, (2) will find multiple inheritance will melt your brain ("okay, so Black and White inherit from Color, and Zebra inherits from Black, White, and Animal...") and (3) will find than once your done you haven't actually solved your problem. (Not surprisingly, Simula didn't have multiple inheritence, and language without that feature often seem to gravitate to Nygaard-style OOP.)
So much for what not to do. What about the right way? I don't really have any links, but good principles are: Focus on interfaces and behaviours. Classes should be abstract (in the common sense, not the language keyword), not concrete. Understand how your program works, break it down into functional areas, implement each area as a class. If you find common behaviours, abstract them into base classes which you mixin where you need them. Inheritence trees should be flat, minimal, and almost an afterthought. Also, a language like Python (with duck typing) is a lot easier for most people to "get" OO than Java.
Finally, the way it was explained to me that really "clicked" is this:
"Where I think most introductory courses in OOP go wrong is introducing objects as being nouns rather than a collection of verbs. That leads directly to improper use of inheritance. I was misled for years by the 'is a' idea. I wish someone had told me 20 years ago that it was 'has the behavior of' that was important."[3]
I think that's exactly right. But do keep in mind that the world is full of people who think that's a heretical view and the Nygaard Classification is the One True Way. :)
[1]: http://c2.com/cgi/wiki?NygaardClassification [2]: http://lambda-the-ultimate.org/node/3265#comment-48063 [3]: Omnivore, in a conversation on the ##stars! channel on Freenode
- http://en.wikipedia.org/wiki/Circle-ellipse_problem
- http://en.wikipedia.org/wiki/Liskov_substitution_principle#A...
A physics engine will have classes for the things it cares about like masses, constraints, collisions, contacts, etc. A renderer could have things like meshes, materials, particle systems, and lights. Each subsystem has a different idea of what a particular vehicle, weapon, or monster is. Two monsters might have the same physics properties but be rendered very differently. A Koopa and a Flying Koopa might look the same but behave differently. You can't start with classes for vehicles, weapons and monsters, and expect these classes to be used across all subsystems.
OOP is really a way to organize your code. OOP doesn't generate solutions.
http://lambda-the-ultimate.org/node/3265#comment-48061
http://lambda-the-ultimate.org/node/3265#comment-48063
The general idea is that your objects should model the program, not the domain.
Games may blur this line since they are simulations in some respect, which is a situation where you actually do want to model the domain. That doesn't mean you need anything as unsubtle as classes with a 1:1 correspondence to domain objects, though, which is what this article is about.
Trying to simulate things that don't require simulation (aka, modeling physical properties), is a bad idea. You're not really making a program then, but a very specific, complicated subset that requires way more work and doesn't get your job done.
I'm convinced they're actually identifying a deep problem with inheritance as a paradigm.
I won't say it's useless; sometimes you really do have an ironclad relationship. But I think if you just want to communicate that your object fulfills a promise, an interface is better. And if the code reuse is circumstantial rather than essential, you're better off reaching for multiple inheritance from partial classes -- traits, mixins, whatever your modern language calls them.
This design pattern gives you the freedom to select algorithms for different behaviours without being tied to the strictness of the inheritance hierarchy.
What was it Alan Kay said? It's not about the objects, it's about the messages. (That is, messages and redirecting messages).
Hell, "inherit less" is what you have to say to junior engineers CONSTANTLY upon coming out of school.