Events or Interfaces Listeners?

If you’re reading my posts and you’re checking my public repositories, you might notice that I’m implementing UI with Events that are passing input to the controllers. This is really convenient as you have a clear separation between logic and display layers.

Background

To recap what this UI implementation looks like, let me first explain where it comes from.

From the beginning, I always tried to make my code modular, but I couldn’t grasp how to do it in a really clean way. It was literally taking me years, multiple projects and few programming languages to get to digging into design patterns finally. With them, I started to understand more of what I was doing and how easy it was to start taking care of your code and separate different layers from each other! 😍

Adding MVC and State Machine on top of that, and we have a clean UI code that is only responsible for displaying data and receiving input from the user.

With that said, received input can be passed onto controllers in many different ways, but here we are going to talk about only two of them.

Event-based UI

The first input handling is based on Events and Actions.
If you don’t know what these are, they are variables in which you can store methods that can be called by calling these variables.

Example

To let you better understand how they are working, I’m going to prepare a little view class that I’m going to attach to the UI.

using UnityEngine;
using UnityEngine.Events;

/// <summary>
/// Example of view based on events/actions.
/// </summary>
public class UIEventView : MonoBehaviour
{
    // Event called when Play Button is clicked.
    public UnityAction OnPlayClicked;

    /// <summary>
    /// Method called by Play Button.
    /// </summary>
    public void PlayClicked()
    {
        OnPlayClicked?.Invoke();
    }

    // Event called when Options Button is clicked.
    public UnityAction OnOptionsClicked;

    /// <summary>
    /// Method called by Options Button.
    /// </summary>
    public void OptionsClicked()
    {
        OnOptionsClicked?.Invoke();
    }

    // Event called when Quit Button is clicked.
    public UnityAction OnQuitClicked;

    /// <summary>
    /// Method called by Quit Button.
    /// </summary>
    public void QuitClicked()
    {
        OnQuitClicked?.Invoke();
    }
}

With that view being done, now we need something that will attach itself to this class.

using UnityEngine;

/// <summary>
/// Example of handling UI with events.
/// </summary>
public class EventHandlingExample : MonoBehaviour
{
    // Reference to the view with events.
    [SerializeField]
    private UIEventView eventView;

    /// <summary>
    /// Unity method called when component is enabled.
    /// Here used to attach events.
    /// </summary>
    private void OnEnable()
    {
        eventView.OnPlayClicked += OnPlayClicked;
        eventView.OnOptionsClicked += OnOptionsClicked;
        eventView.OnQuitClicked += OnQuitClicked;
    }

    /// <summary>
    /// Unity method called when component is disabled.
    /// Here used to detach events.
    /// </summary>
    private void OnDisable()
    {
        eventView.OnPlayClicked -= OnPlayClicked;
        eventView.OnOptionsClicked -= OnOptionsClicked;
        eventView.OnQuitClicked -= OnQuitClicked;
    }

    /// <summary>
    /// Handling UI Play Button Click.
    /// </summary>
    private void OnPlayClicked()
    {
        Debug.Log("[Event] Play clicked");
    }

    /// <summary>
    /// Handling UI Options Button Click.
    /// </summary>
    private void OnOptionsClicked()
    {
        Debug.Log("[Event] Options clicked");
    }

    /// <summary>
    /// Handling UI Quit Button Click.
    /// </summary>
    private void OnQuitClicked()
    {
        Debug.Log("[Event] Quit clicked");
    }
}

Did you notice that in order for this class to handle all events from the UI it needs to be attached to all of them? Of course, this also gives us some room to play as we maybe want only to connect to a few of the events. But in most cases, you will need all of them.

Interface listener-based UI

When we have more events in UI class, it’s starting to get a little annoying to keep track of attaching and detaching our events. So thanks to learning Swift and their approach to callbacks based on protocols I decided to make the same thing with interfaces.

Example

The idea is straightforward. You need to create an interface that will contain all of the events that you want to delegate from UI class.

