EventBus - Sistem Acara untuk Unity

Dalam artikel ini, saya akan memandu Anda tentang apa itu sistem acara dalam kaitannya dengan Unity. Mari pelajari metode populer dan analisis secara detail implementasi pada antarmuka, yang saya temui saat bekerja di Owlcat Games.





Kandungan



  1. Apa itu sistem acara?
  2. Implementasi yang ada

    2.1. Langganan kunci

    2.2. Langganan menurut Jenis Acara

    2.3. Langganan menurut jenis pelanggan


  3. 3.1.

    3.2.

    3.3.


  4. 4.1.

    4.2.

    4.3.


1. ?



: UI, , , . :



  1. . .
  2. . .
  3. . .


, . . , . , .



public class InputManager : MonoBehavioiur
{
    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.S))
        {
            EventSystem.RaiseEvent("quick-save");
        }
    }
}

public class SaveLoadManager : Monobehaviour
{
    private void OnEnable()
    {
        EventSystem.Subscribe("quick-save", QuickSave);
    }

    private void OnDisable()
    {
        EventSystem.Unsubscribe("quick-save", QuickSave);
    }

    private void QuickSave()
    {
        //  
        ...
    }
}


SaveLoadManager.OnEnable() QuickSave "quick-save". , EventSystem.RaiseEvent("quick-save") SaveLoadManager.QuickSave() . , null reference exception .



. , .



— , . . — .



2.



:



// 
EventSystem.Subscribe(_, _);

// 
EventSystem.RaiseEvent(_, );


, .



2.1.



_ Enum. — IDE, . . params object[] args. IDE .



// 
EventSystem.Subscribe("get-damage", OnPlayerGotDamage);

// 
EventSystem.RaiseEvent("get-damage", player, 10);

//  
void OnPlayerGotDamage(params object[] args)
{
    Player player = args[0] as Player;
    int damage = args[1] as int;
    ...
}


2.2.



, .



// 
EventSystem.Subscribe<GetDamageEvent>(OnPlayerGotDamage);

// 
EventSystem.RaiseEvent<GetDamageEvent>(new GetDamageEvent(player, 10));

//  
void OnPlayerGotDamage(GetDamageEvent evt)
{
    Player player = evt.Player;
    int damage = evt.Damage;
    Debug.Log($"{Player} got damage {damage}");
}


2.3.



. , . , .



public class UILog : MonoBehaviour, IPlayerDamageHandler
{
    void Start()
    {
        // 
        EventSystem.Subscribe(this);
    }

    //  
    public void HandlePlayerDamage(Player player, int damage)
    {
        Debug.Log($"{Player} got damage {damage}");
    }
}

// 
EventSystem.RaiseEvent<IPlayerDamageHandler>(h =>
    h.HandlePlayerDamage(player, damage));


3.



. , . " ".



3.1.



, , .



. , :



public interface IQiuckSaveHandler : IGlobalSubscriber
{
    void HandleQuickSave();
}


, , IGlobalSubscriber. - , . IGlobalSubscriber , .



:



public class SaveLoadManager : Monobehaviour, IQiuckSaveHandler
{
    private void OnEnable()
    {
        EventBus.Subscribe(this);
    }

    private void OnDisable()
    {
        EventBus.Unsubscribe(this);
    }

    private void HandleQuickSave()
    {
        //  
        ...
    }
}


Subscribe.



public static class EventBus
{
    private static Dictionary<Type, List<IGlobalSubscriber>> s_Subscribers
        = new Dictionary<Type, List<IGlobalSubscriber>>();

    public static void Subscribe(IGlobalSubscriber subscriber)
    {
        List<Type> subscriberTypes = GetSubscriberTypes(subscriber.GetType());
        foreach (Type t in subscriberTypes)
        {
            if (!s_Subscribers.ContainsKey(t))
                s_Subscribers[t] = new List<IGlobalSubscriber>();
            s_Subscribers[t].Add(subcriber);
        }
    }
}


s_Subscribers. , .



GetSubscriberTypes . -, . : IQiuckSaveHandlerSaveLoadManager .



subscriberTypes. s_Subscribers .



GetSubscribersTypes:



public static List<Type> GetSubscribersTypes(IGlobalSubscriber globalSubscriber)
{
    Type type = globalSubscriber.GetType();
    List<Type> subscriberTypes = type
        .GetInterfaces()
        .Where(it =>
                it.Implements<IGlobalSubscriber>() &&
                it != typeof(IGlobalSubscriber))
        .ToList();
    return subscriberTypes;
}


, , IGlobalSubscriber. , .



, EventBus , .



3.2.



, . InputManager 'S', .



:



public class InputManager : MonoBehavioiur
{
    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.S))
        {
            EventBus.RaiseEvent<IQiuckSaveHandler>(
                IQiuckSaveHandler handler => handler.HandleQuickSave());
        }
    }
}


RaiseEvent:



public static class EventBus
{
    public static void RaiseEvent<TSubscriber>(Action<TSubscriber> action)
    where TSubscriber : IGlobalSubscriber
    {
        List<IGlobalSubscriber> subscribers = s_Subscribers[typeof(TSubscriber)];
        foreach (IGlobalSubscriber subscriber in subscribers)
        {
            action.Invoke(subscriber as TSubscriber);
        }
    }
}


