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. Dismiss Notice

Question Question on class inheritance and scriptableobjects

Discussion in 'Scripting' started by LiteV, Sep 20, 2020.

  1. LiteV

    LiteV

    Joined:
    Dec 21, 2016
    Posts:
    3
    I'm looking into creating an item system, and currently I have created a scriptableobject called Item that has a name and description, and then some children that inherit from item such as Weapon or Book.

    The problem is, when I create a public Item variable in a player script (let's call it Current Item) so I can change the current item from the inspector, I would like to make "Current Item" accept Item and all of it's children (such as Weapon or Book) instead of just Item. Is there a way I could do this?
    I've noticed it is possible to call ScriptableObject for "Current Item" but to my knowledge I cannot access the individual parts of Item or it's children such as it's name or description
     
  2. Dextozz

    Dextozz

    Joined:
    Apr 8, 2018
    Posts:
    488
    That should work by default. Is your code structure something like this?
    Code (CSharp):
    1. public class Item : ScriptableObject
    2. {
    3.     public string Name;
    4.     public string Description;
    5. }
    6.  
    7. public class Weapon : Item
    8. {
    9.  
    10. }
    11.  
    12. public class Book : Item
    13. {
    14.  
    15. }
     
    PraetorBlue likes this.
  3. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,722
    I think OP is wondering how to get a reference of the specific subtype of Item from a script in order to do type-specific things with it. If so, it's a common question and while there are ways to do it, it's a classic rookie mistake. The point of inheritance is to treat all objects of a given type generically. All child-type-specific behavior should live within that child type itself, usually through the use of virtual methods on the parent type with overrides on the child. Otherwise you may as well not use inheritance at all.
     
  4. LiteV

    LiteV

    Joined:
    Dec 21, 2016
    Posts:
    3
    That is the general structure of the code, in practice it looks more like this

    Code (CSharp):
    1. public class Item : ScriptableObject
    2. {
    3.     public string Name;
    4.     public string Description;
    5. }
    6.  
    7. public class Weapon : Item
    8. {
    9. public float damage;
    10. public float criticalchance;
    11. }
    12.  
    13. public class Book : Item
    14. {
    15. public string contents
    16. }
    The hope would be to, in the player script, call a public variable "Current Item" with the class Item that accepts any 'Item' ScriptableObject or any of its children ScriptableObjects when I assign it in the inspector, such as Book or Weapon (at the moment it only accepts objects of the single class (Item Variable would not accept Weapon Variable), so that hopefully I can check the extended type of Item. So if CurrentItem is a Weapon, it can act accordingly.
    Of course, I am still considerably new to Unity and am fully prepared to be told that what I'm doing is incorrect or could be done in a better way I am unaware of. :)

    EDIT: As embarassing as it is, it turns out I just needed to give each class it's own C# script with a matching name.
     
    Last edited: Sep 21, 2020
  5. Vryken

    Vryken

    Joined:
    Jan 23, 2018
    Posts:
    2,106
    This is strange then, because as @Dextozz mentioned, this should be working.

    If, in your Player class, "Current Item" is of type
    Item
    ...
    Code (CSharp):
    1. public class Player : MonoBehaviour {
    2.    public Item currentItem;
    3. }
    ...You should be able to drag any sub-class of
    Item
    into the field.

    Perhaps Unity will only allow this if
    Item
    is abstract?
    Code (CSharp):
    1. public abstract class Item : ScriptableObject
    2. {
    3.     public string Name;
    4.     public string Description;
    5. }
    It shouldn't need to be abstract, but it's worth a shot.
     
  6. LiteV

    LiteV

    Joined:
    Dec 21, 2016
    Posts:
    3
    thanks guys, I've edited my above reply to show I solved one half of the problem just by giving each class it's own individual c# script with appropriate naming. that part was definitely a rookie mistake on my behalf.

    Now im posed with the last half of the problem, how would I access the child class? I can do something like
    Code (CSharp):
    1.  
    2.  
    3. public Item currentItem; //Lets say I assigned 'Machete' in the inspector with 5 damage.
    4.  
    5. void weaponHandler()
    6. {
    7. if (currentItem is Weapon)
    8. {
    9. print(currentItem.name) //this works fine because name is an attribute of Item
    10.  
    11. print(currentItem.damage) //logically, this doesn't work
    12. }
    13. }
    14.  
    It makes complete sense that printing the damage variable wouldn't work because currentItem was originally declared as just an Item, with only a description and name, but is there a way for me to possibly declare some kind of local variable or something to help me access the variables of the Machete, now that the code knows that currentItem (Machete) is of class type Weapon?
     
  7. Vryken

    Vryken

    Joined:
    Jan 23, 2018
    Posts:
    2,106
    When you compare types using the
    is
    keyword, you can also define a variable which would cast as the correct type, like so:
    Code (CSharp):
    1. if(currentItem is Weapon weapon) {
    2.    Debug.Log(weapon.name);
    3.    Debug.Log(weapon.damage);
    4. }
     
    edin97, Dextozz and Bunny83 like this.
  8. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,528
    This is just plain strongly typed OOP. Either your base class has virtual properties or methods which the child can override, or you need to cast the reference to the actual type. A common way is to to

    Code (CSharp):
    1. if (currentItem is Weapon))
    2. {
    3.     var weapon = (Weapon)currentItem;
    4.     // do stuff with your weapon
    5. }
    6. else if (currentItem is Book))
    7. {
    8.     var book = (Book)currentItem;
    9.     // do stuff with your book
    10. }
    Virtual methods or properties usually isn't an option in this case as it would turn the base class into a god object. I actually did this for my JSONNode class in my SimpleJSON framework because there is a relatively small fix feature set that isn't going to change. This allows me to avoid the need for casting. So in this case you didn't have to pay much attention to the actual underlying type as you can access (almost) everything from the common base class interface. Of course trying to get a child object from a string value doesn't make much sense, however the base class provides dummy implementations for all those properties and indexers.

    Though back to your case, another thing you can use is implementing interfaces. This is in general better than long inheritance chains. Normal inheritance but you in a straitjacket. Interfaces have the advantage that a class can implement as many interfaces as you want. So you can define common traits with an interface. So you could define abstract concepts like IUsable, IInventoryItem, IReloadable, IInspectable, ... So a useable object simply gets a "Use()" method while your inventory essentially contains IInventoryItems. You are free to give each concrete class a combination of "traits" and they implement those methods. From the usage perspective you do the same as in my example above. You see if a class implements this interface, cast it to that interface type and you can use it's methods / properties.

    Note that interfaces can also implement other interfaces. So it's possible to define common grouping interfaces that way.
     
  9. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,528
    Yes this is the new C# 7 syntax which is really neat to simplify the code a bit as you can have the variable declaration inside the if statement. Though if you're using an older version of Unity this might not be an option for you. Of course people who read this in the future should of course use this syntax when possible.
     
    Dextozz likes this.
  10. Dextozz

    Dextozz

    Joined:
    Apr 8, 2018
    Posts:
    488
    That has to be the best thing I've read this entire week. Its beauty is unprecedented. I can't wait to use this. Thanks!
     
    Vryken likes this.