/// <summary>
/// Interface for UIInterfaceView.
/// </summary>
public interface IInterfaceView
{
    // Play Button clicked.
    void OnPlayClicked();

    // Options Button clicked.
    void OnOptionsClicked();

    // Quit Button clicked.
    void OnQuitClicked();
}

Now you need to replace all of the events with calling this interface.

using UnityEngine;

/// <summary>
/// Example of view based on interface listener.
/// </summary>
public class UIInterfaceView : MonoBehaviour
{
    // Reference to listener.
    public IInterfaceView listener;

    /// <summary>
    /// Method called by Play Button.
    /// </summary>
    public void PlayClicked()
    {
        listener?.OnPlayClicked();
    }

    /// <summary>
    /// Method called by Options Button.
    /// </summary>
    public void OptionsClicked()
    {
        listener?.OnOptionsClicked();
    }

    /// <summary>
    /// Method called by Quit Button.
    /// </summary>
    public void QuitClicked()
    {
        listener?.OnQuitClicked();
    }
}

And now last part which is attaching listener.

using UnityEngine;

/// <summary>
/// Example of handling UI with interface listener.
/// </summary>
public class InterfaceHandlingExample : MonoBehaviour, IInterfaceView
{
    // Reference to the view with interface listener.
    [SerializeField]
    private UIInterfaceView interfaceView;

    /// <summary>
    /// Unity method called when component is enabled.
    /// Here used to assign listener in view.
    /// </summary>
    private void OnEnable()
    {
        interfaceView.listener = this;
    }

    /// <summary>
    /// Unity method called when component is disabled.
    /// Here used to remove reference from view.
    /// </summary>
    private void OnDisable()
    {
        interfaceView.listener = null;
    }

    /// <summary>
    /// Handling UI Play Button Click.
    /// </summary>
    public void OnPlayClicked()
    {
        Debug.Log("[Interface] Play clicked");
    }

    /// <summary>
    /// Handling UI Options Button Click.
    /// </summary>
    public void OnOptionsClicked()
    {
        Debug.Log("[Interface] Options clicked");
    }

    /// <summary>
    /// Handling UI Quit Button Click.
    /// </summary>
    public void OnQuitClicked()
    {
        Debug.Log("[Interface] Quit clicked");
    }
}

Isn’t it easy? 🤓

Conclusions

In the beginning, I would like to point out that both methods are great for making your code more modular and extendable. Of course, both have their cons and pros, and you have to know when to use one or the other. So let me highlight a few points for each here.

Events

They are great as you don’t have to worry about who is listening to them. Your job here is just to provide information about what is going on with the code you are running. In this example, it will be just notifying anyone who signs up for receiving calls on specific events.

What is also great about Events is that you can attach as many listeners to them as you want! For example, if you are handling UI events in one place and you would like to attach analytics to it, it’s not a problem. You are just adding more methods to the events, and that won’t interfere with the rest of the game. 🥰

To balance the pros for Events, I would say that when you have a lot of events in UI views, it might start to get messy with attaching and detaching code which will grow enormously.

Interface Listeners

With Interfaces, this is much easier as you are assigning and removing the reference from the variable. Of course, you have to create an interface for it, but it’s a small price for such convenience.

The interface also gives you the certainty that you are implementing correct methods and you don’t have to worry that you are missing something. That can be viewed from the pros and cons perspective depending on what and how you want to do things.

But the biggest issue is that you can only assign one reference to the variable here. That won’t be a problem in most cases, but sometimes you want to have a little bit more flexibility.

Of course, from the outside they will look the same.

Ultimatly it’s your call!

You don’t need to jump and start implementing all the things in one way or the other, but it’s good to know what you can and what you can’t do with both of them. 🤓

I hope you enjoyed it!

As always project is available at my public repository here. 🔗

Share it if you find it interesting, or you are planning to test it yourself!

See you here next time! 🔥

0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments