An EventManager class that can probably be improved

I’ve made an event manager that uses UnityEvent. It works well enough, but I think it could be improved, as functions that invoke or otherwise interact with its stored events need 2 generics even if they only have 1 parameter. e.g.:

EventManager.Global.InvokeEvent<PauseEvent, PauseEventData>(new PauseEventData());

An overview of how the manager & its related classes work:

GameEventData
GameEventData and the classes that inherit from it are serializable collections of relevant data for events.

[Serializable] public class GameEventData
{
    public static uint EventID { get; private set; }
    public TimeSpan EventTime { get; private set; }
    public string EventType { get; private set; }
    public string EventLog { get; }
}

GameEvent
The abstract GameEvent class inherits from UnityEvent and all events stored in the event manager inherit from it.

[Serializable] public abstract class GameEvent<T> : UnityEvent<T> where T : GameEventData { }
/code]


An implementation of some GameEvents and GameEventData classes:

[code=CSharp][Serializable] public sealed class PauseStartEvent : GameEvent<PauseEventData> { } 
[Serializable] public sealed class PauseStopEvent : GameEvent<PauseEventData> { } 
[Serializable] public class PauseEventData : GameEventData { }

EventManager
It stores and manages GameEvents. It’s not a proper singleton because I wanted to allow for local event managers.

public class EventManager
{
    private readonly Dictionary<System.Type, System.Object> events;
    private static EventManager g;
    public static EventManager Global { get; }
    public void AddListener<T0,T1>(UnityAction<T1> listener)
        where T0 : GameEvent<T1>, new()
        where T1 : GameEventData { }
    public void ClearEvents() { }
    public T0 GetEvent<T0, T1>()
        where T0 : GameEvent<T1>, new()
        where T1 : GameEventData { }
    public void InvokeEvent<T0, T1>(T1 data)
        where T0 : GameEvent<T1>, new()
        where T1 : GameEventData { }
    public void RemoveEvent<T0, T1>()
        where T0 : GameEvent<T1>, new()
        where T1 : GameEventData { }
    public void RemoveListener<T0, T1>(UnityAction<T1> listener)
        where T0 : GameEvent<T1>, new()
        where T1 : GameEventData { }
}

The event dictionary in EventManager uses types & objects. Types because I wanted to make sure that a given EventManager only ever stores one of each type of GameEvent. Objects because it’s difficult to make a dictionary that can hold generic objects that inherit from another generic class.
Because the dictionary is so generic, every function that interacts with it needs to specify the type of non-abstract GameEvent that it’s interacting with and the type of GameEventData that’s used by that GameEvent. Each of these functions contains code similar to this:

public void InvokeEvent<T0, T1>(T1 data)
    where T0 : GameEvent<T1>, new()
    where T1 : GameEventData
{
    var eventType = typeof(T0);
    if (events.ContainsKey(eventType))
        (events[eventType] as T0).Invoke(data);
}

So, it works as intended, but are there any thoughts on how this could be improved?

Is there a good reason to use UnityEvent? Is this being exposed in an editor or something? If not, you can probably simplify things but just not using that infrastructure.

For instance, if the event payload was the only variation that mattered you didn’t have a distinction between event and event data. Then you could just use Action as contract for handlers.

That would get rid of the need to have two generic parameters and, if done right, the generic parameters could be inferred by the compiler.