I've always wondered to what extent object orientation is used by videogames, especially huge 3D projects.
I reckon it would be cool that both troll and werewolf inherited its attributes from enemy and overwrote some of them. But maybe classes are too heavy to be practical, I don't know...
|
|
|||||||||||
|
|
To come to your example I will explain my thoughts about Entites in videogames. I won´t discuss the general advantage and disadvantage of objects and classes, neither their whole role in Videogames. Entities in small videogames are mostly defined by seperate classes, in big games they are often defined in files. There are two general approaches to follow: Extention:
In your example Component based: The second approach (and my favorite) is one In big video games like World of Warcraft or Starcraft 2 Entities aren´t classes, but sets of data, which specify the whole Entity. Scripting is also important, because you define what the Entity does not in a Class, but in a Script (if it is unique, not things like moving). I am currently programming an RTS and I take the component based approach with Descriptor and Script files for Entites, Skills, Items and everything else dynamic in the game. |
|||||||||||||
|
Unfortunately this approach to polymorphism, popular in the 90s, has shown itself to be a bad idea in practice. Imagine you add 'wolf' to the enemy list - well, it shares some attributes with werewolf, so you'd want to consolidate those into a shared base class, eg. 'WolfLike'. Now you add 'Human' to the enemy list, but werewolves are sometimes humans, so they share attributes too, such as walking on 2 legs, being able to talk, etc. Do you create a 'Humanoid' common base for humans and werewolves, and do you then have to stop werewolves deriving from WolfLike? Or do you multiply inherit from both - in which case, which attribute takes precedence when something in Humanoid clashes with something in WolfLike? The problem is that trying and model the real world as a tree of classes is ineffective. Take any 3 things and you can probably find 4 different ways to factor their behaviour into shared and non-shared. This is why many have turned to a modular or component-based model instead, where an object is comprised of several smaller objects that make up the whole, and the smaller objects can be swapped or change to make up the behaviour you want. Here you might have just 1 Enemy class, and it contains sub-objects or components for how it walks (eg. Bipedal vs. Quadrupedal), its methods of attack (eg. bite, claw, weapon, fist), its skin (green for trolls, furry for werewolves and wolves), etc. This is still completely object-oriented, but the notion of what is a useful object is different from what used to be commonly taught in the text books. Rather than having a large inheritance tree of various abstract classes and several concrete classes at the tips of the tree, you typically have just 1 concrete class representing an abstract concept (eg. 'enemy') but which contain more concrete classes representing more abstract concepts (eg. attacks, skin type). Sometimes these second classes can be best implemented via 1 level of inheritance (eg. a base Attack class and several derived classes for specific attacks) but the deep inheritance tree is gone.
In most modern languages, classes aren't 'heavy'. They're just another way of writing code, and they usually just wrap the procedural approach you would have written anyway, except with easier syntax. But by all means, don't write a class where a function will do. Classes are not intrinsically better, only where they make the code easier to manage or to understand. |
||||
|
|
|
I'm not sure what you mean by "too heavy," but C++/OOP is the lingua franca of game development for the same good reasons that it is used elsewhere. Talk of blowing caches is a design issue, not a language feature. C++ can't be too bad since it's been used in games for the past 15-20 years. Java can't be too bad since it's used in the very tight run time environments of smart phones. Now on to the real question: for many years class hierarchies became deep and started to suffer from problems related to that. In more recent times class hierarchies have flattened and composition and other data-driven designs are being rather than logic-driven inheritance. It's all OOP and still used as the baseline when designing new software, just different focus on what the classes embody. |
|||
|
|
|
Classes do not involve any run-time overhead. Run-time polymorphism is just calling through a function pointer. These overheads are exceedingly minimal compared to other costs like Draw calls, concurrency synchronization, disk I/O, kernel mode switches, poor memory locality, over-use of dynamic memory allocation, poor choice of algorithms, use of scripting languages, and the list goes on. There's a reason that object orientation has essentially supplanted what came before it, and it's because it's much better. |
|||||||
|
|
One thing to be aware of is that the concepts of object-oriented code are often more valuable than their implementation in languages. In particular, having to do 1000s of virtual update calls on entities in a main loop can cause a mess in the cache in C++ on platforms like 360 or PS3 - so the implementation might avoid "virtual" or even "class" keywords for the sake of speed. Often though it will still follow the OO concepts of inheritance and encapsulation - just implementing them differently to how the language does itself (e.g. curiously recursive templates, composition instead of inheritance (the component based method described by Marco)). If the inheritance can always be resolved at compile-time then the performance issues go away, but the code can become a bit hairy with templates, custom RTTI implementations (again the built-in ones are usually impractically slow) or compile-time logic in macros etc. In Objective-C for iOS/Mac there are similar, but worse problems with the messaging scheme (even with the fancy caching stuff)... |
|||
|
|