Hacker News new | past | comments | ask | show | jobs | submit login
Game Development Essentials #1 - Don’t use inheritance for your game objects (unlikekinds.com)
104 points by whalabi on Feb 7, 2012 | hide | past | favorite | 47 comments



This is great advice.

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.


It needs to be mentioned that components are not how UnrealEngine was architected, rather Unreal was designed as a monolithic single inheritance tree, and components were very recently just shoehorned into the system. Unreal still carries a lot of complexities from it's inheretiance even though some of the more basic systems have been converted into component systems.


This cannot be said enough. If you are in doubt of the truth of this post, look at Actor.uc.


The Component system is also at the core of the Unity3D game engine : everything in the scene is just a basic game object which just has a parent and a position ; and each specific behaviour (be it mesh rendering, collision handling, also all custom user scripts) is then added via components.


This is reasonable advice for the top 50% of your game (i.e. the bit you could write in a scripting language or a custom data format: AI, dialogue, game mechanics). For the bottom 50% (physics, effects, collision, animation), this technique will cripple performance because of the massive amounts of indirection and you will want to use a data-oriented programming approach instead.


Splitting objects into components and pursuing a more data-oriented design is absolutely the way to go.

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.


I've seen articles (book chapters, blog posts, usenet rants, etc) for years similar in intent. "Don't use language feature X because it can be misused and abused."

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


The article didn't say "Don't use language feature X because it can be misused and abused."

It said "Don't use language feature X in this fairly specific situation and here's why:"


This model is pretty mainstream these days and the best commercial implementation I've seen has to be in the Unity3D engine. It's almost entirely componentised, and writing totally generic 'work-anywhere' components is liberating.

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.


I'm surprised no one mentioned that this is, conceptually, prototype-based OO.

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.


This is good general programming advice beyond game development. If you think you need inheritance, you probably will be better off using composition because the general "is-a" rule of thumb is, in my opinion, a bad way to figure out if inheritance is required.

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.


Even though the suggested model is ultimately much more flexible, I'm surprised the article didn't even mention multiple inheritance as the most obvious solution to the dialog/animated problem. Not all the world is Java.


Inheritance is usually the tightest form of coupling in a programming language. It is also usually misused horribly. A component/plugin architecture for game objects wins hands down.


Eek! Multiple inheritance is not implemented in most programming languages for good reason. In general it is wise to favor composition over inheritance.


It is deciding between "IS A" and "HAS A".

In general, there is no such a rule that says: "HAS A" is wiser. It depends on the use case.


Related, an interesting article: IS-A IS-A HAS-A

http://weblog.raganwald.com/2008/03/is-is-has.html


Is a massive rippling mutable OO graph the universal way to do game state? It presumably makes concurrency an impossible deadlock-fest.

Are there any examples of using something like relational tables for game state?


Essentially, yes. Concurrency concerns in games are quite different to most applications (because of the high value of latency and the low value of fidelity[0]): typically there are around a dozen large computations to do 60 times a second (AI, animation, physics, collision, audio, effects, rendering, UI, game mechanics, pathfinding, networking), and the environment for games has been manycore (in the sense of both number and inhomogeneity of processor) since the PS2.

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. :)


Mapping an update function over a list of entities is a form of concurrency (note: not parallelism)... If you ever write a game engine from scratch, there are several tricky issues you must deal with because of this -- for example, the death and birth of new entities caused by traversing the list.

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!


Sorry if this sounds a bit ranty I recently inherited a really poorly done component based system at work.

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.


I don't think it's ranty at all, and I think you make a good point.

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.


Not every game needs as much flexibility as the author suggests.

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.


I would still use inheritance to create a basic addComponent/componentFunction class. I don't think inheritance is meant to be used for creating as much sub-classes as possible. But as with all software design, it is important to know where to draw the line. Where have you inherrited all you can? What you describe is pretty much inheritance vs composition. A tradeoff as old as oop.


In some sense, this is actually a workaround for languages that don't support MI.


C++ (where this advice is often given) supports MI, but there are some issues with its implementation (the diamond problem) that make it unsuitable to solve this problem in games.


Use traits and you get this for free, without implementing all the plumbing yourself.

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.


I don't come from a game development background, but I do find the realm interesting, and I'll probably have my crack at it soon enough.

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.


Not everyone agrees with this, but, here goes:

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.)


At least in that example the Cat is everything the Animal is.

Some examples go as bad as: Square inherit Rectangle inherit Shape because "all squares are rectangles and all rectangles are shapes"......


For completeness, Square inheriting from Rectangle is horrible for at least the following reasons:

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.


It's also interesting that this Rectangle/Square conundrum disappears when you remove mutatable state from the picture. I think it's usually best to model mathematical objects like these as simple value objects anyway, it's less surprising and more similar to how they are treated within mathematics itself.


The conundrum is that you can't define a canonical inheritance relationship between square and rectangle or mammal and cat because the relationship depends entirely on how you are using these concepts in your program. Making them immutable may affect the relationship but does not make it canonical.


That's more or less how I learned it, and never really realized that it was an anti-pattern. Do you have any links that go deeper into it?



It's mostly stuff I picked up from practical experience and talking to other programmers, so I don't have any links offhand. Let me expand a bit though.

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


I just mean that first you need to figure out how your engine is going to work -- what actual code you are going to write -- then go about organizing that code into an object model.

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.


Here are some nice comments on LTU that explain what I think the GP meant.

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.


I think the problem exists because people hear "object" and they think of physical objects, when, as you say, the objects in OOP are supposed to be something more abstract: a means of gathering together common code and common data, not a means of creating digital mirrors of real objects.


Objects when not doing a simulation are merely syntactic sugar of keeping the data and the functions that act on them very close to each other in a very tightly coupled way, while keeping other functions more loosely coupled.

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.


Languages with traits may not allow them to be changed at runtime, which may be a useful behavior to have in a game.


Composition may be done at runtime, but indeed changing the behavior of an existing object at runtime is not something trait systems provide. Cloning into a new object with different traits is perhaps an option.


Good advice. Inheritance looks like simple code reuse, but it's extremely inflexible. Every CS 101 class goes through that Mammal.breathe(), Mammal::Animal.walk(), exercise, and every time, someone asks, "Well, what do I do with a dolphin?" Do you borrow swim() from Fish? Or breathe() from Mammal? Or reimplement them? Or what?

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.


I was going to try to write a short description but I may as well just link to the wiki page: http://en.wikipedia.org/wiki/Strategy_pattern

This design pattern gives you the freedom to select algorithms for different behaviours without being tied to the strictness of the inheritance hierarchy.


This speaks more about the benefits for mixins, be they formal or informal. Or composable functions for that matter. C++-style "object oriented programming" is not the only game in town.

What was it Alan Kay said? It's not about the objects, it's about the messages. (That is, messages and redirecting messages).


Great advice. This touches on one of the limitations of OO design. Here is an interesting writeup on some other arguments against Object Oriented Programming: http://c2.com/cgi/wiki?ArgumentsAgainstOop


I don't think it's a limitation of OO per se, it's just the way you use it. The "use composition over inheritance" advice comes to mind: http://en.wikipedia.org/wiki/Composition_over_inheritance


This is more of another way of achieving polymorphism. Just because many college level OO classes go coo-coo for co co puffs over inheritance, it doesn't mean saying "inherit less" is a refutation of the whole idea.

Hell, "inherit less" is what you have to say to junior engineers CONSTANTLY upon coming out of school.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: