# C# Event System While developing Narra's Gambit I've gone through many iterations of a event system. What I've stuck with has been something like this: `Event Bus => Static C# class that holds all relevant events as fields.` `Event Class => C# class that accepts generic types. Heavily based on C# Actions` `Then I can Subscribe and Unsubscribe to events in any class.` Event Bus (User Interface): ```csharp /// <summary> /// User Interface event bus. /// </summary> public static class UIEventBus { static UIEventBus() { Debug.Log("Loaded UI Event Bus"); } //player public static NarraEvent OnPlayerStatsAdjusted = new("OnPlayerStatsAdjusted"); public static NarraEvent OnUpdatePrimaryUI = new("OnUpdatePrimaryUI"); //inventory public static NarraEvent OnOpenInventory = new("OnOpenInventory"); //components public static NarraEvent<int, int> OnUpdateCurrentDayUI = new("OnUpdateCurrentDayUI"); public static NarraEvent<int, int> OnUpdateResourceUI = new("OnUpdateResourceUI"); public static NarraEvent<int, int> OnUpdateStaminaUI = new("OnUpdateStaminaUI"); public static NarraEvent<int, int> OnUpdateHealthUI = new("OnUpdateHealthUI"); public static NarraEvent<int> OnUpdateGoldUI = new("OnUpdateGoldUI"); public static NarraEvent<int> OnUpdateXPUI = new("OnUpdateXPUI"); //global message public static NarraEvent<string> OnSendGlobalMessage = new("OnSendGlobalMessage"); } ``` Event: ```csharp public class NarraEvent<T> { private Action<T> eventAction; private readonly string name; public NarraEvent(string eventName) { name = eventName; } public void RaiseEvent(T value) { if (eventAction != null) { eventAction?.Invoke(value); } else { Debug.LogWarning(quot;No Listeners Subscribed to {name} type"); } } public void Subscribe(Action<T> action) { eventAction += action; Debug.Log(quot;{name} listener Subscribed {action.Target}"); } public void Unsubscribe(Action<T> action) { eventAction -= action; Debug.Log(quot;{name} listener Unsubscribed {action.Target}"); } } ``` --- Using the *OnUpdateResourceUI* event as an example: ```csharp public static NarraEvent<int, int> OnUpdateResourceUI = new("OnUpdateResourceUI"); ``` Use in a class - Event Subscribe/Unsubscribe: ```csharp private void OnEnable() { UIEventBus.OnUpdateResourceUI.Subscribe(ResourceDisplay); } private void OnDisable() { UIEventBus.OnUpdateResourceUI.Unsubscribe(ResourceDisplay); } private void ResourceDisplay(int currentResource, int maxResource) { //do stuff } ``` Event Calling ```csharp UIEventBus.OnUpdateResourceUI.RaiseEvent(currentResource, resourcePerTurn); ``` # C# Reference Events I built a system we've dubbed "On" and "Do" events. - "On events" are when something happens and could trigger other events. - "On Take Damage" will be raised when the player takes any damage, but... - "Do events" are hooked into the player script as to bypass any other event calls. - "Do Damage", will not trigger the "On Take Damage event". - This avoids any crazy recursion we'd run into, and allows things like Ex: ```csharp [Serializable] public sealed class CritVampirism : EventHandlerTwoRef<SDamageStats>, IEventHandler { public override string Title => "Crit Vampirism"; public override string Description => "When you land a critical strike heal for 15% of the damage done"; public bool IsEnabled => true; public void Subscribe() { GameplayOnEventBus.OnEnemyTakeDamage.Subscribe(Handle); } public void Unsubscribe() { GameplayOnEventBus.OnEnemyTakeDamage.Unsubscribe(Handle); } protected override void Handle(ref SDamageStats originalData, ref SDamageStats modifiedData) { if (originalData.damageFlag == EDamageFlag.CRIT && originalData.attacker is PlayerBase) { Resolve(ref originalData, ref modifiedData); } } protected override void Resolve(ref SDamageStats originalData, ref SDamageStats modifiedData) { int value = (int)Math.Round(originalData.Damage * .15f); SDamageStats healing = new() { Damage = value, damageType = StaticReferenceManager.GetDamageType("healing"), damageFlag = EDamageFlag.NONE }; GameplayDoEventBus.DoPlayerHeal.RaiseEvent(healing); } } ```