Problems while working with polymorphism and inheritance

Basically the problem is that I have a SkillManager Script, which would be activating skills by executing ExecSkills(). I have all sorts of unique script for different skills like Punch.cs, Throw.cs that inherits Skills.cs. Skills defines virtual methods like Perform() and would be overwritten by inherited class.
Something like:

SkillManager : MonoBehaviour{
  var actualMethod;

  void Start(){
     actualMethod = this.gameobject.GetComponent<XXX>();
  }

  void Update(){
    actualMethod.Perform();
    // Can't Do This ↑
}
}

obviously, you can’t force the code editor to assume that actualMethod is 100% a script that inherits Skills.cs and conatins Perform(), and so there would be an error if you try to write actualMethod.Perform();

Is there a possible solution or I should change the whole way of thinking?

There is a way to force a variable to inherit from Skills.
Just use ā€˜Skills _cachedSkill’ and make sure the base class Skills has a Perform() method which every class inheriting has to implement (maybe use abstract)

1 Like

To make it visual:

public abstract class Skills : MonoBehaviour {
  public abstract void Perform ();
}

public class Throw : Skills {
  public override void Perform () {
    // do throw logic
  }
}
2 Likes

Thanks guys! True it’s actually better to use abstract class, but I’m still unable to solve the problem, and I encountered new problems while optimizing.
I changed:

actualMethod = this.gameobject.GetComponent<XXX>();

into:

actualMethod = this.gameobject.AddComponent<Type.GetType(skillClassName)>();

And found that Type.GetType(skillClassName) returns null. (But it functions properly while getting classes that directly derives from MonoBehaviour). I assume that if i’m able to fix this new problem, I can get pass the old problem.

As for forcing a variable to inherit from Skills, I don’t know how to write it correctly.
I tried:

actualMethod = this.gameobject.GetComponent<Throw>();

adding:

Skills actualMethod.Perform()

But editor reported an error.

If a skill is already on the object you can just do GetComponent() and it should work. Not sure about AddComponent. You probably just need to AddComponent() for the throw skill for example

I’m passing information to the manager from scriptable objects at Start(), so i’m using a for loop + addcomponent + (string)classNameToBeAdded. Therefore i’m expecting to use addcomponent so that I won’t have to drag a lot of copies of scripts.

Why use strings? It is easier and less error prone to just use the classes like .AddComponent();

I completely forgot that I can make scripts a variable in Scriptable Objects, my bad :face_with_spiral_eyes:

1 Like

In your first script, can you just change
var actualMethod
to
Skill actualMethod
?
Then actualMehtod.Perform() shouldn’t be a problem for the code editor.

1 Like

And about that use of var, I think where-ever you learned polymorphism or whomever said to use var gave you bad advice, at least for C#. In some languages you get free polymorphism – pass in any variable type you want and if it happens to have x.Perform(), things are cool. That’s basically what var actualMethod; is saying, in a language like that.

But C# is the opposite. Your class needs Perform(), but also has to ā€œregisterā€ it by inheriting from Skill, and has to mark Perform() as virtual so it knows you want polymorphism for it. And then, the whole point of having the base class Skill is so you can use Skill actualType; to say ā€œthis variable can be any type that inherits from Skill, which we know for a fact means it has the correct Perform() function, and it can’t be any other type (not even one with another Perform()).ā€

1 Like

Actually var is illegal in C# field declarations in the first place, so the var example wouldn’t even compile.

1 Like

Thanks everyone, it’s a great help. Following Owen’s reply, I was able to use GetComponent to get my Throw.cs (Which derives from Skills) with:

Skills actualMethod = this.gameobject.GetComponent<Throw>();

But there is still a problem that I’m not able to assign Throw.cs to [SerializeField] Skills actualType; by dragging it in editor.
My current solution is instead of trying to assgin script components by AddComponent<> , I made copies of empty gameobject prefabs. So everytime the game starts, prefabs are Instantiated (Instructed by certain Scriptable Objects) as a child, and I’m able to use gameObject.GetComponentsInChildren and store every acutalMethod classes in an array.
I’m curious if there is a better way.

Do the skills need to inherit from MonoBehavior?
Otherwise you can just make a new Skill() to fill all variables.

Otherwise getcomponent should work yeah. Haven’t tried scriptable objects too much

I think Skills does have to inherit from MonoBehavior because I’m now using the prefab method, I’m not sure if I can make Skills (and derived classes) a component on a gameobject if Skills isn’t deriving from MonoBehavior.
The main reason I’m using Scriptable Object is to store fundamental variables with different numbers for different skills, eg: skillCooldownTime, skillID, skillActivatekey. It’s easier to create and add properities in case if I’m adding more things for Skills.
The whole concept is to have a SkillManager as a factory, SkillManager creates a bunch of acutualSkills depending on a Scriptable Object array (array acts kinda like a TODO list). While in the game, SkillManager determine which skill to use and executes Perform() method in acutualSkills.

What exactly are you dragging? The actual script file? Or a gameobject in the scene that has this component on it? You can only drag instances of that script to a Skill field. So you have to drag a gameobject or the actual component header on a gameobject onto that field. Dragging the script itself does not work.

You could drag on the prefabs if those already got the components needed maybe?

Yep, that pretty much what i’m doing, it’s working properly for now

I have a variable set as:

[SerializeField] Skills actualSkill;

and I was trying to drag a script called Throw.cs (Non abstract class and drives from Skills class) in the inspector. I’m doing this because I wan’t to create a Throw Script Component using AddComponent method while running, But this strategy seems bad so I discarded this method and went using the empty gameobject prefab method.

That’s what I thought. This doesn’t work this way since a script asset is actually a MonoScript instance which is actually a TextAsset. However the MonoScript class is an editor only class as this is how script assets are represented in the editor.

I made a SerializableMonoScript class which you can use to essentially serialize the actual type information inside the inspector by dragging a script asset onto the field. It actually stores the assembly qualified type name of the type that the MonoScript implements. At runtime you can read the ā€œTypeā€ property which gives you the System.Type which you can use in AddComponent, or when you use the generic version, it has a neat helper method ā€œAddToGameObjectā€ which allows to add this type as a component to the given gameobject and return the casted instance.

The generic version also allows you to restrict the types to a certain base class or interface. Note since I use an ordinary ObjectField you can actually drop any MonoScript onto that field, however incompatible types will be rejected with a log message. This is not the best user experience, however this way we get all the ObjectField stuff for free (selecting / pinging the script in the project, object picker).

So with this file in your project you can simply do

using B83;

// [ ... ]

public SerializableMonoScript<Skills> skillType;

and when you want to actually add this skill type as a component to a gameobject, you can simply do

Skills skillInst = skillType.AddToGameObject(gameObject);

or the manual way like this:

Skills skillInst = (Skills)gameObject.AddComponent(skillType.Type)

which does the same thing.

Note that this class also works for ScriptableObjects equally. The generic version also has a dedicated ā€œCreateSOInstance()ā€ method.

Just a short warning: Since a ā€œSerializableMonoScriptā€ actually just stores the assembly qualified type name, ā€œreferencesā€ stored that way would break when you refactor the type name or move it to a different assembly. So keep that in mind.

ps: I also made a SerializableInterface some time ago which allows you to store and filter a UnityEngine.Object instance reference based on an interface or base class type. So this also works for MonoBehaviours and ScriptableObjects. Since it’s generic you also get easy access to the properly casted instance. When you drag a gameobject onto that field and multiple components match the filter, a drop down pops up that lets you select the instance you want.

That directly solves my problem in an elegant way, Unity really should consider making this method built-in.:wink: