In diesem Artikel werde ich Ihnen zeigen, was das Ereignissystem in Bezug auf Unity ist. Lassen Sie uns gängige Methoden studieren und die Implementierung auf den Schnittstellen, die ich bei Owlcat Games kennengelernt habe, im Detail analysieren.

Inhalt
- Was ist ein Ereignissystem?
- Bestehende Implementierungen
2.1. Schlüsselabonnement
2.2. Abonnement nach Ereignistyp
2.3. Abonnement nach Abonnententyp -
3.1.
3.2.
3.3. -
4.1.
4.2.
4.3.
1. ?
: UI, , , . :
- . .
- . .
- . .
, . . , . , .
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 . -, . : IQiuckSaveHandler — SaveLoadManager .
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.
. . .
Unsere Lösung zeichnet sich durch die Verwendung von Schnittstellen aus. Wenn Sie ein wenig darüber nachdenken, ist die Verwendung von Schnittstellen im Ereignissystem sehr logisch. Schließlich wurden Schnittstellen ursprünglich erfunden, um die Fähigkeiten eines Objekts zu definieren. In unserem Fall sprechen wir über die Fähigkeit, auf bestimmte Ereignisse im Spiel zu reagieren.
In Zukunft kann das System für ein bestimmtes Projekt entwickelt werden. In unserem Spiel gibt es beispielsweise Abonnements für die Ereignisse einer bestimmten Einheit. Ein weiterer Aufruf und Abschluss eines mechanischen Ereignisses.