Skip to content

Notification System

Overview

The Notification Service provides a centralized and flexible system for managing and dispatching in-game notifications. It allows various game systems to send notifications and other systems to subscribe and react to them based on category or specific ID. This framework is designed to be robust, easy to use, and integrated with the LoL Engine's core services like the Event Manager.

It supports different notification priorities, categories, custom data payloads, and automatic dismissal, making it suitable for a wide range of use cases from simple system messages to complex gameplay alerts.

Quick Start

  1. Enable Enable Notification Service in your ServiceConfiguration asset. ImprovedGameInitializer registers the service and creates the NotificationHelper automatically.
  2. Send a notification from any MonoBehaviour via the extension method:
using LoLEngine.Core.Notifications.Extensions;
using LoLEngine.Core.Notifications.Service;

this.SendNotification("Level Up!", "You reached level 5!",
    NotificationCategory.Gameplay, NotificationPriority.High);
  1. React by subscribing to a category or ID — see the Usage Examples below.

Core Components

INotificationService (Interfaces/INotificationService.cs)

The main interface defining the contract for the notification system. It outlines methods for sending, subscribing, managing, and configuring notifications.

NotificationService (Service/NotificationService.cs)

The concrete implementation of INotificationService. It handles the logic for: - Storing subscribers (by category and ID). - Managing active notifications. - Maintaining a history of notifications. - Dispatching notifications to subscribers. - Integrating with IEventManager to broadcast global notification events. - Handling notification categories and their enabled states. - Scheduling auto-dismissal of notifications.

INotification (Interfaces/INotification.cs)

Interface representing a single notification. It defines properties like Id, Title, Message, Priority, Category, Data, Timestamp, Duration, and IsPersistent.

Notification (Data/Notification.cs)

The default concrete implementation of INotification. It provides constructors for creating new notifications and manages their data. Custom notification types should typically inherit from this class or implement INotification.

NotificationCategory (Providers/NotificationCategory.cs)

An enum defining different categories for notifications (e.g., UI, System, Gameplay, Level, Combat, Achievement, Error). This allows for broad-stroke subscription and filtering.

NotificationPriority (Providers/NotificationPriority.cs)

An enum defining the priority levels for notifications (e.g., Low, Normal, High, Critical). This can be used by UI systems to display notifications differently.

NotificationEvents (Events/NotificationEvents.cs)

A static class defining global game events related to notifications, which are broadcast via the IEventManager: - NotificationSent: Fired when a notification is successfully sent. - NotificationDismissed: Fired when a notification is manually dismissed. - NotificationExpired: Fired when a notification's duration ends and it's auto-dismissed.

NotificationExtensions (Extensions/NotificationExtensions.cs)

Provides convenient extension methods for MonoBehaviours to easily send and subscribe/unsubscribe to notifications without needing to directly fetch the INotificationService instance every time.

NotificationHelper (Service/NotificationService.cs)

A simple MonoBehaviour singleton used by the NotificationService to run coroutines, specifically for auto-dismissing notifications after their specified duration.

Key Features

  • Flexible Sending: Send notifications by providing full INotification objects or by specifying individual parameters (title, message, category, etc.).
  • Targeted Subscriptions:
    • Subscribe to all notifications of a specific NotificationCategory.
    • Subscribe to notifications with a specific Id (useful for unique, identifiable events).
  • Active Notification Management:
    • Retrieve a list of currently active (non-dismissed, non-expired) notifications.
    • Clear active notifications, optionally filtered by category.
    • Manually dismiss a specific notification by its ID.
  • Notification History:
    • Maintains a configurable history of recently sent notifications.
    • Retrieve a portion of the history.
  • Category Management:
    • Enable or disable entire notification categories at runtime. Notifications sent to a disabled category will be ignored.
  • Auto-Dismissal:
    • Notifications with a Duration > 0 and IsPersistent = false will automatically be dismissed after their duration. This is handled via the NotificationHelper coroutine.
  • Custom Data Payloads: Attach arbitrary data to notifications using a Dictionary<string, object>.
  • Event System Integration: Global events (NotificationSent, NotificationDismissed, NotificationExpired) are broadcast, allowing any system to listen and react without direct subscription to the NotificationService.
  • Thread Safety Considerations: Uses ConcurrentDictionary for subscriber and active notification collections, providing a degree of thread safety for dictionary operations. List modifications within these dictionaries are not inherently thread-safe if accessed from multiple threads simultaneously (primarily a concern if extending the system for multi-threaded send/subscribe).
  • Ease of Use: Extension methods simplify common operations for MonoBehaviour scripts.

Setup and Registration

To use the NotificationService, it needs to be instantiated and registered with your ServiceLocator (or equivalent dependency injection system). The NotificationHelper also needs to be present in the scene.

Example Registration (in your bootstrapper, or via ImprovedGameInitializer):

Note: With ImprovedGameInitializer, the NotificationService is automatically registered when enabled in ServiceConfiguration. The NotificationHelper is also automatically created during initialization. You typically don't need to manually register the service.

// In ServiceConfiguration ScriptableObject
Enable Notification Service: [x]

For custom initialization or manual setup:

using LoLEngine.Core.Events.Interfaces;
using LoLEngine.Core.Notifications.Interfaces;
using LoLEngine.Core.Notifications.Service;
using LoLEngine.Core.ServiceManagement.Service;
using UnityEngine;

public class CustomNotificationSetup : MonoBehaviour
{
    protected void RegisterNotificationSystem()
    {
        // Ensure IEventManager is already registered
        var eventManager = ServiceLocator.Instance.Get<IEventManager>();
        if (eventManager == null)
        {
            Debug.LogError("IEventManager not found. NotificationService requires it.");
            return;
        }

        // Create and register notification service
        var notificationService = new NotificationService(eventManager);
        ServiceLocator.Instance.Register<INotificationService>(notificationService);
        notificationService.Initialize();

        // Create NotificationHelper for coroutines (auto-dismissal)
        if (NotificationHelper.Instance == null)
        {
            GameObject helperObj = new GameObject("NotificationHelper");
            helperObj.AddComponent<NotificationHelper>();
            DontDestroyOnLoad(helperObj); // Persist across scenes
        }

        Debug.Log("NotificationService registered and NotificationHelper created.");
    }
}
Note: Ensure the NotificationHelper GameObject is set up to persist (e.g., DontDestroyOnLoad) if your notifications or the service itself need to function across scene loads.

Usage Examples

Sending Notifications

From a MonoBehaviour (using extension methods):

using LoLEngine.Core.Notifications.Extensions;
using LoLEngine.Core.Notifications.Service;
using UnityEngine;
using System.Collections.Generic;

public class GameplayManager : MonoBehaviour
{
    public void PlayerLeveledUp(int newLevel)
    {
        // Simple notification
        this.SendNotification("Level Up!", $"You reached level {newLevel}!", NotificationCategory.Gameplay, NotificationPriority.High);
    }

    public void QuestCompleted(string questName)
    {
        // Notification with custom data and duration
        var data = new Dictionary<string, object> { { "questId", questName }, { "rewardGold", 100 } };
        this.SendNotification(
            title: "Quest Complete",
            message: $"You completed: {questName}",
            category: NotificationCategory.Gameplay,
            priority: NotificationPriority.Normal,
            duration: 5f, // Auto-dismiss after 5 seconds
            data: data
        );
    }

    public void SendCustomNotificationObject()
    {
        // Using a pre-defined custom notification type (see Creating Custom Notification Types below)
        // var customNotification = new MyCustomNotification(...);
        // this.SendNotification(customNotification);
    }
}

Directly via the service instance:

var notificationService = ServiceLocator.Instance.Get<INotificationService>();
notificationService.Send("System Alert", "Maintenance soon.", NotificationCategory.System, NotificationPriority.Critical);

Subscribing to Notifications

From a MonoBehaviour (using extension methods):

using LoLEngine.Core.Notifications.Extensions;
using LoLEngine.Core.Notifications.Interfaces;
using LoLEngine.Core.Notifications.Service;
using UnityEngine;

public class UIManager : MonoBehaviour
{
    void Start()
    {
        // Subscribe to all Gameplay notifications
        this.SubscribeToNotifications(NotificationCategory.Gameplay, HandleGameplayNotification);

        // Subscribe to a specific system notification by ID (if known)
        // var notificationService = ServiceLocator.Instance.Get<INotificationService>();
        // notificationService.Subscribe("SpecificSystemEventID", HandleSpecificNotification);
    }

    private void HandleGameplayNotification(INotification notification)
    {
        Debug.Log($"[UI] Gameplay Notification: {notification.Title} - {notification.Message}");
        // Update UI elements based on notification.Category, notification.Id, or notification.Data
        if (notification.Data.TryGetValue("rewardGold", out object goldValue))
        {
            Debug.Log($"Player received {goldValue} gold!");
        }
    }

    private void HandleSpecificNotification(INotification notification)
    {
        Debug.Log($"[UI] Specific Notification Received: {notification.Title}");
    }

    void OnDestroy()
    {
        // IMPORTANT: Always unsubscribe to prevent memory leaks and errors
        this.UnsubscribeFromNotifications(NotificationCategory.Gameplay, HandleGameplayNotification);
        // var notificationService = ServiceLocator.Instance.Get<INotificationService>();
        // notificationService?.Unsubscribe("SpecificSystemEventID", HandleSpecificNotification);
    }
}

