Get Off My Lawn
Get Off My Lawn

Reputation: 36341

Virtual method getting called instead of the override

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:

before

After I run the game the button now looks like this:

after

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:

type mismatch

Upvotes: 3

Views: 1346

Answers (1)

Ruzihm
Ruzihm

Reputation: 20259

Unity does not support built-in polymorphic serialization.

When you save the prefab, it serializes the List as a list of pure Actions, 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, a Cat and a Giraffe, after serialization, you have three instances of Animal.

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 a ScriptableObject derived class or another MonoBehaviour derived class, and reference that. The downside of this is that you need to store that Monobehaviour or scriptable 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

Related Questions