A child class override its base variable with a variable more derived

I’ve got a class called WorldObject which many of my other classes inherit from.
I’m trying to move some of the variables from WorldObject to a scriptableObject simply called WorldObjectData and give WorldObject a reference to it as a variable called “data”.

My problem is that I want to do the same with some of the classes that inherit from WorldObject and create a scriptableObject for that class as well and make its scriptableObject also inherit from WorldObjectData

A simple visualization would be like this:

WorldObjectWorldObjectData
CharacterCharacterData
PlayerPlayerData

(player inherit character which inherits worldObject)
(same with playerData inherit characterData inherit worldObjectData)

So what I want is that WorldObject’s “data” variable refers to WorldObjectData but Character’s “data” variable to be overridden to point to CharacterData instead, which still is a WorldObjectData just more derived.

How is this done?

You could accomplish this using generics with type-constraints:

public class WorldObjectData : ScriptableObject {}
public class CharacterData : WorldObjectData {}
public class PlayerData : CharacterData {}
public abstract class WorldObject<T> where T : WorldObjectData {
   T Data { get; set; }
   //etc...
}

public abstract class Character<T> : WorldObject<T> where T : CharacterData {
  //etc...
}

public class Player : Character<PlayerData> {
   void SomeMethod() {
      //"Data" is "PlayerData"
      this.Data.whatever...
   }
}

However, if you want to write a custom inspector for WorldObject or Character, that’s pretty much not doable.
Unity will only draw the custom inspector for the exact generic type put into the class, even if you were to use an abstract type:

//Custom inspector will only be drawn on a WorldObject type that has a WorldObjectData generic type exactly.
//Derived types of WorldObjectData will not be drawn.
[CustomEditor(typeof(WorldObject<WorldObjectData>))]
public class WorldObjectEditor : Editor { }

My advice though, I wouldn’t do this. I ran into a similar problem in my game, where I had a structure like this:

  • Weapon (inherits MonoBehaviour) → WeaponData (inherits ScriptableObject)
  • RangedWeapon (inherits Weapon) → RangedWeaponData (inherits WeaponData)
  • ProjectileWeapon (inherits RangedWeapon) → ProjectileWeaponData (inherits RangedWeaponData)

And I tried fighting against Unity for days to get a nice generic structure like the above example, but I’d always run into issues with inspector serialization one way or another.
Eventually, I just settled on composition for the WeaponData types, but kept inheritance for the Weapon types:

  • Weapon (inherits MonoBehaviour) → WeaponData (inherits nothing)
  • RangedWeapon (inherits Weapon) → RangedWeaponData (inherits nothing)
  • ProjectileWeapon (inherits RangedWeapon) → ProjectileWeaponData (inherits nothing)
public abstract class Weapon : MonoBehaviour {
   [SerializeField] private WeaponData weaponData;

   public WeaponData WeaponData => weaponData;
}

public abstract class RangedWeapon : Weapon {
   [SerializeField] private RangedWeaponData rangedData;

   public RangedWeaponData RangedData => rangedData;
}

public class ProjectileWeapon : RangedWeapon {
   [SerializeField] private ProjectileWeaponData projectileData;

   void SomeMethod() {
      this.WeaponData.SomeUniversalWeaponProperty...
      this.RangedData.SomeRangedWeaponProperty...
      this.ProjectileData.SomeProjectileSpecificProperty...
   }

   public ProjectileWeaponData ProjectileData => projectileData;
}

It’s a little more tedious to have to go and populate the inspector with multiple different ScriptableObject references this way, but I’ve not run into inspector serialization issues since.

1 Like

You can’t do quite what you described.

It’s a general principle of polymorphism that you can pass a subclass instance to anyone expecting a base class instance and all expected functionality should work (maybe with some hidden extras). That means subclasses need to be at least as permissive with their inputs and at least as strict with their outputs. If you can hand a WorldObjectData to the base class and have it work, then the subclass also needs to be able to handle a WorldObjectData.

And at an implementation level, the subclass usually is an instance of the base class with some extra stuff attached. So you can’t remove or replace any base class variables because you have an actual, unmodified base class instance sitting in memory as the core of your subclass.

But here’s a couple of things you could do:

Instead of making CharacterData be a subclass of WorldObjectData, you could make it be a separate class that contains only the extra variables that a Character needs, and then the Character class can contain both a WorldObjectData and a CharacterData.

or

If CharacterData is a subclass of WorldObjectData, then you’re allowed to store a CharacterData object inside of a WorldObjectData variable. Your Character could continue using the same variable as the base class, but have a convention that the only things you actually store there will always be CharacterData instances. This will involve doing a lot of casting of the variable so that you can access the subclass members, although you could write a property to wrap that cast so it’s not usually visible.

BUT this also means that if there’s any function that allows an outside object to set your WorldObjectData variable, the interface for that function will still accept any WorldObjectData, not only CharacterData. You’d basically have to write a check in that function that throws a runtime exception if the argument is not a CharacterData, which is messy.

I’d like to point out that under this method, a Character is not a subtype of WorldObject, and cannot be passed to a function/stored in a variable that is expecting a WorldObject.

3 Likes