using System.Collections; using System.Collections.Generic; using Convai.Scripts.Runtime.UI; using UnityEngine; namespace Convai.Scripts.Runtime.Addons { /// /// This class is responsible for controlling the UI notifications in the game. /// It handles the creation, activation, deactivation, and animation of notifications. /// public class UINotificationController : MonoBehaviour { /// /// Maximum number of notifications that can be displayed at the same time. /// private const int MAX_NUMBER_OF_NOTIFICATION_AT_SAME_TIME = 3; /// /// References to the UI notification prefab and other necessary components. /// [Header("References")] [SerializeField] private UINotification _uiNotificationPrefab; /// /// Spacing between Notifications /// [Header("Configurations")] [SerializeField] private int _spacing = 100; /// /// Position for Active Notification /// [Tooltip("Starting position for the first notification; Y value adjusts sequentially for each subsequent notification.")] [SerializeField] private Vector2 _activeNotificationPos; /// /// Position for Deactivated Notification /// [SerializeField] private Vector2 _deactivatedNotificationPos; [Header("UI Notification Animation Values")] [SerializeField] private float _activeDuration = 4f; [SerializeField] private float _slipDuration = 0.3f; [SerializeField] private float _delay = 0.3f; [SerializeField] private AnimationCurve _slipAnimationCurve; private readonly float _fadeInDuration = 0.35f; private readonly float _fadeOutDuration = 0.2f; /// /// List to keep track of the order in which pending notifications were requested. /// private readonly List _pendingNotificationsOrder = new(); private Queue _activeUINotifications; private CanvasGroup _canvasGroup; private Queue _deactivatedUINotifications; private FadeCanvas _fadeCanvas; /// /// Flag indicating whether a UI notification movement animation is currently in progress. /// Used to prevent overlapping animation coroutines for UI notifications. /// private bool _isNotificationAnimationInProgress; /// /// Awake is called when the script instance is being loaded. /// It is used to initialize any variables or game state before the game starts. /// private void Awake() { // Get necessary components and initialize UI notifications. _canvasGroup = GetComponent(); _fadeCanvas = GetComponent(); InitializeUINotifications(); } /// /// This function is called when the object becomes enabled and active. /// It is used to subscribe to the OnNotificationRequested event. /// private void OnEnable() { NotificationSystemHandler.Instance.OnNotificationRequested += NotificationSystemHandler_OnNotificationRequested; } /// /// This function is called when the behaviour becomes disabled or inactive. /// It is used to unsubscribe from the OnNotificationRequested event. /// private void OnDisable() { NotificationSystemHandler.Instance.OnNotificationRequested -= NotificationSystemHandler_OnNotificationRequested; } /// /// Handles a new notification request by adding it to the order list and attempting to initialize it. /// If a notification animation is already in progress, waits for it to complete before processing the new request. /// /// The requested SONotification to be processed. private void NotificationSystemHandler_OnNotificationRequested(SONotification SONotification) { // Add the requested notification to the order list and try to initialize it. _pendingNotificationsOrder.Add(SONotification); // If a notification animation is already in progress, wait for it to complete before processing the new request. if (_isNotificationAnimationInProgress) return; // If initialization fails, return if (TryInitializeNewNotification(SONotification, out UINotification uiNotification) == false) return; // Start the coroutine for UI notification animations StartNotificationUICoroutine(uiNotification); } /// /// This function is used to initialize the UI notifications. /// It initializes the queues for active and deactivated UI notifications and instantiates and enqueues deactivated UI /// notifications. /// private void InitializeUINotifications() { // Initialize the queues for active and deactivated UI notifications. _activeUINotifications = new Queue(); _deactivatedUINotifications = new Queue(); // Instantiate and enqueue deactivated UI notifications. for (int i = 0; i < MAX_NUMBER_OF_NOTIFICATION_AT_SAME_TIME; i++) { UINotification uiNotification = Instantiate(_uiNotificationPrefab, transform); // Initialize Position uiNotification.NotificationRectTransform.anchoredPosition = _deactivatedNotificationPos; _deactivatedUINotifications.Enqueue(uiNotification); } } /// /// Attempts to initialize a new UI notification using the provided SONotification. /// Tries to get an available UI notification and initializes it with the given SONotification. /// /// The SONotification to be used for initializing the UI notification. /// The initialized UINotification if successful, otherwise null. /// True if initialization is successful, false otherwise. private bool TryInitializeNewNotification(SONotification SONotification, out UINotification uiNotification) { // Try to get an available UI notification and initialize it with the given SONotification. uiNotification = GetAvailableUINotification(); if (uiNotification == null) return false; uiNotification.Initialize(SONotification); return true; } /// /// Initiates the coroutine for UI notification animations and adds the notification to the active queue. /// /// The UINotification to be animated and added to the active queue. private void StartNotificationUICoroutine(UINotification uiNotification) { // Define additional delay for smoother notification end transition float extraDelayForNotificationEndTransition = 0.5f; // Calculate the total duration including fadeIn, activeDuration, slipDuration (for both activation and deactivation), delay, and extra delay float totalAnimationDuration = _fadeInDuration + _activeDuration + 2 * _slipDuration + _delay + extraDelayForNotificationEndTransition; // Start the fade animation for the canvas group _fadeCanvas.StartFadeInFadeOutWithGap(_canvasGroup, _fadeInDuration, _fadeOutDuration, totalAnimationDuration); // Enqueue the notification to the active queue _activeUINotifications.Enqueue(uiNotification); // Start the coroutine for individual UI notification animations StartCoroutine(StartNotificationUI(uiNotification)); } /// /// Coroutine for managing the lifecycle of a UI notification, including its activation, display duration, and /// deactivation. /// /// The UINotification to be managed. private IEnumerator StartNotificationUI(UINotification uiNotification) { // Remove the notification from the pending list int firstIndex = 0; _pendingNotificationsOrder.RemoveAt(firstIndex); // Move to the active position yield return MoveUINotificationToActivePosition(uiNotification); // Wait for the active duration yield return new WaitForSeconds(_activeDuration); UpdateUINotificationPositions(); // Move to the hidden position yield return MoveUINotificationToHiddenPosition(uiNotification); // Deactivate the UI notification, update positions, and check for pending notifications. DeactivateAndEnqueueUINotification(uiNotification); // If there are pending notifications, initialize and start a new one if (AreTherePendingNotifications()) TryInitializeAndStartNewNotification(); // Update UI notification positions after the lifecycle is complete UpdateUINotificationPositions(); } /// /// Moves the UI notification to its active position. /// private IEnumerator MoveUINotificationToActivePosition(UINotification uiNotification) { float targetY = _activeNotificationPos.y - _spacing * (_activeUINotifications.Count - 1); Vector2 targetPos = new(_activeNotificationPos.x, targetY); yield return StartCoroutine(MoveUINotificationToTargetPos(uiNotification, targetPos)); } /// /// Moves the UI notification to its hidden position. /// private IEnumerator MoveUINotificationToHiddenPosition(UINotification uiNotification) { Vector2 targetPos = _deactivatedNotificationPos; yield return StartCoroutine(MoveUINotificationToTargetPos(uiNotification, targetPos)); } /// /// Deactivates the UI notification, updates positions, and enqueues it for later use. /// private void DeactivateAndEnqueueUINotification(UINotification uiNotification) { uiNotification.SetActive(false); _activeUINotifications.Dequeue(); _deactivatedUINotifications.Enqueue(uiNotification); UpdateUINotificationPositions(); } /// /// Checks if there are pending notifications and initializes and starts a new one if available. /// private void TryInitializeAndStartNewNotification() { if (TryInitializeNewNotification(_pendingNotificationsOrder[0], out UINotification newUiNotification)) StartNotificationUICoroutine(newUiNotification); } /// /// Smoothly moves the UI notification to the target position over a specified duration. /// /// The UINotification to be moved. /// The target position to move the UINotification to. private IEnumerator MoveUINotificationToTargetPos(UINotification uiNotification, Vector2 targetPos) { // Set flag to indicate that a notification animation is in progress _isNotificationAnimationInProgress = true; float elapsedTime = 0f; Vector2 startPos = uiNotification.NotificationRectTransform.anchoredPosition; // Move the UI notification smoothly to the target position over the specified duration while (elapsedTime <= _slipDuration + _delay) { elapsedTime += Time.deltaTime; float percent = Mathf.Clamp01(elapsedTime / _slipDuration); float curvePercent = _slipAnimationCurve.Evaluate(percent); uiNotification.NotificationRectTransform.anchoredPosition = Vector2.Lerp(startPos, targetPos, curvePercent); yield return null; } // Reset the flag once the animation is complete _isNotificationAnimationInProgress = false; } /// /// Updates the positions of active UI notifications along the Y-axis. /// private void UpdateUINotificationPositions() { float targetX = _activeNotificationPos.x; float targetY = _activeNotificationPos.y; // Iterate through active UI notifications and move them to their respective positions foreach (UINotification activeUINotification in _activeUINotifications) { Vector2 targetPos = new(targetX, targetY); StartCoroutine(MoveUINotificationToTargetPos(activeUINotification, targetPos)); targetY -= _spacing; } } /// /// Gets an available UI notification from the deactivated queue. /// /// The available UI notification, or null if the deactivated queue is empty. private UINotification GetAvailableUINotification() { // Check if there are available deactivated UI notifications if (_deactivatedUINotifications.Count == 0) return null; // Dequeue and return an available UI notification return _deactivatedUINotifications.Dequeue(); } /// /// Checks if there are pending notifications in the order list. /// /// True if there are pending notifications, false otherwise. private bool AreTherePendingNotifications() { // Check if there are any pending notifications in the order list return _pendingNotificationsOrder.Count >= 1; } } }