Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Is It Good Interitance

Discussion in 'Scripting' started by NMJ_GD, Mar 14, 2020.

  1. NMJ_GD

    NMJ_GD

    Joined:
    Dec 28, 2019
    Posts:
    14
    Hello,

    I want to know if this is a good way of inheritance and if there is better way to do it.
    So, I have an

    public abstract class Item : ScriptableObject
    {
    //Some Code
    }

    public abstract class BaseEq : Item
    {
    //Some Code
    }

    public abstract class BaseFigthEq : BaseEq
    {
    public Skill[] skills;//I have other scriptable skills for fast entry
    }

    public abstract class Weapon: BaseFigthEq
    {
    public WeaponEnum enum;
    public void Attack()
    {
    //some code
    }
    }

    public abstract class Armour: BaseFigthEq
    {
    public ArmourEnum enum;
    public void Defence()
    {
    //some code
    }
    }


    Given this way of inheritance should I have so many abstract classes? Or is it good that for performance? Is there better way to optimize it?
     
  2. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,992
    General advice is to first figure out what the real classes will have in common. Then group them with subclasses. Will you have a list or a variable that can ether be armour or a weapon but nothing else? That's what your BaseFightEq allows.

    The classic example is not just making Animal->Mammal->Canine->Dog. Your game probably doesn't care whether two animals are any of those things. Even if it did, an enum is often easier.
     
  3. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,513
    Having a tree of 4 abstract classes is a bit odd. It implies your inheritance tree will branch in all 4 places... will it?

    Clearly it branches at BaseFightEq into Weapon and Armour. But that leaves me wondering... what does BaseEq branch into other than BaseFightEq? I'd think maybe BaseDefenseEq as I can think of only a handful of equipment types... attack, defensive, others?

    But... you have Armour inheriting from BaseFightEq implying it's a "fight equipment". So what other types of equipment are there?

    I could see this complexity of a tree... but it also implies a certain level of complexity to the game that I can't gauge for you since I don't know your game.
    Item - anything that can be stored in inventory

    Consumable : Item - an item which is consumed
    Equipment : Item - an item which can be equipped

    Potion : Consumable - gives you strength
    Food : Consumable - gives you health

    Armour : Equipment - represents a piece of armour
    Weapon : Equipment - represents a weapon

    Those has reduced it down to 3, and has clear distinctions for why there are 3 branches since each branch has multiple types under it.

    Though I'd argue this is still a bit limiting as it creates weird inheritance chains. Furthermore this inheritance chain could be limiting if you wanted more complex items. You effectively had a complicated code structure for a simplistic item hierarchy. (which also implies a hierarchy of items... why do items have hierarchies???)

    ...

    So how about an example of alternatives.

    This isn't to say you should use this example, heck, this example is just a spitball I'm coming up with on the spot. It's more to be an example of how you might think of thing in a different manner.

    Pure Code Interface Example (this uses pure code like your inheritance method, but instead uses interfaces instead of abstract classes to imply behaviour):
    Code (csharp):
    1.  
    2. abstract class Item : ScriptableObject
    3. {
    4.  
    5. }
    6.  
    7. interface IEquippable
    8. {
    9.  
    10.     BodyPartEnum BodyPart { get; } //the body part it attaches to
    11.  
    12. }
    13.  
    14. interface IWeapon
    15. {
    16.    
    17.     void Attack(IEntity user, AttackInfo info);
    18.    
    19. }
    20.  
    21. interface IArmour
    22. {
    23.    
    24.     void Defend(IEntity user, AttackInfo info);
    25.    
    26. }
    27.  
    28. interface IConsummable
    29. {
    30.    
    31.     void Consume(IEntity user);
    32.    
    33. }
    34.  
    35.  
    36. class HealthItem : Item, IConsumeable
    37. {
    38.    
    39.     public float Health;
    40.    
    41.     public void Consume(IEntity user)
    42.     {
    43.         user.Health += this.Health;
    44.     }
    45.    
    46. }
    47.  
    48. class ProtectRing : Item, IEquippable, IArmour
    49. {
    50.    
    51.     public ElementalTypeEnum ProtectAgainst;
    52.    
    53.     public BodyPartEnum BodyPart { get { return BodyPartEnum.HandAux; } } //can be equipped to the hand but not as a held item... like a ring
    54.    
    55.     public void Defend(IEntity user, AttackInfo info)
    56.     {
    57.         if(info.ElementalEffect == this.ProtectAgainst)
    58.         {
    59.             info.Cancel();
    60.         }
    61.     }
    62.    
    63. }
    64.  
    65. class Sword : Item, IEquippable, IWeapon
    66. {
    67.    
    68.     public float Power;
    69.    
    70.     public BodyPartEnum BodyPart { get { return BodyPartEnum.Hand; } }
    71.    
    72.     public void Attack(IEntity user, AttackInfo info)
    73.     {
    74.         info.Power += this.Power;
    75.     }
    76.    
    77. }
    78.  
    79. class Gauntlets : Item, IEquippable, IArmour, IWeapon
    80. {
    81.        
    82.     public float Power;
    83.     public float Defense;
    84.    
    85.     public BodyPartEnum BodyPart { get { return BodyPartEnum.Hand; } }
    86.    
    87.     public void Attack(IEntity user, AttackInfo info)
    88.     {
    89.         info.Power += this.Power;
    90.     }
    91.    
    92.     public void Defend(IEntity user, AttackInfo info)
    93.     {
    94.         info.Power -= this.Defense;
    95.     }
    96.    
    97. }
    98.  
    99. class EdibleUnderpants : Item, IEquippable, IArmour, IConsumeable
    100. {
    101.    
    102.     public BodyPartEnum BodyPart { get { return BodyPartEnum.Crotch; } }
    103.    
    104.     public void Defend(IEntity user, AttackInfo info)
    105.     {
    106.         info.Power -= 1f; //little effect
    107.     }
    108.    
    109.     public void Consume(IEntity user)
    110.     {
    111.         entity.Health += 1f;
    112.         entity.Horniness += 9000f;
    113.     }
    114.    
    115. }
    116.  
    Or what if you started turning this into a compositable solution... lets use MonoBehaviour here to convey the compositing better than ScriptableObject does (SO requires a lot more heavy lifting to do this. Where as prefabs can handle it easily... sort of how we used to do assets before SO was released).

    Code (csharp):
    1.  
    2. interface IItemAttrib
    3. {
    4.    
    5. }
    6.  
    7. interface IEquippableItemAttrib : IItemAttrib
    8. {
    9.  
    10.     BodyPartEnum BodyPart { get; } //the body part it attaches to
    11.  
    12. }
    13.  
    14. interface IWeaponAttrib : IItemAttrib
    15. {
    16.    
    17.     void Attack(IEntity user, AttackInfo info);
    18.    
    19. }
    20.  
    21. interface IArmourAttrib : IItemAttrib
    22. {
    23.    
    24.     void Defend(IEntity user, AttackInfo info);
    25.    
    26. }
    27.  
    28. interface IConsummable
    29. {
    30.    
    31.     void Consume(IEntity user);
    32.    
    33. }
    34.  
    35. class EquippableAttribute : MonoBehaviour, IEquippableItemAttrib
    36. {
    37.    
    38.     private BodyPartEnum _bodyPart;
    39.    
    40.     public BodyPartEnum BodyPart { get { return _bodyPart; } }
    41.    
    42. }
    43.  
    44. class WeaponAttribute : MonoBehaviour, IWeaponAttrib
    45. {
    46.    
    47.     public float Power;
    48.    
    49.     public void Attack(IEntity user, AttackInfo info)
    50.     {
    51.         info.Power += this.Power;
    52.     }
    53.    
    54. }
    55.  
    56. class ArmourAttribute : MonoBehaviour, IArmourAttrib
    57. {
    58.    
    59.     public float Defense;
    60.    
    61.     public void Defend(IEntity user, AttackInfo info)
    62.     {
    63.         info.Power -= this.Defense;
    64.     }
    65.    
    66. }
    67.  
    68. class ElementalArmourAttribute : MonoBehaviour, IArmourAttrib
    69. {
    70.    
    71.     public ElementalTypeEnum ProtectAgainst;
    72.    
    73.     public void Defend(IEntity user, AttackInfo info)
    74.     {
    75.         if(info.ElementalEffect == this.ProtectAgainst)
    76.         {
    77.             info.Cancel();
    78.         }
    79.     }
    80.    
    81. }
    82.  
    83. class HealthConsumeable : MonoBehaviour, IConsummable
    84. {
    85.        
    86.     public float Health;
    87.    
    88.     public void Consume(IEntity user)
    89.     {
    90.         user.Health += this.Health;
    91.     }
    92.    
    93. }
    94.  
    Then to say create a sword... you'd attach EquippableAttribute and give it a "Hand" body part, and you'd attack the WeaponAttribute and assign its power.

    Or if you wanted a ProtectRing you'd do EquippableAttribute and ElementalArmourAttribute.

    Gauntlets would be EquippableAttribute, ArmourAttribute, WeaponAttribute.

    Magic Gauntlets just get ElementalArmourAttribute added to it.

    ...

    And lets say you're weeks into development and you're like "Oh, darn, what if I want some items sellable and others not?" You could add a "SellableAttribute". Or say you wanted a crafting system? You could have a "CraftingAttribute" that breaks that has data for what items are needed to craft it, as well as what items result from destroying it.

    ...

    Again of course... this isn't to say this is how you should do it, nor am I saying it's a good way of doing it. I don't know what you need.

    What I am saying is... why restrict yourself to inheritance? What benefits is your inheritance tree giving you?

    ...

    I mean heck... you could just adhoc your items depending the complexity of them. Just have an "Item" class and every single type of item inherits directly from that.
     
    eisenpony likes this.
  4. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,992
    And your examples all use a C# Interface. That's what you use when you just want to write regular classes and tack-on fake inheritance as needed.
     
  5. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,513
    Well no... my second example uses composition with interfaces as a contract to define how the composition takes place.

    Second off I more meant long inheritance trees like OP has. Which is what I spent most of my first few paragraphs talking about. (I should have included the word "just inheritance" to make that more clear)

    And lastly I was just giving really basic examples of breaking out of long inheritance trees. I started to write a 3rd example that was completely data oriented... but stopped myself for a few reason, primarily my post was getting long and I had somewhere to be. So instead I cut it off at how these aren't necessarily how OP should go and that even an adhoc solution where inheriting directly from Item may be just fine until you know the complexity of the project.
     
    Last edited: Mar 15, 2020
  6. NMJ_GD

    NMJ_GD

    Joined:
    Dec 28, 2019
    Posts:
    14
    Thank you both for answer.
    Now I tried to make the inheritance tree which i think I want to make
    Here pic: opera_XAECb7HjYs.png
    So, my idea is that to make the whole project as designer friendly as possible. Like Everything here would have its own AssetMenu. and even though I know making consumable -> abstract isn't needed because everything under it is only enum, but i think it is good for organization (pls correct if it is not good practice).
    PS. It is for my practice only project, to get familiar with SO and overall with good practice in writing in Unity.
    In Addition to this, I have character class which has similar branching.
     
  7. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,992
    You might look at what Unity does. Lights, Colliders, AudioSource ... inherit from Component. The allows them to be in the Component list, to have variables gameObject and transform, and function GetComponent. That's pretty useful. All types of colliders inherit from Collider. That's also useful since often you don't care what type it is. Maybe you just want to enable and disable it.

    Most things don't inherit that much, other that that. Generally a common abstract base class is for things that would obviously be useful. For example MaskableGraphic is the base class for Image and RawImage. In code we rarely care which one it is.
    MaskableGraphic picture1;
    lets us move it, change size, change color, without caring whether it's raw or not.
     
  8. Stardog

    Stardog

    Joined:
    Jun 28, 2010
    Posts:
    1,910
    It will die when you need a shield (inheriting Armour) that can shield bash or be thrown (Weapon attack function), unless you do it how lordofduct said.
     
    matkoniecz likes this.
  9. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,992
    If feels as if the OP is trying more for traditional OOP than just getting a game working. The traditional OOP approach would be to make a class WeaponArmour.