10 Trigger Event: Parallel Event Dispatch
๐ Overviewโ
In complex games, one action (like "Attack Command") often needs to trigger multiple independent systems: combat logic, sound effects, UI updates, achievements, analytics, etc. Implementing this in code leads to bloated functions with dozens of lines. The Flow Graph visualizes this as parallel dispatchโone root event fans out to multiple conditional branches, each with its own priority and filtering logic.
- How to use the Flow Graph for visual event routing
- Parallel execution vs sequential priority ordering
- Conditional branching with node conditions
- Type conversion and argument filtering in trigger nodes
- The difference between Trigger Events and Chain Events
๐ฌ Demo Sceneโ
Assets/TinyGiants/GameEventSystem/Demo/10_TriggerEvent/10_TriggerEvent.unity
Scene Compositionโ
Visual Elements:
-
๐ด Turret_A (Left) - Red "Smart" turret
- Priority Order: Buff (100) โ Fire (50)
- Result: Critical Hit
-
๐ต Turret_B (Right) - Blue "Glitchy" turret
- Priority Order: Fire (100) โ Buff (30)
- Result: Weak Hit (buff arrives too late)
-
๐ฏ TargetDummy - Center capsule target
- Receives damage from both turrets
- Has Rigidbody for physics reactions
-
๐บ HoloDisplay - Information panel
- Displays damage data logs
- Shows "SYSTEM READY" by default
- Updates with damage info when triggered
-
๐จ AlarmVignette - Fullscreen red overlay
- Flashes when global alarm triggers
- Independent of turret-specific branches
UI Layer (Canvas):
- ๐ฎ Two Command Buttons - Bottom of the screen
- "Command A" โ Triggers
TriggerEventRaiser.CommandTurretA() - "Command B" โ Triggers
TriggerEventRaiser.CommandTurretB()
- "Command A" โ Triggers
Game Logic Layer:
-
๐ค TriggerEventRaiser - Command issuer
- Only references ONE root event:
onCommand - Completely unaware of downstream events
- Ultimate decoupling demonstration
- Only references ONE root event:
-
๐ฅ TriggerEventReceiver - Action executor
- Contains 5 independent action methods
- Flow Graph orchestrates which methods execute when
- Methods have different signatures (void, single arg, dual args)
๐ฎ How to Interactโ
The Parallel Dispatch Experimentโ
One root event (onCommand) splits into multiple parallel branches based on conditions and priorities.
Step 1: Enter Play Modeโ
Press the Play button in Unity.
Initial State:
- Two turrets idle (slow rotation sweep)
- HoloDisplay shows "SYSTEM READY"
- No alarm vignette visible
Step 2: Test Smart Turret (Correct Priority)โ
Click "Command A":
What Happens:
- ๐ฏ Red turret rotates toward target (fast tracking)
- ๐ Projectile fires and travels
- ๐ฅ On impact - Root event raised with
Turret_Aas sender
Parallel Execution Branches:
Branch 1: Turret A Specific (Conditional):
-
โ onActiveBuff (Priority 100)
- Condition:
sender.name.Contains("Turret_A")โ TRUE - Executes FIRST due to highest priority
- Turret turns gold, buff aura spawns
- Sets
_isBuffedA = true - Console:
[Receiver] (A) SYSTEM OVERCHARGE: Buff Activated for Turret_A.
- Condition:
-
โ onTurretFire (Priority 50)
- Condition:
sender.name.Contains("Turret_A")โ TRUE - Executes SECOND (lower priority than Buff)
- Checks
_isBuffedAโ finds it TRUE - Result: CRIT! -500 damage
- Orange floating text, explosion VFX, camera shake
- Console:
[Receiver] (B) TURRET HIT: Critical Strike! (500 dmg)
- Condition:
Branch 2: Global (Unconditional):
-
โ onHoloData (Priority 1s delay)
- No condition โ always executes
- Type conversion: Drops
GameObjectsender, passes onlyDamageInfo - HoloDisplay updates: "Damage DATA Type: Physical, Target: 100"
- Console:
[Receiver] (C) HOLO DATA: Recorded 100 damage packet.
-
โ onGlobalAlarm (Priority immediate, void)
- No condition โ always executes
- Type conversion: Drops all arguments
- Screen flashes red 3 times
- Alarm sound plays
- Console:
[Receiver] (D) ALARM: HQ UNDER ATTACK! EMERGENCY PROTOCOL!
-
โ onSecretFire (Priority 1s delay, argument blocked)
- No condition โ always executes
- PassArgument = false โ receives default/null values
- Console:
[Receiver] (E) SECURE LOG: Data transmission blocked by Graph.
Result: โ Smart turret achieves critical hit because buff applied BEFORE damage calculation.
Step 3: Test Glitchy Turret (Wrong Priority)โ
Click "Command B":
What Happens:
- ๐ฏ Blue turret rotates toward target
- ๐ Projectile fires and travels
- ๐ฅ On impact - Root event raised with
Turret_Bas sender
Parallel Execution Branches:
Branch 1: Turret B Specific (Conditional):
-
โ onActiveBuff (Turret A condition)
- Condition:
sender.name.Contains("Turret_A")โ FALSE - NOT EXECUTED - filtered out by condition
- Condition:
-
โ onTurretFire (Priority 100) - Different node than Turret A
- Condition:
sender.name.Contains("Turret_B")โ TRUE - Executes FIRST (highest priority in Turret B branch)
- Checks
_isBuffedBโ finds it FALSE (buff hasn't run yet) - Result: -100 normal damage
- Grey floating text, small explosion
- Console:
[Receiver] (B) TURRET HIT: Normal Hit. (100 dmg)
- Condition:
-
โ onActiveBuff (Priority 30) - Different node than Turret A
- Condition:
sender.name.Contains("Turret_B")โ TRUE - Executes SECOND (lower priority)
- Turret turns gold, buff aura spawns
- Sets
_isBuffedB = trueTOO LATE! - Console:
[Receiver] (A) SYSTEM OVERCHARGE: Buff Activated for Turret_B.
- Condition:
Branch 2: Global (Unconditional):
- Same 3 global nodes execute (onHoloData, onGlobalAlarm, onSecretFire)
- Independent of which turret fired
Result: โ Glitchy turret gets normal hit because damage calculated BEFORE buff applied.
Both turrets trigger the same root event (onCommand), but:
- Conditional nodes filter by sender name
- Priority order within each branch determines outcome
- Global nodes execute regardless of sender
- All branches evaluate in parallel (same frame)
๐๏ธ Scene Architectureโ
Parallel vs Sequential Executionโ
Traditional Sequential Code:
void OnAttackCommand(GameObject sender, DamageInfo info)
{
if (sender.name == "Turret_A") ActivateBuff(sender, info);
TurretHit(sender, info);
if (sender.name == "Turret_A") ActivateBuff(sender, info); // Wrong order!
HoloDamageData(info);
GlobalAlarm();
LogSecretAccess(sender, info);
}
Flow Graph Parallel Dispatch:
๐ก Root: onCommand.Raise(sender, info)
โ
โโ ๐ฑ [ Conditional Branch: Turret A ] โ ๐ก๏ธ Guard: `Sender == "Turret_A"`
โ โโ ๐ [Prio: 100] โ onActiveBuff() โ
Executes 1st
โ โโ โก [Prio: 50 ] โ onTurretFire() โ
Executes 2nd
โ
โโ ๐ฑ [ Conditional Branch: Turret B ] โ ๐ก๏ธ Guard: `Sender == "Turret_B"`
โ โโ โก [Prio: 100] โ onTurretFire() โ
Executes 1st
โ โโ ๐ [Prio: 30 ] โ onActiveBuff() โ
Executes 2nd
โ
โโ ๐ [ Global Branch: Always Run ] โ ๐ข Guard: `None (Always Pass)`
โโ ๐ฝ๏ธ onHoloData โฑ๏ธ Delay: 1.0s | ๐ข Single Arg
โโ ๐จ onGlobalAlarm โก Immediate | ๐ Void (Signal Only)
โโ ๐ต๏ธ onSecretFire โฑ๏ธ Delay: 1.0s | ๐ก๏ธ Blocked Args
Execution Behavior:
- All branches evaluate simultaneously (parallel)
- Conditions filter which nodes execute
- Priority determines order within passing branches
- Type conversion happens automatically per node
Event Definitionsโ

| Event Name | Type | Role | Color |
|---|---|---|---|
onCommand | GameEvent<GameObject, DamageInfo> | Root | Gold |
onActiveBuff | GameEvent<GameObject, DamageInfo> | Trigger | Green |
onTurretFire | GameEvent<GameObject, DamageInfo> | Trigger | Green |
onHoloData | GameEvent<DamageInfo> | Trigger | Green |
onGlobalAlarm | GameEvent (void) | Trigger | Green |
onSecretFire | GameEvent<GameObject, DamageInfo> | Trigger | Green |
Key Insight:
- Root event (gold): Only one directly raised by code
- Trigger events (green): Automatically triggered by Flow Graph
- Code only knows about
onCommandโcompletely decoupled from downstream logic
Flow Graph Configurationโ
Click "Flow Graph" button in the Game Event Editor to open the visual graph:

Graph Structure:
Root Node (Left, Red):
onCommand <GameObject, DamageInfo>- Entry point for entire graph
- Single node raised by code
Turret A Branch (Top Right, Green):
onActiveBuff(Priority: โ 100, Condition: Turret_A, Pass: โ)- Highest priority in branch
- Only executes if sender is Turret_A
onTurretFire(Priority: โ 50, Condition: Turret_A, Pass: โ)- Second priority
- Only executes if sender is Turret_A
Turret B Branch (Middle Right, Green):
onTurretFire(Priority: โ 100, Condition: Turret_B, Pass: โ)- Highest priority in branch
- Only executes if sender is Turret_B
onActiveBuff(Priority: โ 30, Condition: Turret_B, Pass: โ)- Lower priority (executes after Fire!)
- Only executes if sender is Turret_B
Global Branch (Bottom Right, Yellow/Green):
onHoloData(Delay: โฑ๏ธ1s, Pass: ๐ด Single Arg Only)- Type conversion:
<GameObject, DamageInfo>โ<DamageInfo> - Yellow line indicates type compatibility warning
- Type conversion:
onGlobalAlarm(Pass: โญ Void)- Type conversion:
<GameObject, DamageInfo>โ(void) - Drops all arguments
- Type conversion:
onSecretFire(Delay: โฑ๏ธ1s, Pass: ๐ Static/Blocked)- PassArgument = false
- Receives default/null values
Legend:
- ๐ข Green Lines: Type match (compatible)
- ๐ก Yellow Lines: Type conversion (compatible with data loss)
- ๐ด Red Lines: Type incompatible (won't connect)
The Flow Graph provides instant visual understanding of:
- Which events trigger which downstream events
- Execution priorities within branches
- Type conversions and argument passing
- Conditional routing logic
- Parallel execution structure
Sender Setup (TriggerEventRaiser)โ
Select the TriggerEventRaiser GameObject:

Game Event:
Command Event:onCommand- Tooltip: "The ONE event that triggers the whole graph"
- Type:
GameEvent<GameObject, DamageInfo>
Turret A (Smart):
Turret A: Turret_A (GameObject)Turret Head A: Head (Transform)Turret Muzzle A: MuzzlePoint (Transform)
Turret B (Rushed):
Turret B: Turret_B (GameObject)Turret Head B: Head (Transform)Turret Muzzle B: MuzzlePoint (Transform)
Shared Resources:
Projectile Prefab,Muzzle Flash VFX,Hit Target
Critical Observation: Script only references ONE event. It has NO KNOWLEDGE of the 5 downstream events. This is ultimate decouplingโthe Flow Graph handles all routing logic.
Receiver Setup (TriggerEventReceiver)โ
Select the TriggerEventReceiver GameObject:

Target References:
Target Dummy,Target Rigidbody
Visual Resources:
Buff VFX Prefab: TurretBuffAura (Particle System)Hit Normal VFX,Hit Crit VFX,Floating Text Prefab
Alarm VFX:
Alarm Screen Group: AlarmVignette (Canvas Group)Holo Text: LogText (Text Mesh Pro)
Turret Configurations:
- Turret A: Renderers array, Normal material
- Turret B: Renderers array, Normal material
- Shared: Buffed material (gold)
๐ป Code Breakdownโ
๐ค TriggerEventRaiser.cs (Sender)โ
using UnityEngine;
using TinyGiants.GameEventSystem.Runtime;
public class TriggerEventRaiser : MonoBehaviour
{
[Header("Game Event")]
[Tooltip("The ONE event that triggers the whole graph.")]
[GameEventDropdown]
public GameEvent<GameObject, DamageInfo> commandEvent;
[Header("Turret A (Smart)")]
public GameObject turretA;
// ... turret references ...
private bool _isAttackingA;
private bool _isAttackingB;
/// <summary>
/// Button A: Signals Turret A to attack.
/// Starts the aiming sequence, which culminates in raising the root event.
/// </summary>
public void CommandTurretA()
{
if (commandEvent == null || turretA == null) return;
_isAttackingA = true; // Begin rotation/fire sequence
}
/// <summary>
/// Button B: Signals Turret B to attack.
/// </summary>
public void CommandTurretB()
{
if (commandEvent == null || turretB == null) return;
_isAttackingB = true;
}
private void FireProjectile(GameObject senderTurret, Transform muzzle)
{
// Spawn muzzle flash, launch projectile...
var shell = Instantiate(projectilePrefab, muzzle.position, muzzle.rotation);
shell.Initialize(hitTarget.position, 20f, () =>
{
Vector3 hitPos = hitTarget.position;
DamageInfo info = new DamageInfo(100f, false, DamageType.Physical,
hitPos, "Commander");
// CRITICAL: Raise the ONE root event
// The Flow Graph decides everything else:
// - Which downstream events trigger
// - In what priority order
// - With what arguments
commandEvent.Raise(senderTurret, info);
Debug.Log($"[Sender] Impact confirmed from {senderTurret.name}. " +
"Event Raised.");
});
}
}
Key Points:
- ๐ฏ Single Event Reference - Only knows about root event
- ๐ Zero Downstream Knowledge - No idea about 5 trigger events
- ๐ก Simple API - Just
.Raise(sender, data) - ๐๏ธ Maximum Decoupling - Flow Graph handles all routing
๐ฅ TriggerEventReceiver.cs (Listener)โ
using UnityEngine;
using System.Collections;
public class TriggerEventReceiver : MonoBehaviour
{
private bool _isBuffedA;
private bool _isBuffedB;
/// <summary>
/// [Action A] Activate Buff
/// Bound to Trigger nodes in Flow Graph (separate nodes for Turret A and B).
///
/// Priority Impact:
/// - Turret A: Priority 100 โ Executes BEFORE damage (correct)
/// - Turret B: Priority 30 โ Executes AFTER damage (wrong!)
/// </summary>
public void ActivateBuff(GameObject sender, DamageInfo args)
{
if (sender == null) return;
bool isA = sender.name.Contains("Turret_A");
// Set the critical flag
if (isA) _isBuffedA = true;
else _isBuffedB = true;
// Visual feedback: Gold material + particle aura
Renderer[] targetRenderers = isA ? renderersA : renderersB;
foreach (var r in targetRenderers)
if (r) r.material = mat_Buffed;
if (buffVFXPrefab)
{
var vfx = Instantiate(buffVFXPrefab, sender.transform.position,
Quaternion.identity);
vfx.transform.SetParent(sender.transform);
vfx.Play();
if (isA) _auraA = vfx;
else _auraB = vfx;
}
Debug.Log($"[Receiver] (A) SYSTEM OVERCHARGE: Buff Activated for {sender.name}.");
}
/// <summary>
/// [Action B] Turret Hit
/// Bound to Trigger nodes in Flow Graph (separate nodes for Turret A and B).
///
/// Checks buff state AT MOMENT OF EXECUTION.
/// Priority determines whether buff is active yet.
/// </summary>
public void TurretHit(GameObject sender, DamageInfo args)
{
if (sender == null) return;
// Check if buff is currently active
bool isBuffed = sender.name.Contains("Turret_A") ? _isBuffedA : _isBuffedB;
float finalDamage = args.amount;
bool isCrit = false;
ParticleSystem vfxToPlay;
if (isBuffed)
{
// CRITICAL PATH: Buff was active
finalDamage *= 5f; // 500 damage
isCrit = true;
vfxToPlay = hitCritVFX;
StartCoroutine(ShakeCameraRoutine(0.2f, 0.4f));
Debug.Log($"[Receiver] (B) TURRET HIT: Critical Strike! ({finalDamage} dmg)");
}
else
{
// NORMAL PATH: Buff wasn't active yet
vfxToPlay = hitNormalVFX;
Debug.Log($"[Receiver] (B) TURRET HIT: Normal Hit. ({finalDamage} dmg)");
}
// Spawn VFX, apply physics, show floating text...
StartCoroutine(ResetRoutine(sender, isBuffed));
}
/// <summary>
/// [Action C] Holo Damage Data
/// Bound to Trigger node with TYPE CONVERSION.
///
/// Graph configuration:
/// - Input: GameEvent<GameObject, DamageInfo>
/// - Output: GameEvent<DamageInfo>
/// - Result: Sender is dropped, only data is passed
/// </summary>
public void HoloDamageData(DamageInfo info)
{
if (holoText)
{
holoText.text = $"Damage DATA\nType: {info.type}, Target: {info.amount}";
}
Debug.Log($"[Receiver] (C) HOLO DATA: Recorded {info.amount} damage packet.");
StartCoroutine(ClearLogRoutine());
}
/// <summary>
/// [Action D] Global Alarm
/// Bound to Trigger node with TYPE CONVERSION to VOID.
///
/// Graph configuration:
/// - Input: GameEvent<GameObject, DamageInfo>
/// - Output: GameEvent (void)
/// - Result: All arguments dropped
/// </summary>
public void GlobalAlarm()
{
Debug.Log("[Receiver] (D) ALARM: HQ UNDER ATTACK! EMERGENCY PROTOCOL!");
StopCoroutine(nameof(AlarmRoutine));
if (alarmScreenGroup) StartCoroutine(AlarmRoutine());
}
/// <summary>
/// [Action E] Secret Log
/// Bound to Trigger node with PassArgument = FALSE.
///
/// Demonstrates ARGUMENT BLOCKING:
/// Even though root event has data, this node receives default/null values.
/// Useful for security, debugging, or data isolation.
/// </summary>
public void LogSecretAccess(GameObject sender, DamageInfo data)
{
bool isBlocked = (data == null || (data.amount == 0 && data.attacker == null));
if (isBlocked)
Debug.Log("<color=lime>[Receiver] (E) SECURE LOG: " +
"Data transmission blocked by Graph.</color>");
else
Debug.Log("<color=red>[Receiver] (E) SECURE LOG: " +
"Data LEAKED! ({data.amount})</color>");
}
private IEnumerator AlarmRoutine()
{
int flashes = 3;
float flashDuration = 0.5f;
for (int i = 0; i < flashes; i++)
{
if (alarmClip) _audioSource.PlayOneShot(alarmClip);
// Sine wave alpha animation
float t = 0f;
while (t < flashDuration)
{
t += Time.deltaTime;
float alpha = Mathf.Sin((t / flashDuration) * Mathf.PI);
alarmScreenGroup.alpha = alpha * 0.8f;
yield return null;
}
alarmScreenGroup.alpha = 0f;
yield return new WaitForSeconds(0.1f);
}
}
}
Key Points:
- ๐ฏ 5 Independent Methods - Each handles one action
- ๐ Different Signatures - void, single arg, dual args
- ๐ State Dependency -
TurretHitreads_isBuffedA/Bflags - โฑ๏ธ Priority Critical - Order determines if buff is active
- ๐จ Type Agnostic - Methods don't know about type conversion
๐ Key Takeawaysโ
| Concept | Implementation |
|---|---|
| ๐ณ Flow Graph | Visual parallel dispatch replacing bloated code |
| ๐ฏ Trigger Nodes | Automatically fired downstream events |
| ๐ Conditional Routing | Node conditions filter execution |
| โฑ๏ธ Priority Ordering | Controls execution sequence within branches |
| ๐ Type Conversion | Automatic argument adaptation per node |
| ๐ Argument Blocking | PassArgument flag controls data transmission |
| ๐ก Parallel Execution | All branches evaluate simultaneously |
Trigger Events are perfect for:
- Fan-Out Architecture - One action triggers many systems
- Conditional Routing - Different logic paths based on sender/data
- Priority Management - Control execution order visually
- Type Adaptation - Connect incompatible event signatures
- Decoupling - Senders unaware of downstream complexity
Trigger vs Chain Events:
- Trigger (Parallel): All nodes evaluate simultaneously, filtered by conditions
- Chain (Sequential): Nodes execute in strict linear order, one after another
Use Trigger when you need parallel branching with conditions (e.g., combat system responding to different attackers). Use Chain when you need guaranteed sequential order (e.g., tutorial steps, cutscene sequences).
- Same Priority: If multiple nodes have identical priority, execution order is undefined
- Cross-Branch Priority: Priority only matters within the same conditional branch
- Delay Interaction: Delayed nodes may execute after non-delayed nodes regardless of priority
- State Mutations: Be careful with state changesโlater nodes see earlier mutations
๐ฏ What's Next?โ
You've mastered parallel trigger events. Now let's explore chain events for guaranteed sequential execution.
Next Chapter: Learn about sequential chains in 11 Chain Event
๐ Related Documentationโ
- Flow Graph Editor - Edit Node Flow Graph
- Node & Connector - Understand the visual language of the graph
- Node Behavior - Node configuration and conditions
- Advanced Logic Patterns - How the system executes Triggers versus Chains
- Programmatic Flow - How to Implement Process Control via FlowGraph API
- Best Practices - Architectural patterns for complex systems