07 Delayed Event: The Time Bomb Scenario
๐ Overviewโ
Standard events fire instantly (Raise() โ Execute()). Delayed events introduce a critical gap: Raise() โ [Pending State] โ Execute(). This demo demonstrates the Scheduling System through a classic "Cut the Wire" mini-game, where you'll learn how to configure delayed execution andโcriticallyโhow to cancel pending events before they execute.
- How to configure action delays in the Behavior Window
- How the event scheduling system works internally
- How to cancel pending events with
.Cancel() - The difference between visual timers and logic timers
๐ฌ Demo Sceneโ
Assets/TinyGiants/GameEventSystem/Demo/07_DelayedEvent/07_DelayedEvent.unity
Scene Compositionโ
Visual Elements:
- ๐ฃ TimeBomb_TNT - Cylindrical bomb in the center
- Black cylinder body with red caps
- Orange timer display showing countdown: "04.046" (updates in real-time)
- Two colored indicator lights (red and green) on top
- Sits on a grey circular platform
UI Layer (Canvas):
- ๐ฎ Three Buttons - Bottom of the screen
- "Arm Bomb" (White) โ Triggers
DelayedEventRaiser.ArmBomb() - "Cut RedWire" (Red/Pink) โ Triggers
DelayedEventRaiser.CutRedWire() - "Cut GreenWire" (Green) โ Triggers
DelayedEventRaiser.CutGreenWire()
- "Arm Bomb" (White) โ Triggers
Game Logic Layer (Demo Scripts):
-
๐ค DelayedEventRaiser - GameObject with the raiser script
- Manages bomb arming and wire cutting logic
- Randomly determines which wire is safe each round
- Controls visual countdown timer (cosmetic)
- Calls
.Cancel()when correct wire is cut
-
๐ฅ DelayedEventReceiver - GameObject with the receiver script
- Listens to
onExplodeEvent - Executes explosion logic: VFX, physics, camera shake
- Only called if timer reaches zero (not canceled)
- Listens to
Audio-Visual Feedback:
- ๐ Tick Sound - Plays every second during countdown
- ๐ฅ Explosion VFX - Particle system spawns on detonation
- โก Wire Sparks - Particle effect when cutting wires
- ๐น Camera Shake - Intense shake on explosion
๐ฎ How to Interactโ
The Defusal Challengeโ
You have 5 seconds to identify and cut the correct wire. One wire is SAFE (cancels the event), the other is a TRAP (does nothing).
The safe wire is randomized every time you arm the bomb! Pay attention to the Console log (or take your chances).
Step 1: Enter Play Modeโ
Press the Play button in Unity. The bomb displays "READY" in white text.
Step 2: Arm the Bombโ
Click "Arm Bomb" (White Button):
What Happens:
- ๐ Ticking sound begins (beep every second)
- โฑ๏ธ Timer starts counting down from
05.000in orange - ๐ฒ System randomly selects safe wire (Red or Green)
- ๐ Console reveals the answer:
[Game Logic] Bomb Armed! The SAFE wire is: Red - ๐ฃ Event enters Pending State - will execute in 5 seconds
Visual Changes:
- Timer text turns from white to orange
- Timer counts down with millisecond precision:
04.987,04.834... - Color gradually shifts from orange โ red as time runs out
Behind the Scenes:
explodeEvent.Raise()is called- Because Action Delay = 5s is configured in the Behavior Window
- The event is queued in the GameEventManager's scheduler
- A countdown timer starts internally
Step 3: Choose Your Fateโ
You now have three options with very different outcomes:
Option A: Do Nothing (Let It Explode)โ
Action: Don't click any button. Wait.
Timeline:
04.000- Second tick sound03.000- Tick, timer turns more red02.000- Tick, urgency builds01.000- Final tick00.000- BOOM!
Result: ๐ฅ EXPLOSION
- Console:
BOOM! The event executed. - Massive explosion VFX spawns at bomb location
- Bomb cylinder becomes kinetic and launches into the air
- Camera shakes violently (0.5s duration, 0.8 magnitude)
- Explosion sound plays
- Timer text changes to "ERROR" in dark red
Why: The 5-second delay elapsed, so DelayedEventReceiver.OnExplode() was invoked by the scheduler.
Option B: Cut the Wrong Wireโ
Action: Click the button that is NOT the safe wire.
Example: If Console said The SAFE wire is: Red, click "Cut GreenWire"
What Happens:
- โก Wire sparks VFX plays
- ๐ Wire cutting sound
- ๐ Console:
[Player] Cutting Green Wire... - ๐ Console:
Wrong wire! The clock is still ticking... - โฑ๏ธ Timer continues counting down
- ๐ฃ Event remains in Pending State
Result: Nothing changes. The countdown continues.
- After a few seconds: BOOM! (same as Option A)
- You get to feel the tension of making the wrong choice
Why: The code checks if (color == _safeWireColor), and since it's false, .Cancel() is never called. The scheduler keeps running.
Option C: Cut the Correct Wire (Defuse)โ
Action: Click the button matching the safe wire.
Example: If Console said The SAFE wire is: Red, click "Cut RedWire"
What Happens:
- โก Wire sparks VFX plays
- ๐ Wire cutting sound
- ๐ Console:
[Player] Cutting Red Wire... - ๐ฏ CRITICAL:
explodeEvent.Cancel()is called - โฑ๏ธ Timer stops immediately at current value (e.g.,
03.247) - ๐ Console:
BOMB DEFUSED! Event Cancelled. - โ Timer text changes to "DEFUSED" in green
- ๐ Defuse success sound plays
- ๐ฃ Event removed from Pending State
Result: ๐ข SUCCESS - No Explosion
- The bomb is safe
DelayedEventReceiver.OnExplode()is NEVER CALLED- You can arm the bomb again for another round
Why: .Cancel() removes the scheduled event from the GameEventManager's internal queue. When the 5-second timer would have elapsed, there's nothing to execute.
๐๏ธ Scene Architectureโ
The Scheduling Systemโ
Delayed events use an internal timer managed by the GameEventManager:
๐ Initiation: Raise()
โ
๐ฆ [ Queue Event + Start Timer ]
โ
โณ Status: Waiting...
โ
โโ โก Execution Path (Timer Expired)
โ โโโบ โ
Execute() โ Logic Invoked
โ
โโ ๐ Interruption Path (Manual/Condition)
โโโบ ๐งน Cancel() โ [ Removed from Queue ]
Key Concepts:
- Pending State: Between
Raise()and execution - Scheduler Queue: Internal list of timed events
- Cancellation: Removes event from queue before execution
- Atomic Operation: If canceled, receiver method never runs
Event Definitionโ

| Event Name | Type | Configured Delay |
|---|---|---|
onExplodeEvent | GameEvent (void) | 5.0 seconds |
Behavior Configuration with Delayโ
Click the (void) icon in the Behavior column to open the Behavior Window:

Schedule Configuration Section:
-
โฑ๏ธ Action Delay:
5seconds- This is the time gap between
Raise()and execution - Configurable per-event in the Editor
- No code changes needed to adjust timing
- This is the time gap between
-
๐ Repeat Interval:
0(disabled) -
๐ข Repeat Count:
Infinite Loop(not used in this demo) -
๐พ Persistent Event: Unchecked
Event Action:
- Method:
DelayedEventReceiver.OnExplode() - Mode: Runtime Only
Want to make the bomb countdown faster or slower? Just change the Action Delay value in this window. Try 3 for harder difficulty or 10 for easier!
Sender Setup (DelayedEventRaiser)โ
Select the DelayedEventRaiser GameObject:

Event Channels:
Explode Event:onExplodeEvent- Tooltip: "Configuration: Start Delay = 5.0 seconds"
References:
Bomb Receiver: DelayedEventReceiver (for callback coordination)
Visuals:
Timer Text: TimerText (TextMeshPro) - displays countdownSparks VFX: WireSparksVFX (Particle System) - wire cutting effect
Receiver Setup (DelayedEventReceiver)โ
Select the DelayedEventReceiver GameObject:

References:
Bomb Raiser: DelayedEventRaiser (for state callback)Bomb Rigidbody: TimeBomb_TNT (Rigidbody) - for explosion physics
Visuals:
Explosion VFX Prefab: BombExplosionVFX (Particle System)
Audio:
Tick Clip: BeepSFX (tick sound every second)Explosion Clip: BoomSFX (explosion sound)Defuse Clip: DefuseSFX (success sound)
๐ป Code Breakdownโ
๐ค DelayedEventRaiser.cs (Sender)โ
using UnityEngine;
using TinyGiants.GameEventSystem.Runtime;
using System.Collections;
public class DelayedEventRaiser : MonoBehaviour
{
[Header("Event Channels")]
[Tooltip("Configuration: Start Delay = 5.0 seconds.")]
[GameEventDropdown] public GameEvent explodeEvent;
private bool _isArmed;
private float _countDownTime = 5.0f;
private string _safeWireColor; // Randomized each round
/// <summary>
/// Button Action: Arms the bomb and starts the delayed event.
/// </summary>
public void ArmBomb()
{
if (_isArmed || explodeEvent == null) return;
_isArmed = true;
// Randomize the puzzle solution
_safeWireColor = Random.value > 0.5f ? "Red" : "Green";
Debug.Log($"[Game Logic] Bomb Armed! The SAFE wire is: " +
$"<color={_safeWireColor.ToLower()}>{_safeWireColor}</color>");
// CRITICAL: Raise the delayed event
// This does NOT execute immediately!
// The event enters "Pending State" for 5 seconds
explodeEvent.Raise();
// Start cosmetic countdown (visual only)
StartCoroutine(CountdownRoutine());
}
/// <summary>
/// Button Action: Player attempts to cut the Red wire.
/// </summary>
public void CutRedWire() => ProcessCut("Red");
/// <summary>
/// Button Action: Player attempts to cut the Green wire.
/// </summary>
public void CutGreenWire() => ProcessCut("Green");
private void ProcessCut(string color)
{
if (!_isArmed) return;
Debug.Log($"[Player] Cutting {color} Wire...");
// Play wire cutting VFX...
// CRITICAL DECISION POINT
if (color == _safeWireColor)
{
// THE MAGIC: Cancel the pending event
// This removes it from the scheduler's queue
// OnExplode() will NEVER be called
explodeEvent.Cancel();
DisarmSuccess();
}
else
{
// Wrong wire - event remains pending
Debug.LogWarning("Wrong wire! The clock is still ticking...");
}
}
private void DisarmSuccess()
{
_isArmed = false;
StopAllCoroutines(); // Stop visual countdown
// Update UI to show success...
Debug.Log("<color=green>BOMB DEFUSED! Event Cancelled.</color>");
}
private IEnumerator CountdownRoutine()
{
// This is PURELY COSMETIC
// The real timer is managed by GameEventManager's scheduler
// Even if this coroutine stops, the bomb would still explode
float _currentTimer = _countDownTime;
while (_currentTimer > 0)
{
_currentTimer -= Time.deltaTime;
if (_currentTimer < 0) _currentTimer = 0;
// Update visual timer text
if (timerText)
{
timerText.text = _currentTimer.ToString("00.000");
// Color lerp from orange to red for urgency
float urgency = 1f - (_currentTimer / _countDownTime);
timerText.color = Color.Lerp(new Color(1f, 0.5f, 0f),
Color.red, urgency);
}
yield return null;
}
}
}
Key Points:
- ๐ฏ Separation of Concerns - Visual timer (coroutine) vs Logic timer (scheduler)
- ๐ฒ Random Selection -
_safeWireColordetermined each round - ๐ด Cancel API -
.Cancel()removes pending event from queue - โฑ๏ธ Cosmetic Countdown - UI updates independently of event system
๐ฅ DelayedEventReceiver.cs (Listener)โ
using UnityEngine;
using System.Collections;
public class DelayedEventReceiver : MonoBehaviour
{
[SerializeField] private Rigidbody bombRigidbody;
[SerializeField] private ParticleSystem explosionVFXPrefab;
private AudioSource _audioSource;
private Camera _mainCamera;
/// <summary>
/// [Event Callback - Delayed Execution]
///
/// This method is ONLY called if:
/// 1. explodeEvent.Raise() was called
/// 2. 5 seconds elapsed
/// 3. explodeEvent.Cancel() was NOT called during that time
///
/// If the correct wire is cut, this method never runs.
/// </summary>
public void OnExplode()
{
Debug.Log("<color=red><b>BOOM! The event executed.</b></color>");
// Spawn explosion VFX
if (explosionVFXPrefab != null)
{
ParticleSystem vfx = Instantiate(explosionVFXPrefab,
transform.position,
Quaternion.identity);
vfx.Play();
Destroy(vfx.gameObject, 3.0f);
}
// Enable physics on bomb
if (bombRigidbody)
{
bombRigidbody.isKinematic = false;
// Apply explosion force (launches bomb upward)
bombRigidbody.AddExplosionForce(2000f,
transform.position + Vector3.down * 0.5f,
5f);
bombRigidbody.AddTorque(Random.insideUnitSphere * 100f,
ForceMode.Impulse);
}
// Audio + Camera shake
if (explosionClip) _audioSource.PlayOneShot(explosionClip);
StartCoroutine(ShakeCamera(0.5f, 0.8f));
}
private IEnumerator ShakeCamera(float duration, float magnitude)
{
if (_mainCamera == null) yield break;
Vector3 originalPos = _mainCamera.transform.position;
float elapsed = 0f;
while (elapsed < duration)
{
float x = Random.Range(-1f, 1f) * magnitude;
float y = Random.Range(-1f, 1f) * magnitude;
_mainCamera.transform.position = originalPos + new Vector3(x, y, 0);
elapsed += Time.deltaTime;
yield return null;
}
_mainCamera.transform.position = originalPos;
}
}
Key Points:
- ๐ฏ Conditional Execution - Only runs if not canceled
- ๐ฅ Explosion Logic - VFX, physics, audio, camera shake
- ๐ฌ Pure Reaction - No knowledge of timers or cancellation
- โฑ๏ธ Delayed Invocation - Called 5 seconds after
Raise()(if not canceled)
๐ Key Takeawaysโ
| Concept | Implementation |
|---|---|
| โฑ๏ธ Action Delay | Configure execution delay in Behavior Window (no code) |
| ๐ Pending State | Events wait in scheduler queue between Raise and Execute |
| ๐ด Cancellation API | .Cancel() removes event from queue before execution |
| ๐ฏ Atomic Execution | Canceled events never invoke receiver methods |
| ๐จ Visual vs Logic | Separate cosmetic timers from event system timers |
Delayed events are perfect for:
- Timed abilities - Cooldowns, cast times, channeling
- Countdown mechanics - Bombs, buffs expiring, reinforcements arriving
- Cancelable actions - Interrupt casting, defuse mechanics
- Turn-based delays - Wait for animation before next action
- Scheduled events - Day/night cycle triggers, periodic spawns
The .Cancel() API is critical for interactive gameplayโletting players interrupt dangerous actions adds tension and player agency!
๐ฏ What's Next?โ
You've mastered delayed execution and cancellation. Now let's explore repeating events for periodic behavior.
Next Chapter: Learn about repeat intervals in 08 Repeating Event
๐ Related Documentationโ
- Game Event Behavior - Complete guide to schedule configuration
- Raising and Scheduling - API reference for
.Raise()and.Cancel() - Best Practices - Patterns for timed gameplay mechanics