Listening to Global Notification Events

Any system can listen to the global notification events broadcast by the IEventManager.

using LoLEngine.Core.Events.Interfaces;
using LoLEngine.Core.Events.Providers;
using LoLEngine.Core.Notifications.Events;
using UnityEngine;

public class GlobalNotificationLogger : MonoBehaviour,
    IEventListener<NotificationEvents.NotificationSent>
{
    void OnEnable()  => this.EventStartListening<NotificationEvents.NotificationSent>();
    void OnDisable() => this.EventStopListening<NotificationEvents.NotificationSent>();

    public void OnGameEvent(NotificationEvents.NotificationSent gameEvent)
    {
        Debug.Log($"Global Event: Notification Sent - ID: {gameEvent.Notification.Id}, Title: {gameEvent.Notification.Title}");
    }
}

Creating Custom Notification Types

You can create specialized notification classes by inheriting from Notification or implementing INotification directly. This is useful for strongly-typed data payloads.

Example PlayerDamageNotification.cs:

// In a new file, e.g., PlayerDamageNotification.cs
using LoLEngine.Core.Notifications.Data;
using LoLEngine.Core.Notifications.Service;
using System.Collections.Generic;

public class PlayerDamageNotification : Notification
{
    public float DamageAmount { get; }
    public string DamageSource { get; }

    public PlayerDamageNotification(float damageAmount, string damageSource, bool criticalHit)
        : base(
            title: criticalHit ? "Critical Hit!" : "Damage Taken",
            message: $"You took {damageAmount} damage from {damageSource}.",
            category: NotificationCategory.Gameplay,
            priority: criticalHit ? NotificationPriority.High : NotificationPriority.Normal,
            duration: 2f // Show for 2 seconds
          )
    {
        DamageAmount = damageAmount;
        DamageSource = damageSource;

        // Add to custom data payload
        Data["damageAmount"] = damageAmount;
        Data["damageSource"] = damageSource;
        Data["isCritical"] = criticalHit;
    }
}

Sending the custom notification:

// In some combat script
var damageNotification = new PlayerDamageNotification(25f, "Goblin Archer", false);
this.SendNotification(damageNotification); // Using MonoBehaviour extension

Advanced Usage & Extensibility

The NotificationSystem sample (see Samples~/NotificationSystem/README.md) provides examples of more advanced scenarios: - UI Integration: Displaying popups, updating health bars from your UI controllers. - Gameplay System Integration: Reacting to gameplay conditions, sending damage/power-up notifications. - Level Flow Integration: Broadcasting level-wide conditions. - Performance-Optimized Notification Queue: For scenarios with very high notification throughput. - Configuration-Driven Service: Extending NotificationService to be configured by a ScriptableObject. - Animated UI Displays: Creating more dynamic visual feedback for notifications. - Editor Debug Tools: For testing and inspecting notifications during development.

Refer to that document for detailed code examples of these advanced patterns.

Best Practices

  • Unsubscribe: Always unsubscribe from notifications in OnDestroy (or when the listener is no longer needed) to prevent memory leaks and errors.
  • Use Categories and Priorities: Leverage categories and priorities to allow for flexible filtering and differentiated UI treatment.
  • Specific IDs for Unique Events: Use the ID-based subscription for notifications that are unique and require a very specific handler.
  • Custom Data for Context: Use the Data dictionary to pass along any relevant contextual information with your notifications.
  • Global Events for Broad Impact: Use the IEventManager integration if many unrelated systems need to be aware of notification lifecycle events without direct coupling.
  • Keep Payloads Lean: While flexible, avoid putting excessively large objects directly into the Data dictionary if performance is critical. Consider passing IDs or references instead.
  • NotificationHelper Persistence: Ensure NotificationHelper.Instance.gameObject is configured with DontDestroyOnLoad(gameObject) if notifications need to auto-dismiss across scene loads.

Troubleshooting

Notifications are never received - Confirm Enable Notification Service is on in ServiceConfiguration, and resolve via the interface: ServiceLocator.Instance.Get<INotificationService>(). - The target NotificationCategory may be disabled at runtime — notifications sent to a disabled category are ignored.

Auto-dismiss (duration) doesn't fire - The NotificationHelper MonoBehaviour runs the dismissal coroutine. With ImprovedGameInitializer it is created automatically; in a custom bootstrap, ensure it exists and persists (DontDestroyOnLoad).

See Also

This Notification Service provides a powerful and adaptable foundation for handling various types of in-game communication.