- Home /
Prefab seems to break polymorphism
Instantiation seems to change which method is called (base class or derived class)
I'll admit I'm no expert in C# polymorphism, but can someone tell me what's going on here?
It seems the uninstantiated prefab will only call the base class methods, but the instantiated game object will call the derived class method.
ParentScript.cs
using UnityEngine;
public abstract class ParentScript : MonoBehaviour {
virtual internal string firstName {get; set;} = "Mother";
void Start() {
Debug.Log("Start: " + firstName);
}
}
ChildScript.cs
using UnityEngine;
public class ChildScript : ParentScript {
override internal string firstName {get; set;} = "Daughter";
}
CreatorScript.cs
using UnityEngine;
public class CreatorScript : MonoBehaviour {
// An empty gameobject with only a ChildScript component
public GameObject prefab;
void Start() {
ParentScript parentScript = prefab.GetComponent<ParentScript>();
Debug.Log("Before Instantiation (pulled as parent): " + parentScript.firstName);
ChildScript childScript = prefab.GetComponent<ChildScript>();
Debug.Log("Before Instantiation (pulled as child): " + childScript.firstName);
ChildScript asChildScript = (parentScript as ChildScript);
Debug.Log("Before Instantiation (as child): " + asChildScript.firstName);
ChildScript castChildScript = (ChildScript) parentScript;
Debug.Log("Before Instantiation (cast as child): " + castChildScript.firstName);
GameObject p = Instantiate(prefab, Vector3.zero, Quaternion.identity);
Debug.Log("After Instantiation (prefab): " + parentScript.firstName);
ParentScript instantiatedParentScript = p.GetComponent<ParentScript>();
Debug.Log("After Instantiation (instantiated - pulled as parent): " + instantiatedParentScript.firstName);
ChildScript instantiatedChildScript = p.GetComponent<ChildScript>();
Debug.Log("After Instantiation (instantiated - pulled as child): " + instantiatedChildScript.firstName);
}
}
Console Output
Before Instantiation (pulled as parent): Mother
UnityEngine.Debug:Log (object)
CreatorScript:Start () (at Assets/CreatorScript.cs:9)
Before Instantiation (pulled as child): Mother
UnityEngine.Debug:Log (object)
CreatorScript:Start () (at Assets/CreatorScript.cs:12)
Before Instantiation (as child): Mother
UnityEngine.Debug:Log (object)
CreatorScript:Start () (at Assets/CreatorScript.cs:15)
Before Instantiation (cast as child): Mother
UnityEngine.Debug:Log (object)
CreatorScript:Start () (at Assets/CreatorScript.cs:18)
After Instantiation (prefab): Mother
UnityEngine.Debug:Log (object)
CreatorScript:Start () (at Assets/CreatorScript.cs:21)
After Instantiation (instantiated - pulled as parent): Daughter
UnityEngine.Debug:Log (object)
CreatorScript:Start () (at Assets/CreatorScript.cs:25)
After Instantiation (instantiated - pulled as child): Daughter
UnityEngine.Debug:Log (object)
CreatorScript:Start () (at Assets/CreatorScript.cs:27)
Start: Daughter
UnityEngine.Debug:Log (object)
ParentScript:Start () (at Assets/ParentScript.cs:7)
Note: The scene is just a single empty game object with a CreatorScript
component
Note: GetType()
for any of these will return ChildScript
For what it is worth, this is the simplest example I could create, but in my real project, I have an uninstantiated prefab where I know it's script's base class, but not the derived class. So I can get the script with var script = GetComponent<BaseClass>()
but when I go to call any method, it is useless as it only calls the base class method. Even explicitly casting to the child class doesn't seem to help.
(and c# won't allow the kind of dynamic typing that would normally get one out of this situation)
(If anyone cares, in the real project, the base class has many properties of different types, and each derived class will only override a handful, so dictionaries or abstract methods or other solutions I would normally go with wont help)
Answer by logicandchaos · Jan 11, 2021 at 10:31 PM
That is normal behaviour for C#, if you want to be able to call child methods from a base class reference you need to make a virtual method in the base class and then override it in the child class.
That is what I did, is it not?
The property is virtual in the parent class and overriden by the child class. Furthermore, the logging shows that the prefab is returning the parent method for:
1. prefab.GetComponent<ParentScript>()
2. prefab.GetComponent<ChildScript>()
3. ParentScript used as ChildScript
4. ParentScript explicitly cast as ChildScript
The only time the child method seemed to be called was on the instantiated gameobject the prefab created, but not the prefab itself.
(Note that the prefab has the ChildScript as its only component)