Jump to content
  • Advertisement
Sign in to follow this  
AndreTheGiant

Having trouble harnessing the power of Inheritance. (best practices advice requested)

This topic is 1222 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

It seems every time I try to use inheritance to save time, it ends up being way more trouble than its worth, and I end up ripping out the inheritance code and doing it without. 

 

Heres a great example. This is C# in case that matters.

 

I'm making a game which has a lot of different types of bad guys. So naturally I thought inheritance. I created a Baddie class which had some very basic stuff, like X,Y position, hitpoints and movement speed. Then I began creating all sorts of specific bad guy classes that inherit from Baddie. I've got Slimer, Knight, Wizard, and Snake bad guys for example, and a lot more, that all extend Baddie.

 

While they all share the basic position, hitpoints and speed, the big problem is they are quite different other than that. The Wizard casts spells, the Snake just moves around randomly, and the Knight charges up his swing and then chops.  I'm finding that I have to have specific code depending on the bad guy type, which if I'm not mistaken defeats the point of using inheritance in the first place!

 

So for example I have instances of all these different bad guy types in a single List<Baddie> list. I'm looping through the list and I want to update each baddie, regardless of the type. However I'm having to use all kinds of type-specific code like this:

foreach (Baddie theBaddie in baddieList) {

if (theBaddie is Knight) {...}

if (theBaddie is Wizard) {...}

}

 

Its becoming quite a mess and I'm wondering why I don't just scrap the whole Inheritance idea, and simply have a separate list for each baddie type:

 

List<Knight>

List<Slimer>

List<Wizard>

...etc

 

And separate update loops for each list.

 

Am I using inheritance wrong? Is this not an appropriate place to use inheritance? I'm starting to think that inheritance is pretty useless unless all of your classes are unbelievably similar. As soon as a class is more than just trivially different, inheritance is not going to help.

 

 

Share this post


Link to post
Share on other sites
Advertisement

Hi.

 

maybe all you need to have is some virtual methods.

 

here or here second post explains it well.

Edited by ankhd

Share this post


Link to post
Share on other sites

Having trouble harnessing the power of Inheritance. (best practices advice requested)


Step 1: Don't.

Am I using inheritance wrong? Is this not an appropriate place to use inheritance? I'm starting to think that inheritance is pretty useless unless all of your classes are unbelievably similar. As soon as a class is more than just trivially different, inheritance is not going to help.


Yup, you got the right of it. Look up the words "aggregation" and "composition." There are plenty of good times to use inheritance, but this isn't one of them.

Its becoming quite a mess and I'm wondering why I don't just scrap the whole Inheritance idea, and simply have a separate list for each baddie type:
<snip>
And separate update loops for each list.


This isn't too far off from how many AAA games work. Many older games did exactly what you outlined while newer ones often use a component-based approach rather than distinct monolithic objects (if for no other reason than because they don't wnat to hardcode their list of enemies in the game and force designers to wait for engineer support to make any simple change).

Share this post


Link to post
Share on other sites

List<Knight>
List<Slimer>
List<Wizard>
...etc
 
And separate update loops for each list.
 
Am I using inheritance wrong? Is this not an appropriate place to use inheritance? I'm starting to think that inheritance is pretty useless unless all of your classes are unbelievably similar. As soon as a class is more than just trivially different, inheritance is not going to help.

The difficulty is that it doesn't extend.

 

Let's say you have a bunch of these monster classes.  Gnome, Soldier, Centaur, Dragon, Angel, Demon, Monkey, Ape...

 

And then you look over it. What kind of difference is there between monkey and ape? So you make a base class and inherit there, too.

 

Then you decide you want some of those to be archers and some to be melee, so you get GnomeArcher, SoldierArcher, ApeArcher, GnomeSwordsman, SoldierSwordsman, DemonSwordsman. Then you decide to make something else, and very soon you're got a combinatorial explosion with thousands of classes.

 

If you use composition you avoid that. You have a character class. It is composed of a creature type (gnome, ape, dragon, demon, whatever), and a set of components. It may have a locomotion component with parameters describing how it moves. It may have an Archery component allowing it to shoot some type of items, perhaps arrows, perhaps lighting bolts, perhaps fireballs. It may have a Melee component allowing it to use an attached weapon.

 

With that system you can build everything from a dragon (flying locomotion, archery with fireball), wizards (character locomotion, archery with low power lighting bolts), soldiers (character locomotion, melee attack with sword) a trebuchet (slow moving locomotion, archery with big stick), archer towers (near-instant rotation only locomotion, fires arrows), cannon towers (slow moving rotation only locomotion, fires cannonballs).  

 

 

 

As for when to use inheritance, its when all the main things work the same and a small number of internals are changing.

 

You normally want an inheritance tree to be shallow and wide.  That is, you start with one base class and have a large number of derived classes, maybe twenty or fifty or even more, depending on your situation.  Usually the classes should only modify internal behavior.

 

Over the years, I've also found the best inheritance trees have no virtual public members. The member functions can call protected and private member functions, but virtual functions shouldn't be called by others. In practice over time a public virtual method will take on additional meaning and violate the IS-A rule. Instead every instance should do the same basic thing and only look up protected member functions to do whatever slightly modified functionality is necessary.

 

There are many common examples of it. This one stolen from Wikipedia's Factory Method example, then modified slightly to C++.

class MazeGame {
    public:

    /* NOT virtual */
    void BuildMaze() {
        Room* room1 = makeRoom();
        Room* room2 = makeRoom();
        room1->connect(room2);
        this->addRoom(room1);
        this->addRoom(room2);
    }
 
    protected:
     virtual Room* makeRoom() {
        return new OrdinaryRoom();
    }
}

