Serialization issue with Generics in ScriptableObject-based Events

Hello everyone :slight_smile:

I am creating my first game in Unity and I am trying to create an event system that I can rely on, to avoid having direct references between the different parts of my scene.

I use a ScriptableObject approach for my event system, similar to this what was presented at Unite Austin 2017.

However I am having Serialization issues within the Unity Inspector on my EventListeners :confused:

I created these 2 classes:

GameEventListener<T> : MonoBehaviour
using UnityEngine.Events;

public abstract class GameEventListener<T> : GameEventListenerBase where T : struct
{
    public GameEvent<T> gameEvent;
    public UnityEvent<T> unityEvent;

    public void SetGameEvent(GameEvent<T> gameEvent)
    {
        this.gameEvent = gameEvent;
        gameEvent.AddListener(this);
    }

    void OnEnable()
    {
        gameEvent?.AddListener(this);
    }

    void OnDisable()
    {
        gameEvent?.RemoveListener(this);
    }

    public void OnEventTriggered(T data)
    {
        unityEvent.Invoke(data);
    }
}
GameEvent<T> : ScriptableObject
using System.Collections.Generic;
using UnityEngine;

public abstract class GameEvent<T> : ScriptableObject where T : struct
{
    List<GameEventListener<T>> listeners = new List<GameEventListener<T>>();

    virtual public void TriggerEvent(T data)
    {
        for (int i = listeners.Count - 1; i >= 0; i--)
        {
            listeners[i].OnEventTriggered(data);
        }
    }

    public void AddListener(GameEventListener<T> listener)
    {
        listeners.Add(listener);
    }

    public void RemoveListener(GameEventListener<T> listener)
    {
        listeners.Remove(listener);
    }

    void OnDisable()
    {
        listeners.Clear();
    }
}

I then create my custom derived classes for specific events to be able to pass specific data as structs, e.g.:

OnLivesChanged derived classes example
using UnityEngine;

public class OnLivesChangedListener : GameEventListener<OnLivesChangedData>
{
    // This is required; because it is not possible to instantiate a GameEventListener<int> in the inspector (Generics are not supported for ScriptableObjects and MonoBehaviours)
    // Empty
}

public struct OnLivesChangedData
{
    public int currentLives;
    public int maxLives;
}

[CreateAssetMenu(menuName = "ScriptableObjects/Game Events/OnLivesChanged")]
public class OnLivesChangedGameEvent : GameEvent<OnLivesChangedData>
{
    // Empty
}

Then I create my custom OnLivesChanged SO, and add a OnLivesChanged Listener to the GameObject I want:


I then trigger the method from the component I want and it works perfectly fine.

BUT

When I restart Unity, all the references to the GameEvent ScriptableObjects I added to my listeners are gone, see:

When I change something in the scene and check the scene’s file in git, all the ìd references of the gameEvents are not set correctly:

I am pretty sure this has something to do with Unity’s serialization of ScriptableObjects with Generic types, but I am really struggling to find a solution that works when I restart Unity :frowning:
And what annoys me, is that the serialization itself works pretty well at first, until I reopen the project…

I tried to mark the my gameEvent attribute in GameEventListener as “SerializeReference” like this:
[SerializeReference] public GameEvent<T> gameEvent;
But it did not seem to do the trick.

I know that Serialization of Generic fields is a tricky thing in Unity, and I a m a bit overwhelmed with all the infos I find online, so I thought I’d ask myself :slight_smile:

Does someone know why this happens, and how I can fix this issue?
Discarding the changes of the scene in git every time I reopen Unity is not really nice :smiley:

Thanks in advance!

[SerializeReference] wont work for UnityObject derived types.

AFAIK your current setup should be ok, but i see 2 likely errors.

On your GameEvent<T>, there is no [SerializeField] for your listeners List (this doesnt seem to be the problem you are reporting, so maybe is just a typo).

And, on your OnLiveChanged implementation (maybe is just how you showed it) make sure the listener and the event are on 2 distinct script files, if not, it will only appear to work while the editor is running, but when you restart the editor, it loses references, because the OnLiveChangedEvent scriptable should be broken (because they are normally referencing their type by script guid, and if 2 UnityObject’s share script, they break, so if this is the case im guessing your OnLivesChanged scriptable you have “missing script” warning on the inspector)

Thank you so much!
It seems it was indeed the issue that I declared my derived OnLivesChangedGameEvent and OnLivesChangedListener in the same file!

I did not have the “missing script” warning on the inspector, simply:
image

I guess because Unity was still able to reference the base abstract class GameEvent or something

I guess I learned something on how Unity references the files in a scene :slight_smile:

1 Like