Jump to content
  • Advertisement
Sign in to follow this  
zuhane

The big face-off between inheritance/interfacing

This topic is 796 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

Hey there. Thanks for taking the time to read my post!

 

I've got a problem in regards to inheritance in that I'm creating a pile of variables that don't get used by a multitude of child classes, and I'd like to go about it in the best fashion possible and reduce the amount of data stored in memory. This system also uses an awful lot of inheritance. I'll explain what I have so far:

 

weptepmlate_zps7goyatnn.png

 

So here we have a very simplistic take on how the inheritance works for my game. Each child class has its own unique behaviour, such as input for the player, AI for enemies, hashing for projectiles, wiring for props, etc. The base entity class contains a load of behaviour which will be executed the same in every child class regardless. An example of this is containing a graphic/animation, being allocated a team, having a bounding box, position, velocity, etc! 

 

I like to keep this hierarchy because there are future plans which could be compromised by breaking this structure. For example, enemies and players can apply forces to each other and be destroyed, but certain projectiles may also be able to be deflected or destroyed. Creatures also have passive abilities and auras that affect the stats of other surrounding entities, such as changing the speed of something in a close vicinity, for example. This is all present in the base.

 

However, on the converse, there will be simple projectiles created which still share lots of this behaviour (velocity, position, graphics, team, etc), but then may not contain things such as abilities. However, in the base entity class, these variables may never be used by a child class, but they still exist in the first place.

 

I've looked into inheritance and was wondering whether to implement a bunch of interfaces instead, such as IPhysics, IWeapons, ICircuitryInput, etc. This would give me more control, as the amount of behaviour entities will have in common differs greatly. Some entities will share lots of behaviour while others only have a slight amount in common.

 

I know that I could fix this problem by adding many more layers of inheritance, but is that a bad idea? I understand that by upping inheritance, I essentially cause a bigger disturbance to the code when changes have to be made, or I have to fix a problem I didn't foresee.

 

I like inheritance, as it allows me to essentially create a contract that enforces certain methods to be invoked, but at the same time, the base behaviour of these invoked methods might actually be the same, so manually writing these methods out for every class would be really counter-productive!

 

Is there an industry standard way of going about this in the most methodical way, or is it more a matter of just gauging the situation and using educated guesses? Help would be muchly appreciated! :D

 

 

Share this post


Link to post
Share on other sites
Advertisement
Favor composition instead.

 

QFFE.

 

The method you're describing, zuhane, is basically the canonical example of how games would (ab)use inheritance in the past. If any "industry standard" exists today, it is to move away from that kind of inheritance-based approach as much as possible and use aggregation or composition based methods (as ApochPiQ noted).

 

That's not to say you can't or shouldn't use inheritance at all, mind, just that you should be wary of a design philosophy that involved deepening the tree like yours every time something new comes along.

 

It seems like all of the functionality you've described in your initial post can be fairly easily transformed into a composition-based approach, each feature or package of functionality (such as, for example, the ability to apply buffs or debuffs to something in a surrounding region) can be isolated and turned into a thing you can attach to an entity instead of something you need to subclass from.

Edited by Josh Petrie

Share this post


Link to post
Share on other sites

Yeah, I'd agree with ApochPiQ . You should go with an entity component model. You could also mix the two and make some concrete classes, somewhat like what UE4 does. They have a base class called "AActor" , this will pretty much be your entity class. They then have a derived class called "APawn". This is a class that is used for actors that represent a movable entity , like a player character. So this class has a UMovementComponent. Using this same component/inheritance model. You may make a class called "CMonster" , this class could have a component called "CMonsterBehaviorComponent" . The component could have derived classes such as CSeaMonsterBehaviorComponent and etc.. So you keep the same monster class, and can just switch out the actual behavior of the monster by using different components.

Share this post


Link to post
Share on other sites

Thanks for the swift response, guys. I've done some research and it actually seems like this approach would make much more sense than my current one. They really didn't touch up this at all when I did my degree, so it's refreshing to learn about. I do have to ask, though, what are the negatives? This approach seems better in almost every aspect? Is it more a case of inheritance still having its uses in niche situations?

Share this post


Link to post
Share on other sites

In my personal experience, the only negative that i've seen with composition model is that there are many ways to implement it. There are dozens of different ways to design a "has-a" relationship in code, while "is-a" models such as inheritance are usually straight forward. Some believe that while an entity "has-a" component such as a movement component, the entity should not have any control over the component. In this model the CMovementComponent is just a container of data, i.e like Velocity .. etc. and you will have a CMovementSystem that updates the component. In another model, the entity both has a component, and is responsible for updating the component too, like in Killzone Shadowfall and UE4(to an extent, technically a task graph updates it.. but the thought process is the same). In a third model, and entity is just an id, and you find out which components an entity has by querying each system using the entity id, this was done in the original thief engine. The permutations can go on and on.. A benefit of inheritance is that it can be good for quick prototyping, but you will eventually hit the "inheritance triangle of depth" and the many other issues inherent in the model.

Share this post


Link to post
Share on other sites

They really didn't touch up this at all when I did my degree, so it's refreshing to learn about.

 

A lot of courses appear to focus too tightly on the inheritance tree when it come to OO and it leads many to believe that deep trees are what OO is actually about when it isn't. It is only when you start to do something in the real world you realise it causes you to box yourself into a corner and end up with convoluted virtual routing or duplicated functionality or you need to carry crap in the base just because it is used further down the tree. :)

 

Sounds like you have the right attitude though the "oh, this is cool and solve my problem so I will refactor my code"

With all code it is selecting the right tool in the toolbox for the job :)

Share this post


Link to post
Share on other sites

Inheretence generally means it is exactly interchangable.

 

 