class MagicMazeGame : public MazeGame {

    protected:
    virtual Room* makeRoom() {
        return new MagicRoom();
    }
}

If we allowed BuildMaze to be virtual then each derived class could change it to be whatever details they wanted. In practice if BuildMaze were virtual it would start out okay, but over time new features would be added, and MiniGolfMazeGame::BuildMaze() might change some game states, DragonWarfareMazeGame::BuildMaze() will modify some UI elements, and soon you'll find changes to the base game also mean rewriting most of the derived classes as well.

 

-----

 

Note that ECS-based games (Entity Component Systems) tend to use both composition and inheritance.

 

The individual components tend to be derived from a base class, building up whatever behaviors you need.  There may be a large number in a shallow but deep inheritance tree. Perhaps hundreds, perhaps thousands, of component building pieces.  There will be a variety of different locomotion components, respawn components, animation components, event trigger components, and on and on and on.

 

These components are composed together inside a game object. The game object has a collection of components or behaviors, rather than inheriting from them with multiple inheritance or whatever you would have done.

 

-----

 

This doesn't mean that inheritance trees and object interfaces doesn't work as a strategy. It can work, and it has worked for many major games.  However, it tends to be fragile and difficult to extend, it also tends to require more careful and thoughtful design to work successfully. It is easier and less error prone to build through composition.

Share this post


Link to post
Share on other sites

 

Am I using inheritance wrong? Is this not an appropriate place to use inheritance? I'm starting to think that inheritance is pretty useless unless all of your classes are unbelievably similar. As soon as a class is more than just trivially different, inheritance is not going to help.


Yup, you got the right of it. Look up the words "aggregation" and "composition." There are plenty of good times to use inheritance, but this isn't one of them.
 

 

I don't quite understand why you say this. I'm familiar with inheritance vs composition, and IS-A vs HAS-A. It seems to me that this is clearly an IS-A situation. 
Wizard IS-A Baddie

Knight IS-A Baddie

Slimer IS-A Baddie

etc.

 

This is exactly what I was thinking when I decided to use inheritance for this problem. Yet this is apparently not a good time to use inheritance. Can you explain why everyone always says to do the IS-A vs HAS-A test, when it fails in this extremely simple case? I did search the forums before posting and I'd say the number one piece of advice given is to think about IS-A vs HAS-A to determine whether inheritance or composition is more appropriate. Where is my thinking going wrong here? How is my Baddie heirarchy a HAS-A relationship instead of an IS-A ?

 

 

Anyway, everyeone else has posted some great information too. I'll digest it all and post back soon. Thanks!

Share this post


Link to post
Share on other sites

The way I think of it is this: it has the properties of a baddie so therefore it's implied that it is a baddie and can be treated as one.

 

It's basically a form of duck typing, which is an alternate to using interfaces.  Both are forms of polymorphism, but since we want our objects to be easily changed (essentially making them dynamically typed) we use duck typing instead of interfaces and inheritance.  This of course goes along with avoiding problems related to the deadly diamond and virtual function calls.

Edited by Boreal Games

Share this post


Link to post
Share on other sites

I don't quite understand why you say this. I'm familiar with inheritance vs composition, and IS-A vs HAS-A. It seems to me that this is clearly an IS-A situation. 
Wizard IS-A Baddie
Knight IS-A Baddie
Slimer IS-A Baddie

 
The problem is that you use IS-A in the way we normally use the term in natural, spoken language. However in OOP the term has very specific meaning. I personally blame bad teachers for this misunderstanding, as inheritance is often taught with contrived real world examples that never actually occur in real software projects. (Plant-Flower-Tree-Bush for example)
 
Make yourself familiar with the Liskov substitution principle. This should be the benchmark to decide wether you should use inheritance or composition. As a general rule of thumb: When in doubt, use composition.

Share this post


Link to post
Share on other sites

I don't quite understand why you say this. I'm familiar with inheritance vs composition, and IS-A vs HAS-A. It seems to me that this is clearly an IS-A situation. 
Wizard IS-A Baddie
Knight IS-A Baddie
Slimer IS-A Baddie
etc.


You're thinking like a person, not a computer. Your real-world taxonomies don't mean a thing to a mathemetical computation engine crunching through logical structure of algorithms. Programming languages are made to execute code, not to model dictionary definitions of real-world objects.

Why would your program care in any way, shape, or form that some object is a Baddie? What does that possibly mean to your code or your computer? Your program cares about what data that object has and which operations are legal to perform on that data. e.g., is it something that can perform an attack against another game object? Because there's certainly not something that only a Baddie can do. Is it something that can be drawn? That's certainly not specific to a Baddie. Does it have AI? So would a bot controlling a friendly NPC, so it's certainly not limited to a Baddie. And so on; you are very likely to find that almost nothing in your code is in any way specific to a Baddie; on the other hand, you're likely to find a lot of functionality that makes sense on a diverse cross-section of different types of game objects, which means you either need to bloat up all game objects to put that functionality into a single univeral base class (so everything IS-A GameObject) or using composition to give that functionality only to the objects that need it (so a Wizard HAS-A RenderableComponent... but an AmbientSoundSource or ScriptTriggerZone or so on wouldn't).

Trying to express things like "an orange is a fruit" is the first sin of bad object-oriented programming (and it's so depressingly prevalent because every terrible Java/C# school and book starts off with almost that exact bad example...) and has directly led to the poor reputation OOP has unfairly earned. "Wizard is a Baddie" is a squishy-human-brain form of categorization, not something that's relevant to what program actually needs to do.

Use IS-A and inheritance to model functional interfaces and abstractions that you program against, e.g. FmodAudioService IS-A(n) AudioService, and not to model your real-world taxonomies.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!