Skip to main content

Events

AirPath uses an event-driven architecture for communication between components. This allows loose coupling — components don't need direct references to each other.

Namespace

PlatypusIdeas.AirPath.Runtime.Events

Event Bus Overview

The event system consists of three parts:

  1. PathfindingEventBus — A ScriptableObject asset that manages subscriptions and event delivery
  2. EventBusUpdateHandler — A MonoBehaviour that processes the event queue each frame
  3. Event Classes — Data objects representing different events

Setup

The event bus must be set up before use:

  1. Create the asset: Create → EventSystem → PathfindingEventBus
  2. Add EventBusUpdateHandler to your scene
  3. Assign the asset to the handler's Event Bus Asset field

See Custom Minimal Setup for detailed instructions.

Subscribing to Events

using PlatypusIdeas.AirPath.Runtime.Events;
using UnityEngine;

public class PathListener : MonoBehaviour
{
private void Start()
{
// Subscribe using extension method
this.Subscribe<PathCalculatedEvent>(OnPathCalculated);
}

private void OnDestroy()
{
// Always unsubscribe when destroyed
EventBusExtensions.Unsubscribe<PathCalculatedEvent>(OnPathCalculated);
}

private void OnPathCalculated(PathCalculatedEvent evt)
{
if (evt.Success)
{
Debug.Log($"Path calculated with {evt.WorldPath.Count} waypoints");
}
}
}

Using the Event Bus Directly

private void Start()
{
if (PathfindingEventBus.HasInstance)
{
PathfindingEventBus.Instance.Subscribe<PathCalculatedEvent>(OnPathCalculated, this);
}
}

private void OnDestroy()
{
if (PathfindingEventBus.HasInstance)
{
PathfindingEventBus.Instance.Unsubscribe<PathCalculatedEvent>(OnPathCalculated);
}
}

Priority Subscriptions

Higher priority handlers are called first:

// Called first (priority 10)
this.Subscribe<PathCalculatedEvent>(OnPathCalculatedHighPriority, priority: 10);

// Called second (priority 0, default)
this.Subscribe<PathCalculatedEvent>(OnPathCalculatedNormal);

Publishing Events

using PlatypusIdeas.AirPath.Runtime.Events;

// Using extension method
EventBusExtensions.Publish(new PathCalculatedEvent(result, worldPath, this));

// Using instance directly
if (PathfindingEventBus.HasInstance)
{
PathfindingEventBus.Instance.Publish(new PathCalculatedEvent(result, worldPath, this));
}

Event Reference

All events inherit from PathfindingEventBase:

public abstract class PathfindingEventBase
{
public float Timestamp { get; } // Time.time when event was created
public object Sender { get; } // Object that published the event
}

PathRequestedEvent

Fired when a path calculation is requested.

public class PathRequestedEvent : PathfindingEventBase
{
public PathRequest Request { get; }
public PathfindingModeType RequestingMode { get; }
}
PropertyTypeDescription
RequestPathRequestContains start/end positions, height offset, request ID
RequestingModePathfindingModeTypeMouseClick or TargetFollow

Example:

this.Subscribe<PathRequestedEvent>(evt =>
{
Debug.Log($"Path requested from {evt.RequestingMode} mode");
});

PathCalculatedEvent

Fired when a path calculation completes (success or failure).

public class PathCalculatedEvent : PathfindingEventBase
{
public PathResult Result { get; }
public List<Vector3> WorldPath { get; }
public float CalculationTime { get; }
public bool Success { get; }
}
PropertyTypeDescription
ResultPathResultFull result object with metadata
WorldPathList<Vector3>Path waypoints in world space (null if failed)
CalculationTimefloatTime taken to calculate in milliseconds
SuccessboolWhether a valid path was found

Example:

this.Subscribe<PathCalculatedEvent>(evt =>
{
if (evt.Success)
{
Debug.Log($"Path found: {evt.WorldPath.Count} waypoints in {evt.CalculationTime}ms");

// Use the path
foreach (var waypoint in evt.WorldPath)
{
// Process waypoints...
}
}
else
{
Debug.LogWarning("No path found!");
}
});

PathfindingModeChangedEvent

Fired when switching between pathfinding modes.

public class PathfindingModeChangedEvent : PathfindingEventBase
{
public PathfindingModeType PreviousMode { get; }
public PathfindingModeType NewMode { get; }
public IPathfindingMode ModeInstance { get; }
}
PropertyTypeDescription
PreviousModePathfindingModeTypeMode before the change
NewModePathfindingModeTypeMode after the change
ModeInstanceIPathfindingModeInstance of the new mode

Example:

this.Subscribe<PathfindingModeChangedEvent>(evt =>
{
Debug.Log($"Mode changed: {evt.PreviousMode}{evt.NewMode}");

// Update UI based on new mode
UpdateModeUI(evt.NewMode);
});

BoundaryViolationEvent

Fired when a position is outside the pathfinding grid bounds.

public class BoundaryViolationEvent : PathfindingEventBase
{
public Vector2Int OriginalPosition { get; }
public Vector2Int ClampedPosition { get; }
public string ViolationType { get; }
public bool WasAutoClamped { get; }
}
PropertyTypeDescription
OriginalPositionVector2IntThe out-of-bounds position
ClampedPositionVector2IntPosition after clamping to valid bounds
ViolationTypestringDescription of the violation
WasAutoClampedboolWhether auto-clamping was applied

Example:

this.Subscribe<BoundaryViolationEvent>(evt =>
{
if (!evt.WasAutoClamped)
{
Debug.LogError($"Position {evt.OriginalPosition} is out of bounds!");
}
});

SwarmUpdateEvent

Fired for swarm-related updates.

public class SwarmUpdateEvent : PathfindingEventBase
{
public UpdateType Type { get; }
public Vector3 Position { get; }
public int BirdCount { get; }
public object AdditionalData { get; }

public enum UpdateType
{
BirdsSpawned,
BirdsStartedPath,
BirdReachedTarget,
AllBirdsReachedTarget,
SwarmPositionUpdated
}
}
PropertyTypeDescription
TypeUpdateTypeType of swarm update
PositionVector3Relevant position (e.g., swarm center)
BirdCountintNumber of agents involved
AdditionalDataobjectOptional extra data

Example:

this.Subscribe<SwarmUpdateEvent>(evt =>
{
switch (evt.Type)
{
case SwarmUpdateEvent.UpdateType.AllBirdsReachedTarget:
Debug.Log("All agents reached destination!");
break;

case SwarmUpdateEvent.UpdateType.SwarmPositionUpdated:
UpdateSwarmUI(evt.Position);
break;
}
});

VisualizationUpdateEvent

Fired when visualization state changes.

public class VisualizationUpdateEvent : PathfindingEventBase
{
public UpdateType Type { get; }
public object Data { get; }

public enum UpdateType
{
PathLineUpdated,
MarkersUpdated,
GridCellsUpdated,
DebugVisualizationToggled,
VisualizationCleared
}
}
PropertyTypeDescription
TypeUpdateTypeType of visualization update
DataobjectOptional data related to the update

ConfigurationChangedEvent

Fired when a configuration ScriptableObject changes.

public class ConfigurationChangedEvent : PathfindingEventBase
{
public ConfigurationBase Configuration { get; }
public string ConfigurationType { get; }
public bool RequiresRecalculation { get; }
}
PropertyTypeDescription
ConfigurationConfigurationBaseThe changed configuration asset
ConfigurationTypestringType name (e.g., "PathfindingConfiguration")
RequiresRecalculationboolWhether pathfinding grid needs rebuild

Example:

this.Subscribe<ConfigurationChangedEvent>(evt =>
{
if (evt.RequiresRecalculation)
{
Debug.Log($"{evt.ConfigurationType} changed - recalculating...");
}
});

PathfindingErrorEvent

Base class for error events.

public abstract class PathfindingErrorEvent : PathfindingEventBase
{
public string ErrorMessage { get; }
public Exception Exception { get; }
public bool IsCritical { get; }
}
PropertyTypeDescription
ErrorMessagestringHuman-readable error description
ExceptionExceptionThe exception if one occurred
IsCriticalboolWhether this error is critical

Event Bus Configuration

The PathfindingEventBus asset has these settings:

SettingDefaultDescription
Enable Event LoggingfalseLog all subscribe/publish operations
Process Events ImmediatelytrueProcess events immediately vs. queued
Max Events Per Frame10Max queued events processed per frame

Best Practices

Always Unsubscribe

private void OnDestroy()
{
// Prevents memory leaks and errors
EventBusExtensions.Unsubscribe<PathCalculatedEvent>(OnPathCalculated);
}

Check HasInstance

// Safe for scene transitions
if (PathfindingEventBus.HasInstance)
{
EventBusExtensions.Publish(myEvent);
}

Use Sender for Filtering

this.Subscribe<PathCalculatedEvent>(evt =>
{
// Only handle events from a specific source
if (evt.Sender == myPathfindingManager)
{
ProcessPath(evt.WorldPath);
}
});

Debug with Event Logging

Enable Event Logging on the PathfindingEventBus asset to see all event activity in the console.

See Also