Game development
C#
Unity3D
March 2015 27

Broadcast Event Messaging в Unity3D

При разработке игр, довольно часто возникает необходимость в построении системы широковещательной рассылки сообщений. Предположим, Вы хотите сделать так, чтобы в тот момент когда персонаж, управляемый игроком, вошел в определенную зону, или выполнил определенное действие, все заинтересованные в этом объекты получили уведомление. По возможности это уведомление должно нести в себе информацию о произошедшем событии. В данной статье, я предлагаю Вашему вниманию один из возможных способов построения подобной системы. Приведенная система построена на базе Unity3D EventSystem.

Начиная с версии 4.6 в состав Unity3D включена UI System, значительно упрощающая процесс создания UI. К тому же она является Open Source проектом. В основе этой системы лежат два очень важных компонента — EventSystem и InputModules, позволяющие принимать и обрабатывать события. По сути, InputModules — это обычные компоненты. Они являются наследниками UIBehaviour который в свою очередь наследует MonoBehaviour и содержат в себе логику обработки событий, поступающих от EventSystem.
Отправка события определенному GameObject осуществляется посредством вызова метода ExecuteEvents.Execute(). Определение метода имеет следующий вид:

public static bool Execute<T>(GameObject target, BaseEventData data, EventFunction<T> functor) where T : IEventSystemHandler;

Вызывая этот метод, в качестве параметров Вы должны передать ссылку на GameObject в списке ассоциированных компонентов которого, должен присутствовать InpuModule реализующий интерфейс T либо интерфейс-наследник T. Если таких компонентов окажется несколько, все они будут вызваны по очереди.
Как видно, для отправки события необходимо иметь ссылку на целевой GameObject, что не подходит для широковещательной рассылки.
Решением этой проблемы может послужить коллекция содержащая список GameObjects с прикрепленными InputModules способными обрабатывать широковещательные события.

public abstract class BroadcastInputModule<TEventType> : BaseInputModule
    where TEventType : IEventSystemHandler
{
    protected override void Awake()
    {
        base.Awake();
        BroadcastReceivers.RegisterBroadcastReceiver<TEventType>(gameObject);
    }

    protected override void OnDestroy()
    {
        base.OnDestroy();
        BroadcastReceivers.UnregisterBroadcastReceiver<TEventType>(gameObject);
    }
}

Класс BroadcastInputModule служит базовым для модулей обработчиков широковещательных событий. Его основной задачей является регистрация модуля в этой коллекции.
Давайте перейдем к созданию модуля, который будет реагировать на глобальное событие, которое условно назовём "SomethingHappened".

[AddComponentMenu("Event/Something Happened Input Module")]
public class SomethingHappenedInputModule
    : BroadcastInputModule<ISomethingHappenedEventHandler>, ISomethingHappenedEventHandler
{
    public void OnSomethigHappened(SomethingHappenedEventData data)
    {
        Debug.Log("SomethingHappenedInputModule::OnSomethigHappened()");
    }

    public override void Process()
    {
    }
}

Этот компонент должен быть добавлен ко всем GameObjects заинтересованным в получении события SomethingHappened. Интерфейс ISomethingHappenedEventHandler выглядит следующим образом:

public interface ISomethingHappenedEventHandler : IEventSystemHandler
{
    void OnSomethigHappened(SomethingHappenedEventData data);
}

Коллекция хранящая обработчики может быть довольно простой, например такой:

public static class BroadcastReceivers
{
    private static readonly IDictionary<Type, IList<GameObject>>
        BroadcstReceivers = new Dictionary<Type, IList<GameObject>>();

    public static IList<GameObject> GetHandlersForEvent<TEventType>()
        where TEventType : IEventSystemHandler
    {
        if (!BroadcstReceivers.ContainsKey(typeof (TEventType)))
        {
            return null;
        }
        return BroadcstReceivers[typeof (TEventType)];
    }

    public static void RegisterBroadcastReceiver<TEventType>(GameObject go)
        where TEventType : IEventSystemHandler
    {
        if (BroadcstReceivers.ContainsKey(typeof(TEventType)))
        {
            BroadcstReceivers[typeof(TEventType)].Add(go);
        }
        else
        {
            BroadcstReceivers.Add(typeof(TEventType), new List<GameObject>());
            BroadcstReceivers[typeof(TEventType)].Add(go);
        }
    }

    public static void UnregisterBroadcastReceiver<TEventType>(GameObject go)
    { . . . }    
}

В реальной игре, возможно, эта коллекция должна содержать WeakReferences вместо прямых ссылок на GameObjects, это уже зависит от требований.
Последним элементом является класс BroadcastExecuteEvents:

public static class BroadcastExecuteEvents
{
    public static void Execute<T>(BaseEventData eventData, ExecuteEvents.EventFunction<T> functor) 
        where T : IEventSystemHandler
    {
        var handlers = BroadcastReceivers.GetHandlersForEvent<T>();
        if (handlers == null) return;
        foreach (var handler in handlers)
        {
            ExecuteEvents.Execute<T>(handler, eventData, functor);
        }
    }
}

Как видно из его определения, он является всего лишь обёрткой над ExecuteEvents и выполняет всего одну задачу — выбор подходящих обработчиков для указанного события и их вызов.

Теперь произвести широковещательную посылку события можно так:

BroadcastExecuteEvents.Execute<ISomethingHappenedEventHandler>(null, (i, d) => i.OnSomethigHappened(new SomethingHappenedEventData()));


Исходный код к статье можно забрать с github.com/rumyancevpavel/BroadcastMessaging
+9
16.1k 90
Comments 4