As an example, if I ask for a D3D11 device I do not care what the underlying type is.  I should never need to know if underlying device is a GeForce GTX 750, or a Radeon R7 360, or an Intel HD 4600. All of them are D3D11 devices and all of them operate interchangeably.  Every action I tell it to do should work the same no matter what action I use.

 

As another example, if I am writing code that operates on a GameObject class, I do not care if that is a player, a bazooka, a rock, or a shoe. If it inherits from GameObject I should never care what the concrete type is, I should never do anything different based on what the leaf type happens to be.  All GameObject items should be completely interchangeable. If I can do something with one of them I should expect the action to work properly on all of them.

 

In most languages base classes should generally be kept abstract. You should not be able to create them directly, you should only be able to create child objects, but you should use the base class everywhere as the type of object.  Much like with graphics you might create a D3D11Device, you don't create an NVidiaGtx780Device. 

In your hierarchy, every operation that works on a BaseEntity should work exactly the same on every object.  It doesn't matter if the concrete type happens to be a player, enemy, projectile, or prop, all the operations should work exactly the same.

Similarly, it should not matter what concrete type of creature you've got, if it inherits from Creature then it is completely interchangeable with any other thing that is a creature and you never do anything different based on what kind of creature it is.  All creatures are interchangeable and their concrete types make absolutely no difference.

 

If you haven't already, read up on SOLID development patterns. 

 

Most Entity/Component System designs generally follow SOLID principles, components all implement interchangeable abstract interfaces, any component is interchangeable with other components, you can mix and match and assign them, you work on the abstract types rather than concrete types, they all have a single responsibility of whatever the component happens to be, and so on.  Then you use composition to build up all the behaviors your game object needs by adding component after component until it does everything you need.

Share this post


Link to post
Share on other sites

A lot of courses appear to focus too tightly on the inheritance tree when it come to OO and it leads many to believe that deep trees are what OO is actually about when it isn't.

 

Honestly, it's easy to lose site of this perspective being a relatively "modern" approach to OO design. And when I say modern, I'm talking post-2003 - after the "first five principles" were published, which then acquired the SOLID acronym. For me, this was knowledge I picked up on my own accord after college. Anyone like me who first learned about OO programming in the 90's, or earlier, was very likely to have been actively taught that OO kind of IS about deep hierarchies, finding nouns from the real world and modeling them as classes, using "is-a" and "has-a" as the litmus test, etc. Basically, a lot of the bad/lazy practices that we now take for granted as "not real OO" in hindsight, was just not pervasive wisdom at that time. I'm sure if you were in the right place or had the right teacher, some of those ideas that later coalesced into SOLID were already being written about and discussed. But for 99.9% of programmers, your curriculum was based on the common wisdom (or lack thereof) of the day and your teacher just followed the established lesson plan. It's almost a generational thing. Programmers taught a certain way go to industry, discover new and better ways of doing things, become the older and wiser folks with some pull, and then bring that wisdom back to academia to teach the next crop. It can be a painfully slow process to adapt teaching methods. That's why even today, you may never hear a mention of SOLID principles in lower level courses that teach basic OO using Java.

Share this post


Link to post
Share on other sites

This is certainly very interesting! I essentially get the impression that I'm trying to remove as many dependencies as possible. I've read from quite a few sources that "good code" is basically code that, when changed, has the smallest possible impact on the rest of the code. I did wonder whether every programmer had to deal with this level of mental strain, and it's really reassuring to know that this is a common problem.

 

As I've developed my programming skills, I've found that the actual logic and syntax side of coding has become almost second nature, and small "hacky/hard-coded" projects are simple to develop because there is a simple and achievable end-goal. It seems to be large applications with these huge dependencies that become complicated, as they have to be designed to be "future-proofed". I also feel like once you reach a certain point with inheritance, it almost becomes impossible for the human mind to grasp, not due to lack of understanding, but simply because you have to store all of these variables in your head as you code (almost as if your brain's RAM is bottlenecking your brain's processor if that makes sense)!

 

I'm currently researching both component/entity systems and also the SOLID design principles and I can see that they both provide immense compartmentalisation and allow you to focus in on a single problem at hand without thinking about all the dependencies elsewhere and whether what you're writing will cause problems further down the line.

 

So from what I've grasped so far, a component/entity system deals with entities based on what they contain rather than what they are. Am I correct in assuming this? I'd like to apply an example if that's okay, just to make sure I'm understanding this correctly!

 

 

 

So, for example, say I have a base enemy class:

 

This base class will handle damage input (rectangle, points of damage, source of damage, etc), animation, position, and all shared behaviours. So rather than handling this behaviour within the class itself, I would be handling the logic outside of that class? So with programming conventions side, is it about operating on the upper layer rather than inside the class to an extent?

 

So instead of this:

class BaseMonster
{
      protected virtual void ReceiveDamage(byte damage)
      {
            this.HP -= damage;
            //More damage logic
      }
}

class GameSpace
{
       byte incomingDamage = 1;
       foreach (BaseMonster B in monsters)
       {
             B.ReceiveDamage(incomingDamage);
       }
}

Would it be more a case of:

    class BaseMonster
    {
        //No damage handler method
    }


    class GameSpace
    {
        private void Main()
        {
            HandleDamage(monsters);
        }

        private void HandleDamage(List<BaseMonster> monsters)
        {
            int incomingDamage = 1;

            foreach (BaseMonster B in monsters)
            {
                B.CheckForDamage(incomingDamage, B);
            }
        }

        private void CheckForDamage(int damage, BaseMonster target)
        {
            target.HP -= damage;
        }
    }

Essentially making the objects less "concrete" and giving the handling to the area containing those objects?

Edited by zuhane

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!