TSubscriber IQiuckSaveHandler. IQiuckSaveHandler handler => handler.HandleQuickSave() action, IQiuckSaveHandler. action HandleQuickSave .



IQiuckSaveHandler handler => handler.HandleQuickSave() C# h => h.HandleQuickSave().



, .



3.3.



. :



public interface IQuickSaveLoadHandler : IGlobalSubscriber
{
    void HandleQuickSave();
    void HandleQuickLoad();
}


, , .



, - . 1 . .



public interface IUnitDeathHandler : IGlobalSubscriber
{
    void HandleUnitDeath(Unit deadUnit, Unit killer);
}

public class UILog : IUnitDeathHandler
{
    public void HandleUnitDeath(Unit deadUnit, Unit killer)
    {
        Debug.Log(killer.name + " killed " + deadUnit.name);
    }
}

public class Unit 
{
    private int m_Health

    public void GetDamage(Unit damageDealer, int damage)
    {
        m_Health -= damage;
        if (m_Health <= 0)
        {
            EventBus.RaiseEvent<IQiuckSaveHandler>(h =>
                h.HandleUnitDeath(this, damageDealer));
        }
    }
}


.



4.



, , .



4.1.



. , try catch:



public static void RaiseEvent<TSubscriber>(Action<TSubscriber> action)
where TSubscriber : IGlobalSubscriber
{
    List<IGlobalSubscriber> subscribers = s_Subscribers[typeof(TSubscriber)];
    foreach (IGlobalSubscriber subscriber in subscribers)
    {
        try
        {
            action.Invoke(subscriber as TSubscriber);
        }
        catch (Exception e)
        {
            Debug.LogError(e);
        }
    }
}


4.2.



GetSubscribersTypes , . , .



private static Dictionary<Type, List<Types>> s_CashedSubscriberTypes = 
    new Dictionary<Type, List<Types>>()

public static List<Type> GetSubscribersTypes(
    IGlobalSubscriber globalSubscriber)
{
    Type type = globalSubscriber.GetType();
    if (s_CashedSubscriberTypes.ContainsKey(type))
        return s_CashedSubscriberTypes[type];

    List<Type> subscriberTypes = type
        .GetInterfaces()
        .Where(it =>
                it.Implements<IGlobalSubsriber>() &&
                it != typeof(IGlobalSubsriber))
        .ToList();

    s_CashedSubscriberTypes[type] = subscriberTypes;
    return subscriberTypes;
}


4.3.



, - :



public static void Unsubscribe(IGlobalSubsriber subcriber)
{
    List<Types> subscriberTypes = GetSubscriberTypes(subscriber.GetType());
    foreach (Type t in subscriberTypes)
    {
        if (s_Subscribers.ContainsKey(t))
            s_Subscribers[t].Remove(subcriber);
    }
}


.



Collection was modified; enumeration operation might not execute.



, - foreach .



foreach (var a in collection)
{
    if (a.IsBad())
    {
        collection.Remove(a); //  
    }
}


, .



, . , , . , , null. .



public class SubscribersList<TSubscriber> where TSubscriber : class
{
    private bool m_NeedsCleanUp = false;

    public bool Executing;

    public readonly List<TSubscriber> List = new List<TSubscriber>();

    public void Add(TSubscriber subscriber)
    {
        List.Add(subscriber);
    }

    public void Remove(TSubscriber subscriber)
    {
        if (Executing)
        {
            var i = List.IndexOf(subscriber);
            if (i >= 0)
            {
                m_NeedsCleanUp = true;
                List[i] = null;
            }
        }
        else
        {
            List.Remove(subscriber);
        }
    }

    public void Cleanup()
    {
        if (!m_NeedsCleanUp)
        {
            return;
        }

        List.RemoveAll(s => s == null);
        m_NeedsCleanUp = false;
    }
}


EventBus:



public static class EventBus
{
    private static Dictionary<Type, SubscribersList<IGlobalSubcriber>> s_Subscribers
        = new Dictionary<Type, SubscribersList<IGlobalSubcriber>>();
}


RaiseEvent:



public static void RaiseEvent<TSubscriber>(Action<TSubscriber> action)
where TSubscriber : IGlobalSubscriber
{
    SubscribersList<IGlobalSubscriber> subscribers = s_Subscribers[typeof(TSubscriber)];

    subscribers.Executing = true;
    foreach (IGlobalSubscriber subscriber in subscribers.List)
    {
        try
        {
            action.Invoke(subscriber as TSubscriber);
        }
        catch (Exception e)
        {
            Debug.LogError(e);
        }
    }
    subscribers.Executing = false;
    subscribers.Cleanup();
}


, . , , . , . , .



5.



. . .



Solusi kami dibedakan dengan penggunaan antarmuka. Jika Anda memikirkannya sedikit, penggunaan antarmuka di sistem acara sangat logis. Bagaimanapun, antarmuka pada awalnya diciptakan untuk menentukan kemampuan suatu objek. Dalam kasus kami, kami berbicara tentang kemampuan untuk bereaksi terhadap peristiwa tertentu dalam game.



Di masa depan, sistem dapat dikembangkan untuk proyek tertentu. Misalnya, dalam game kami ada langganan ke acara unit tertentu. Panggilan lain dan penyelesaian beberapa peristiwa mekanis.



Tautan bukan repositori.




All Articles