Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Simple Inheritance Question

Discussion in 'Scripting' started by KyleStank, May 27, 2015.

  1. KyleStank

    KyleStank

    Joined:
    Feb 9, 2014
    Posts:
    204
    I am making an inventory system, and it is going good. Just, I use one "Item" class for each item in the game. Now, I don't think this is practical because the "Item" could be armor, or a weapon... And armor and weapons have different properties, but right now, I am just leaving blank properties. Here is my Item class:
    Code (csharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. [System.Serializable]
    5. public class Item
    6. {
    7.     //All the properties of every item in the game
    8.     public string name;
    9.     public int id;
    10.     public string desc;
    11.     public Sprite icon;
    12.     public GameObject model;
    13.     public int power;
    14.     public int speed;
    15.     public int value;
    16.     public ItemType type;
    17.  
    18.     public enum ItemType
    19.     {
    20.         Weapon,
    21.         Consumable,
    22.         Quest,
    23.         Head,
    24.         Shoes,
    25.         Chest,
    26.         Trousers,
    27.         Earrings,
    28.         Necklace,
    29.         Rings,
    30.         Hands
    31.     }
    32.  
    33.     public Item(string name, int id, string desc, string iconName, string modelName, int power, int speed, int value, ItemType type)
    34.     {
    35.         //For making a new, defined item
    36.         this.name = name;
    37.         this.id = id;
    38.         this.desc = desc;
    39.         this.icon = Resources.Load<Sprite>(iconName);
    40.         this.model = Resources.Load<GameObject>(modelName);
    41.         this.power = power;
    42.         this.speed = speed;
    43.         this.value = value;
    44.         this.type = type;
    45.     }
    46.    
    47.     public Item()
    48.     {
    49.         //Do nothing, used for making empty Items that we can change later
    50.     }
    51. }
    52.  
    You see how this could cause problems. I also have a "ItemDatabase" class, and here it is:
    Code (csharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4.  
    5. public class ItemDatabase : MonoBehaviour
    6. {
    7.     public List<Item> items = new List<Item>();
    8.  
    9.     void Start()
    10.     {
    11.         /**Armor */
    12.         items.Add(new Item("Silver Armor", 001, "Basic armor made out of silver", "A_Armor04", "", 10, 10, 1, Item.ItemType.Chest));
    13.         items.Add(new Item("Bronze Armor", 002, "Basic armor made out of bronze", "A_Armor05", "", 10, 10, 1, Item.ItemType.Chest));
    14.  
    15.         /**Consumables */
    16.         items.Add(new Item("Antidote", 003, "Healing consumable", "I_Antidote", "", 10, 10, 1, Item.ItemType.Consumable));
    17.         items.Add(new Weapon("Hello, World", 004, "Average ASword", "W_Sword001", "", 10, 10, 5, Item.ItemType.Weapon, 100));
    18.     }
    19. }
    20.  
    You see that in the bolded print, I am adding a different class other than "Item" classed "Weapon." This class inherits from "Item", so it has all of the properties of "Item", and one more. Here is the Weapon class:

    Code (csharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4.  
    5. public class Weapon : Item
    6. {
    7.     public int extraPower = 0;
    8.  
    9.     public Weapon (string name, int id, string desc, string iconName, string modelName, int power, int speed, int value, Item.ItemType type, int extraPower)
    10.         :base (name, id, desc, iconName, modelName, power, speed, value, type)
    11.     {
    12.         this.extraPower = extraPower;
    13.     }
    14. }
    15.  
    So, here is what the problem is. When go to access to "Weapon" in the "items" variable in "ItemDatabase", I go to access the "extraPower" property, and it isn't there. I created a Weapon class in a Item class, and I have all of Item's properties to use, but not Weapon's. Why is this?
     
  2. eisenpony

    eisenpony

    Joined:
    May 8, 2015
    Posts:
    971
    Your list is of "Item"s. The compiler cannot assume that there is anything more specific than an Item in this list.

    Since Weapon inherits from Item, we say that Weapon "is a" Item. Excuse the bad grammar, this is just to emphasize the concept of "is a". This is inheritance in a nutshell; the strongest type of relationship two classes can have: SpecificType "is a" GenericType.

    Sometimes it is convenient to have notation to describe things, and such is the case with inheritance. We say that GenericType < SpecificType, or in your case: Item < Weapon (the < symbol means literally "less than"). Since we don't want compilers to make assumptions, they are limited to select the lowest type in our inheritance chain. Thus, if we are not very specific about what we are asking for, the compiler will choose GenericType, or Item. Obviously Item does not have extraPower.

    Okay, so why are we able to add Weapon to the Item list? Consider that methods also have a type and that a List provides two types of methods - a getter and a setter.

    When you put something into a List, it is called a "set". Set methods are contravariant. This means that they have the opposite direction of inheritance as the regular classes they represent. Thus, when we are talking about setters, we might use the notation: Set(Weapon) < Set(Item). Notice that the Weapon set is "less than" the Item set -- reverse from our regular chain. This makes it clear that the compiler will, in fact, allow you add a Weapon to a List of Items. Likewise, the opposite is not true: the compiler will disallow adding an Item to a List of Weapons.

    When you take something from a List , this is called a "get". Get methods are covariant. This means that they have the same direction of inheritance as regular classes. Thus we might use the notation: Item Get() < Weapon Get(). Now if we have a List of Items, the best the compiler can do is return Item; not anything more specific such as Weapon. On the other hand, if we had a list of Weapons, we could easily get an Item. This is obvious since Weapon "is a" item, so we implicitly have an Item.

    In general, getters or, as some purists might call them, sources are always covariant, while setters or, as the purists say, sinks are always contravariant. This is actually one of the main reasons for the Iterator Pattern (think foreach in C#). It ensures you are not trying to mix (or accidentally mixing) covariant setter actions inside of a covariant getter "for loop".

    Interesting stuff.. but not terribly practical. So what can you do?

    The simplest solution would be to explicitly cast the Item into a Weapon. This can be done like:
    Code (csharp):
    1. var myWeapon = (Weapon) myItemDatabase.items[3];
    However, this isn't very generic code and obviously would fail with a InvalidCastException if the 4th item in the list was not a Weapon.

    So, this is a bit of a design issue. Why does your code outside of the Weapon need to know about "extraPower"? By the sounds of it, maybe it should just improve the base power? If so, you could implement Item's power attribute as a Property:
    Code (csharp):
    1. public int Power { get; set; }{
    Now, you can override the behavior of the power property form Weapon:
    Code (csharp):
    1. override public int Power
    2. {
    3. get { return base.Power + this.extraPower; }
    4. set { this.extraPower = value - base.Power; }
    5. }
    However, this begs the question: why does anything outside of Item need to know the Item's power? Sometimes it is necessary for classes to share information about themselves, but in general we like to hide information inside the class. This called encapsulation and is one of many important metrics used to calculate Software Quality. An alternative might be to program the Item with a method that applies it's internal power to something else. Of course, this increases the number of things Item is dependent on. This kind dependency is called coupling and can itself represent a bad design. This is always the struggle with programming...

    Another option is to remove the inheritance altogether. That is, remove the "is a" relationship between Weapon and Item. Because, really, is a Weapon actually an Item? Inheritance is commonly misused by well intentioned programmers because they think that programming models real life - but it doesn't! This is the idea behind the Liskov Principle, one of the 5 tenants of Clean Code.

    Wow.. I feel like I'm rambling a lot, so I'll wrap it up here. I hope I've made some sense and didn't confuse the problem too much..
     
    Last edited: May 27, 2015
    NomadKing likes this.
  3. Jodon

    Jodon

    Joined:
    Sep 12, 2010
    Posts:
    434
    I didn't bother to read the above post because it's quite long. Basically you're about to run into a bug that Unity only supports inheritance on MonoBehaviour and ScriptableObject. Here is the feature request from 2012.
     
  4. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    You can easily check the runtime type of an object.

    Code (CSharp):
    1. Item randomItem;
    2. if (randomItem.GetType() == typeOf(Weapon)){
    3.     ((Weapon)randomItem).DoWeaponStuff();
    4. }
     
  5. hamsterbytedev

    hamsterbytedev

    Joined:
    Dec 9, 2014
    Posts:
    353
    You beat me to it you sly dog. I was in the midst of typing something akin to that which you described.

    Mormon: 1
    Hamster: 0

    #facepalm #explicittypecasting
     
    KyleStank and Kiwasi like this.
  6. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    Yeah, I've been playing a lot with types and reflections lately. Building a trending tool for kicks and giggles. It uses reflection to recursively step through references to find trendable types. It's been an interesting ride figuring out how to write code without ever knowing a runtime type.
     
    hamsterbytedev likes this.
  7. KyleStank

    KyleStank

    Joined:
    Feb 9, 2014
    Posts:
    204
    Thank you so much! I knew this would be such a simple fix!
     
    Kiwasi likes this.
  8. Fajlworks

    Fajlworks

    Joined:
    Sep 8, 2014
    Posts:
    344
    Irrelevant to the question, but I would personally advise you to question your Item design approach. In my opinion, it would be better long term to use components to describe items rather than creating subclass.

    Instead of creating Weapon : Item, your "Iron Sword" can have:

    Iron Sword:
    ItemBehaviour
    AttackBehaviour
    DurabilityBehaviour

    and your "Heath Potion" could have:
    Health Potion:
    ItemBehaviour
    HealBehaviour
    ConsumableBehaviour

    As you can see, you can now place relevant variables in their respective scripts. Unlike subclassing approach, you no longer have to set power to 0 inside potion, since potion does not have attack behaviour. Also, you can experiment and lets say, remove ConsumableBehaviour from potion to make it usable infinite times. Or remove DurabilityBehaviour to make your Item indestructible.

    Good luck!
     
    KyleStank and eisenpony like this.
  9. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    More succinctly-
    Code (csharp):
    1.  
    2. if (randomItem is Weapon)
    3. {
    4.     ((Weapon)randomItem).DoWeaponStuff();
    5. }
    6.  
    Or
    Code (csharp):
    1.  
    2. Weapon w = randomItem as Weapon;
    3. if (w != null)
    4. {
    5.     w.DoWeaponStuff();
    6. }
    7.  
     
    Ryiah, KyleStank, Kiwasi and 2 others like this.
  10. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    Also - this is false. Unity can't serialize polymorphic data types but you can certainly create them yourself if you're not serializing them.

    That being said - I can see how you could misread the original issue. And it would be handy if this functionality was supported.
     
  11. Jodon

    Jodon

    Joined:
    Sep 12, 2010
    Posts:
    434
    Yea that's what I meant seeing as how he's made it a Serializable class. I thought he may have been talking about the inspector. Regardless it's going to be quite puzzling when he loses the Weapon data after saving.
     
  12. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    Yup - totally agree with you. Just wanted to clarify the language around supporting inheritance as opposed to serializing those inherited instances.
     
  13. bartm4n

    bartm4n

    Joined:
    Jun 20, 2013
    Posts:
    57
    This is basically how I do it, and it works.
     
  14. KyleStank

    KyleStank

    Joined:
    Feb 9, 2014
    Posts:
    204
    Thank you everyone for helping me out!

    I will definitely look this into this when I need it. See, right now, I am not making a game, I was just doing random stuff with code, no game in mind what so ever. If I actually start developing something, that would definitely be the best approach. Thank you!

    I didn't know that the first option was possible. That will help out in many different cases for me, thanks! Now, right now I don't need to check the type because I literally know the type, but I will need that in the future.

    Again, thank you everyone for helping me out so much! I have a better idea of how to continue developing this inventory system.

    EDIT:
    I only used [System.Serializable] so I could see the classes properties in the Inspector when created as a new object.
     
  15. eisenpony

    eisenpony

    Joined:
    May 8, 2015
    Posts:
    971
    So obviously I misinterpreted the OP a bit and I apologize for getting into too many details. However, I'm a little surprised there is so much agreement on using runtime analysis of types. Not only is this going to lead to code that looks like:
    Code (csharp):
    1. if (myItem is Item)
    2. // do item stuff
    3. else if (myItem is Weapon)
    4. // do weapon stuff
    5. else if (myItem is Armor)
    6. // do armor stuff
    7. else if (myItem is Consumable)
    8. // etc etc...
    for every single type... But it also completely defeats the purpose of using generics. At this point, there is no difference between using List<Item> and List<Object>.

    Admittedly, I don't have experience making an item database for a video game, but I really encourage exploring options that do not include inheritance.
    I think Fajlworks has a very interesting concept, so it's good that you are considering that option.
     
    landon912 and Kiwasi like this.
  16. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    Admittedly, yes - Item would be better of as an interface that had a Use() or Consume() method.
     
    Kiwasi likes this.
  17. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    A lot depends on your use case. For calling methods on an item an interface might be better. For seeing if an item can fit in the characters weapon slot you could justify runtime type checking.

    My current project demands a lot of runtime type checking, so that's where my head went.