The Events system in Forge provides a flexible event bus for triggering gameplay reactions, driving ability activation, and propagating tagged event data.
For a practical guide on using events with abilities, see the Abilities documentation.
- Events carry tags for filtering
EventTagsplus optional source, target, magnitude, and payload data. - Handlers subscribe by tag and run in priority order (higher priority first).
- Generic events avoid boxing by using typed payloads.
- Generic raises do not forward to non-generic handlers.
public readonly record struct EventData
{
public TagContainer EventTags { get; init; }
public IForgeEntity? Source { get; init; }
public IForgeEntity? Target { get; init; }
public float EventMagnitude { get; init; }
public object? Payload { get; init; }
}public readonly record struct EventData<TPayload>
{
public TagContainer EventTags { get; init; }
public IForgeEntity? Source { get; init; }
public IForgeEntity? Target { get; init; }
public float EventMagnitude { get; init; }
public TPayload Payload { get; init; }
}- EventTags: Tag-based filtering key (uses
TagContainer.HasTag). - Source/Target: Originator and intended recipient of the event.
- EventMagnitude: Optional numeric intensity.
- Payload: Optional opaque object, or a typed payload for generic events.
EventManager manages subscriptions and dispatch. While every IForgeEntity has an EventManager instance, you can create and manage additional instances for any purpose: global event buses, subsystem-specific channels, or custom scopes.
// Per-entity event manager (built-in)
entity.Events.Raise(in eventData);
// Custom event manager for a subsystem
var combatEvents = new EventManager();
combatEvents.Subscribe(damageTag, OnDamageDealt);
// Global event bus
public static class GlobalEvents
{
public static EventManager Instance { get; } = new EventManager();
}public sealed class EventManager
{
public void Raise(in EventData data);
public void Raise<TPayload>(in EventData<TPayload> data);
public EventSubscriptionToken Subscribe(Tag eventTag, Action<EventData> handler, int priority = 0);
public EventSubscriptionToken Subscribe<TPayload>(Tag eventTag, Action<EventData<TPayload>> handler, int priority = 0);
public bool Unsubscribe(EventSubscriptionToken token);
}- Subscriptions are sorted by
priority(higher first). - A handler is invoked when
data.EventTags.HasTag(eventTag)is true. - Generic subscriptions are stored per
TPayloadtype and are only invoked for matching generic raises.
var eventTag = Tag.RequestTag(tagsManager, "events.combat.hit");
EventSubscriptionToken token = entity.Events.Subscribe(eventTag, data =>
{
// React to combat hit
var source = data.Source;
var target = data.Target;
float magnitude = data.EventMagnitude;
});
// Raise the event
entity.Events.Raise(new EventData
{
EventTags = eventTag.GetSingleTagContainer(),
Source = attacker,
Target = victim,
EventMagnitude = 25f
});public record struct HitPayload(int Damage, bool Critical);
var hitTag = Tag.RequestTag(tagsManager, "events.combat.hit");
EventSubscriptionToken token = entity.Events.Subscribe<HitPayload>(hitTag, data =>
{
HitPayload payload = data.Payload;
int damage = payload.Damage;
bool crit = payload.Critical;
});
entity.Events.Raise(new EventData<HitPayload>
{
EventTags = hitTag.GetSingleTagContainer(),
Source = attacker,
Target = victim,
EventMagnitude = 25f,
Payload = new HitPayload(25, critical: true)
});To unsubscribe from an event, call Unsubscribe using the corresponding EventSubscriptionToken.
entity.Events.Unsubscribe(token);- Use dedicated event tags (e.g.,
events.combat.hit,events.status.applied) registered inTagsManager. - Matching uses hierarchy:
EventTags.HasTag(subscriptionTag)supports parent/child tag relationships.
- Abilities can use event tags as triggers (see ability trigger configurations in the Abilities system).
- Event tags can align with cues or effects to coordinate cross-system reactions.
- When an event should trigger visual feedback, raise the event for gameplay logic, then trigger the corresponding cue separately for presentation.
Events and Cues serve distinct purposes:
- Events are part of the core simulation. They drive gameplay logic, trigger abilities, and propagate state changes. In a networked context, events would require reliable replication.
- Cues are for the presentation layer. They handle visual effects, audio, and player feedback. In a networked context, cues can use unreliable replication since they don't affect game state.
Use Events when the outcome affects game state or triggers other gameplay systems. Use Cues when you need to communicate changes to the player through feedback.
- Define Tag Conventions: Use consistent prefixes (e.g.,
events.*) for clarity. - Prefer Typed Payloads: Use
EventData<TPayload>to avoid boxing and improve safety. - Use Priorities Sparingly: Reserve high priorities for critical handlers; keep most at default.
- Unsubscribe When Done: Store tokens and call
Unsubscribeto avoid stale handlers. - Keep Handlers Lightweight: Avoid heavy work inside handlers; defer long tasks if needed.
- Validate Tags: Ensure tags are registered in
TagsManagerbefore use. - Separate Events from Cues: Use events for gameplay-affecting logic; trigger cues separately for presentation.
- Consider Scope: Use entity-level
EventManagerfor entity-specific events; create custom instances for broader or specialized scopes.