Reputation: 36341
I have four classes, Event
and Action
which are both base classes, and then I have two child classes Create : Event
and MoveTo : Action
.
Event
contains a list of Action
instances, and when Trigger()
is called in the child Create
it calls Event.Trigger()
, which loops over the list of actions, and calls Action.Run()
on each action which calls Called()
.
The Issue I am having is the virtual
method is getting called and not the override
method inside of MoveTo
.
[Serializable]
public abstract class Event : MonoBehaviour {
[SerializeField] public List<Action> actions = new List<Action>();
protected void Trigger() {
foreach (Action action in actions) {
action.Run();
}
}
}
Event
public class Create : Event {
void Start() {
Trigger();
}
}
Action
[Serializable]
public class Action {
public virtual void Called() {
Debug.Log("Virtual");
}
public void Run() {
Called();
}
}
MoveTo
public class MoveTo : Action {
public override void Called() {
Debug.Log("Called");
}
}
I am adding the MoveTo
action to the event list from the Unity Editor onto a prefab. I am not sure how unity handles these at runtime, does in initialize them or do I? That I am not sure about. That is what might be causing my issue...
private Event GetCurrentEvent(){}
void AddActionCallback(Type actionType) {
// actionType is MoveTo
var prefab = GetCurrentPrefabItem().Value;
var evt = GetCurrentEvent();
evt.actions.Add((Action)Activator.CreateInstance(actionType));
Undo.RecordObject(prefab.gameObject, "Added actions");
PrefabUtility.RecordPrefabInstancePropertyModifications(prefab.gameObject);
}
Here is what it looks like before I run the game. It shows MoveTo
, the button in the red column shows the action using action.GetType().Name
. This is the name before I run the game:
After I run the game the button now looks like this:
When running:
evt.actions.Add((Action)Activator.CreateInstance(actionType));
The editor displays Type mismatch even when the output of actionType
and Activator.CreateInstance(actionType)
is MoveTo
:
Upvotes: 3
Views: 1346
Reputation: 20259
When you save the prefab, it serializes the List as a list of pure Action
s, and erases any information that only the child class MoveTo
has.
From the Unity docs on serialization:
No support for polymorphism
If you have a
public Animal[] animals
and you put in an instance of aDog
, aCat
and aGiraffe
, after serialization, you have three instances ofAnimal
.One way to deal with this limitation is to realize that it only applies to custom classes, which get serialized inline. References to other
UnityEngine.Objects
get serialized as actual references, and for those, polymorphism does actually work. You would make aScriptableObject
derived class or anotherMonoBehaviour
derived class, and reference that. The downside of this is that you need to store thatMonobehaviour
orscriptable
object somewhere, and that you cannot serialize it inline efficiently.The reason for these limitations is that one of the core foundations of the serialization system is that the layout of the datastream for an object is known ahead of time; it depends on the types of the fields of the class, rather than what happens to be stored inside the fields.
This is why its class shows up as an Action
.
However, it can't serialize as an Action
because:
How to ensure a custom class can be serialized
Ensure it:
Has the Serializable attribute
Is not abstract
Is not static
Is not generic, though it may inherit from a generic class
Action
is an abstract class, so it won't even serialize partially properly. I assume this is the root cause of the Type Mismatch
problem, as Unity is struggling to deserialize something that is unsupported.
In short, if you want to serialize data in MoveTo
, you'll need to have a [SerializeField]
List<MoveTo>
in order to not lose the information, or you can have Action inherit from ScriptableObject
, which brings its own problems.
Upvotes: 1