unity decouples the project through global events

Keywords: Unity Design Pattern Unity3d

If A class wants to call the method of another class, for example, A wants to call the method of B.
Method 1: Reference
The simplest way is that A holds A reference to class B, that is, A has A member object of B.
Mode 2: design mode (intermediary mode)
Or a and B belong to the same Object C and call each other through Object C (mediator mode), such as QQ group. A and B do not have each other's friends, but they are in a QQ group C. they can talk to each other in C.
Mode 3: static (usually single instance mode, B is management class, etc.)
Set the method in B as a static method.
However, in the project, there are N classes, and A and B have no relationship at all. It is not suitable to have each other's reference. Moreover, they are not tool classes and should not be set as static methods. So at this time, how can we connect A and B?
The answer is to use the global event system.
Extract a globally accessible event list (Dictionary)

public class EventManager : MonoBehaviour
{

    public static Dictionary<EventType, List<Delegate>> EventDic = new Dictionary<EventType, List<Delegate>>();

    public delegate void CallBack();
    public delegate void CallBack<T>(T parameter);

    public static void AddListener(EventType eventType, CallBack callBack)
    {
        if (EventDic.ContainsKey(eventType))
        {
            EventDic[eventType].Add(callBack);
        }
        else
        {
            EventDic.Add(eventType, new List<Delegate>() { callBack });
        }
    }

    public static void RemoveListener(EventType eventType, CallBack callBack)
    {
        if (EventDic.ContainsKey(eventType))
        {
            CallBack currentCallBack = null;
            foreach (var item in EventDic[eventType])
            {
                currentCallBack = item as CallBack;
                if (currentCallBack == callBack)
                {
                    currentCallBack = callBack;
                }
            }
            if (currentCallBack != null)
            {
                EventDic[eventType].Remove(callBack);
            }
            if (EventDic[eventType].Count == 0)
            {
                EventDic.Remove(eventType);
            }
        }
        else
        {
            throw new Exception("The event is not registered in the current system");
        }
    }

    public static void AddListener<T>(EventType eventType, CallBack<T> callBack)
    {
        if (EventDic.ContainsKey(eventType))
        {
            EventDic[eventType].Add(callBack);
        }
        else
        {
            EventDic.Add(eventType, new List<Delegate>() { callBack });
        }
    }

    public static void RemoveListener<T>(EventType eventType, CallBack<T> callBack)
    {
        if (EventDic.ContainsKey(eventType))
        {
            CallBack<T> currentCallBack = null;
            foreach (var item in EventDic[eventType])
            {
                currentCallBack = item as CallBack<T>;
                if (currentCallBack == callBack)
                {
                    currentCallBack = callBack;
                }
            }
            if (currentCallBack != null)
            {
                EventDic[eventType].Remove(callBack);
            }
            if (EventDic[eventType].Count == 0)
            {
                EventDic.Remove(eventType);
            }
        }
        else
        {
            throw new Exception("The event is not registered in the current system");
        }
    }



    public static void Dispatch(EventType eventType)
    {
        if (EventDic.ContainsKey(eventType))
        {
            CallBack callBack;
            foreach (var item in EventDic[eventType])
            {
                callBack = item as CallBack;
                if (callBack != null)
                {
                    callBack();
                }
            }
        }
        else
        {
            throw new Exception("The current event system does not contain this event");
        }
    }

    public static void Dispatch<T>(EventType eventType, T parameter)
    {
        if (EventDic.ContainsKey(eventType))
        {
            CallBack<T> callBack;
            foreach (var item in EventDic[eventType])
            {
                callBack = item as CallBack<T>;
                if (callBack != null)
                {
                    callBack(parameter);
                }
            }
        }
        else
        {
            throw new Exception("The current event system does not contain this event");
        }
    }

}

public class EventData 
{
    public int money;
}

public enum EventType
{
    AddMoney,
    AddMoneyWithParameter,
    AddMoneyWithEventData,
}

The index is an enumeration and the value is a delegate type.
Then A and B register the methods they need to call externally.
Here is an example of an employee's boss.
The boss provides the method of collecting money, and the employee is responsible for calling it.

public class Boss: MonoBehaviour
{
    public Button AddListenerButton;
    public Button RemoveListenerButton;

    void Awake()
    {
        AddListenerButton.onClick.AddListener(() =>
        {
            EventManager.AddListener(EventType.AddMoney, GetMoney);
            EventManager.AddListener<int>(EventType.AddMoneyWithParameter, GetMoneyWihtPrameter);
            EventManager.AddListener<EventData>(EventType.AddMoneyWithEventData, GetmoneyWithEventData);
        });

        RemoveListenerButton.onClick.AddListener(() =>
        {
            EventManager.RemoveListener(EventType.AddMoney, GetMoney);
            EventManager.RemoveListener<int>(EventType.AddMoneyWithParameter, GetMoneyWihtPrameter);
            EventManager.RemoveListener<EventData>(EventType.AddMoneyWithEventData, GetmoneyWithEventData);
        });
    }
    void GetMoney()
    {
        Debug.Log("The employees are great when they receive the funds purchased by users");
    }
    void GetMoneyWihtPrameter(int money)
    {
        Debug.Log("After receiving the funds purchased by users, the employees are great and the income is earned  " + money);
    }

    void GetmoneyWithEventData(EventData data) 
    {
        Debug.Log("Received the funds purchased by users through the event data, the employees were great, and the income was earned  " + data.money);
    }
}

public class Staff : MonoBehaviour
{
    public Button BuyButton;

    public Button BuyButtonWithParameter;

    public Button BuyButtonWithEventData;

    private void Start()
    {
        BuyButton.onClick.AddListener(() =>
        {
            EventManager.Dispatch(EventType.AddMoney);
        });
        BuyButtonWithParameter.onClick.AddListener(() =>
        {
            EventManager.Dispatch(EventType.AddMoneyWithParameter, 100);
        });
        BuyButtonWithEventData.onClick.AddListener(() =>
        {
            EventManager.Dispatch(EventType.AddMoneyWithEventData, new EventData() { money = 200});
        });
    }
}

The actual effect is demonstrated as follows:

Summary:
The global event uses three parts: generics (the data type is determined when calling), the parent class can accept all subclasses (Delegate can receive all subclasses), and overloading (the registered method can have parameters or no parameters).
It is somewhat similar to the mediator model. As a mediator, the global event provides an event registration and call to the outside, which achieves the problem of decoupling between classes.
Demo

Posted by arpowers on Thu, 25 Nov 2021 16:32:10 -0800