Question on class inheritance and scriptableobjects

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

1 Like

That should work by default. Is your code structure something like this?

public class Item : ScriptableObject
{
    public string Name;
    public string Description;
}

public class Weapon : Item
{

}

public class Book : Item
{

}
1 Like

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.

1 Like

That is the general structure of the code, in practice it looks more like this

public class Item : ScriptableObject
{
    public string Name;
    public string Description;
}

public class Weapon : Item
{
public float damage;
public float criticalchance;
}

public class Book : Item
{
public string contents
}

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. :slight_smile:

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.

1 Like

This is strange then, because as @Dextozz mentioned, this should be working.

If, in your Player class, “Current Item” is of type Item

public class Player : MonoBehaviour {
   public Item currentItem;
}

…You should be able to drag any sub-class of Item into the field.

Perhaps Unity will only allow this if Item is abstract?

public abstract class Item : ScriptableObject
{
    public string Name;
    public string Description;
}

It shouldn’t need to be abstract, but it’s worth a shot.

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

public Item currentItem; //Lets say I assigned 'Machete' in the inspector with 5 damage.

void weaponHandler()
{
if (currentItem is Weapon)
{
print(currentItem.name) //this works fine because name is an attribute of Item

print(currentItem.damage) //logically, this doesn't work
}
}

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?

1 Like

When you compare types using the is keyword, you can also define a variable which would cast as the correct type, like so:

if(currentItem is Weapon weapon) {
   Debug.Log(weapon.name);
   Debug.Log(weapon.damage);
}
5 Likes

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

if (currentItem is Weapon))
{
    var weapon = (Weapon)currentItem;
    // do stuff with your weapon
}
else if (currentItem is Book))
{
    var book = (Book)currentItem;
    // do stuff with your book
}

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.

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.

1 Like

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!

2 Likes