Search code examples
c#unity-game-enginegame-development

Using abstract class as event Listeners?


I'm making a game and I'm using a lot of events to make one class communicate with others, but its tiresome to handle the subscription and the unsubscription on the OnEnable and OnDisable methods everytime that a new class will listen to a certain event. So I thought about making abstract classes for specific events and then, the class that wants to listen to the event will only have to inherit from the abstract class, to save time from setting up the method, OnEnable and OnDisable again.

The abstract class will be like this:

Lets say we have an event called OnGameStart. I'd make an abstract class just to listen to this event.

public abstact class OnGameStartListener : Monobehaviour
{
    private void OnEnable() => Controller.OnGameStart += OnGameStart;

    private void OnDisable() => Controller.OnGameStart -= OnGameStart;
    
    public abstract void OnGameStart();

}

Then, a class that wants to listen to this event would simply enherit from the abstract class and override the abstract method.

My question is: Is there a better way to do this? Or is this too much expensive? Because if a class Inherits from more than one abstract class to listen to events, we'd have more OnEnable and OnDisable methods being called.

Thanks for the time reading and answering ;)

I havent tried this yet, because Im not sure if its worth it. Not sure how I'd analyze the efficiency too.


Solution

  • As already mentioned by TimChang in the comments: There is no way in c# that you

    Inherit from more than one abstract class

    in the first place.


    If you go for an abstract class you should make OnEnable and OnDisable rather protected virtual void and if you then override it in a child class make sure to also call base.OnEnable(); accordingly. Otherwise there might be unexpected behavior as you don't know which of the OnEnable will be called by the engine.

    Also OnGameStart should then rather be protected abstract void as there is no intend to have it called from the outside.


    But if you say you have different events you should rather go for interfaces.

    Assume something like e.g.

    public interface IGameStartListener
    {
        void OnGameStart();
    }
    
    public interface IGameEndListener
    {
        void OnGameEnd();
    }
    

    And then you can have a class like e.g.

    public class Something : MonoBehaviour, IGameStartListener, IGameEndListener
    {
        void OnEnable()
        {
            Controller.AddListener(this);
        }
    
        void OnDisable()
        {
            Controller.RemoveListener(this);
        }
    
        public void OnGameStart() { }
    
        public void OnGameEnd() { }
    }
    

    where your Controller can simply have multiple events and do something like e.g.

    static event Action OnGameStart;
    static event Action OnGameEnd;
    ...
    
    public static void AddListener(object listener)
    {
        if(listener is IGameStartListener gameStartListener)
        {
            OnGameStart += gameStartListener.OnGameStart;
        }
    
        if(listener is IGameEndListener gameEndListener)
        {
            OnGameEnd += gameStartListener.OnGameEnd;
        }
    
        ...
    }
    
    public static void RemoveListener(object listener)
    {
        if(listener is IGameStartListener gameStartListener)
        {
            OnGameStart -= gameStartListener.OnGameStart;
        }
    
        if(listener is IGameEndListener gameEndListener)
        {
            OnGameEnd -= gameStartListener.OnGameEnd;
        }
    
        ...
    }
    

    For sure as you can see every implementer of the interfaces still has to ensure itself that it is properly registered and unregistered as a listener.

    If you want to avoid this of course you still could simply have a base class that implements them all as virtual which means the behavior can be overwritten by any inheritor.

    public abstract class MonoBehaviourGameEvents : MonoBehaviour, IGameStartListener, IGameEndListener, ...
    {
        virtual void OnEnable()
        {
            Controller.AddListener(this);
        }
    
        virtual void OnDisable()
        {
            Controller.RemoveListener(this);
        }
    
        public virtual void OnGameStart() { }
    
        public virtual void OnGameEnd() { }
    
        ...
    }
    

    This would be more of a convinience implementation if you don't want to handle the registration yourself - but still using interfaces so you can be flexible if you want / need to