restructure

This commit is contained in:
tom.hempel
2025-09-30 18:03:19 +02:00
parent 69b0c79692
commit 78e5dcd53e
4821 changed files with 762 additions and 417 deletions

View File

@ -0,0 +1,63 @@
using System;
using Convai.Scripts.Runtime.Core;
using Convai.Scripts.Runtime.UI;
using UnityEngine;
#if ENABLE_INPUT_SYSTEM
using Convai.Scripts.Runtime.LoggerSystem;
using UnityEngine.InputSystem;
#endif
namespace Convai.Scripts.Runtime.Addons
{
/// <summary>
/// Controls player input to trigger a notification if there is no active NPC available for conversation.
/// </summary>
public class ActiveNPCChecker : MonoBehaviour
{
#if ENABLE_INPUT_SYSTEM
/// <summary>
/// Subscribes to the talk key input action when the script starts.
/// </summary>
private void Start()
{
ConvaiInputManager.Instance.GetTalkKeyAction().started += ConvaiInputManager_TalkKeyActionStarted;
}
/// <summary>
/// Unsubscribes from the talk key input action when the script is destroyed.
/// </summary>
private void OnDestroy()
{
ConvaiInputManager.Instance.GetTalkKeyAction().started -= ConvaiInputManager_TalkKeyActionStarted;
}
/// <summary>
/// Handles the talk key action and triggers a notification if no active NPC is available.
/// </summary>
/// <param name="input">The input context of the talk key action.</param>
private void ConvaiInputManager_TalkKeyActionStarted(InputAction.CallbackContext input)
{
try
{
if (!input.action.WasPressedThisFrame() || UIUtilities.IsAnyInputFieldFocused() || ConvaiNPCManager.Instance.activeConvaiNPC == null ||
ConvaiNPCManager.Instance.CheckForNPCToNPCConversation(ConvaiNPCManager.Instance.activeConvaiNPC))
if (ConvaiNPCManager.Instance.activeConvaiNPC == null && ConvaiNPCManager.Instance.nearbyNPC == null)
NotificationSystemHandler.Instance.NotificationRequest(NotificationType.NotCloseEnoughForConversation);
}
catch (NullReferenceException)
{
ConvaiLogger.DebugLog("No active NPC available for conversation", ConvaiLogger.LogCategory.UI);
}
}
#elif ENABLE_LEGACY_INPUT_MANAGER
private void Update()
{
if (ConvaiInputManager.Instance.WasTalkKeyPressed())
{
if (ConvaiNPCManager.Instance.activeConvaiNPC == null)
NotificationSystemHandler.Instance.NotificationRequest(NotificationType.NotCloseEnoughForConversation);
}
}
#endif
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bd477455ea76a6c46b64614d87aa55b9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,94 @@
using System.Collections;
using Convai.Scripts.Runtime.LoggerSystem;
using UnityEngine;
namespace Convai.Scripts.Runtime.Addons
{
public class MicrophoneInputChecker : MonoBehaviour
{
// Duration for microphone input check.
private const float INPUT_CHECK_DURATION = 3f;
// Microphone sensitivity, adjust as needed.
private const float SENSITIVITY = 10f;
// Threshold level to detect microphone issues.
private const float THRESHOLD = 0.1f;
// Reference to the TalkButtonDurationChecker script to check the talk button status.
private TalkButtonDurationChecker _talkButtonDurationChecker;
private void Awake()
{
// Find and assign the TalkButtonDurationChecker instance in the scene.
_talkButtonDurationChecker = FindObjectOfType<TalkButtonDurationChecker>();
}
/// <summary>
/// Check if the microphone is working by analyzing the provided AudioClip.
/// </summary>
/// <param name="audioClip">The audio clip to analyze.</param>
public void IsMicrophoneWorking(AudioClip audioClip)
{
// Stop any existing coroutines to ensure a clean start.
StopAllCoroutines();
// Start the coroutine to check the microphone device.
StartCoroutine(CheckMicrophoneDevice(audioClip));
}
// Coroutine to check the microphone device after a specified duration.
private IEnumerator CheckMicrophoneDevice(AudioClip audioClip)
{
// Check if the provided AudioClip is null.
if (audioClip == null)
{
// Log an error and abort the microphone check.
ConvaiLogger.Error("AudioClip is null!", ConvaiLogger.LogCategory.Character);
yield break;
}
// Wait for the specified duration before analyzing microphone input.
yield return new WaitForSeconds(INPUT_CHECK_DURATION);
// If the talk button was released prematurely, abort the microphone check.
if (_talkButtonDurationChecker.isTalkKeyReleasedEarly) yield break;
// Calculate the range of audio samples to analyze based on the duration.
int sampleStart = 0;
int sampleEnd = (int)(INPUT_CHECK_DURATION * audioClip.frequency * audioClip.channels);
// Initialize an array to store audio samples.
float[] samples = new float[sampleEnd - sampleStart];
int samplesLength = samples.Length;
// Attempt to retrieve audio data from the AudioClip.
if (audioClip.GetData(samples, sampleStart) == false)
{
ConvaiLogger.Error("Failed to get audio data!", ConvaiLogger.LogCategory.Character);
yield break;
}
// Initialize a variable to store the total absolute level of audio samples.
float level = 0;
// Calculate the total absolute level of audio samples.
for (int i = 0; i < samplesLength; i++) level += Mathf.Abs(samples[i] * SENSITIVITY);
// Normalize the calculated level by dividing it by the number of samples and then multiply by sensitivity.
level = level / samplesLength * SENSITIVITY;
// Check if the microphone level is below the threshold, indicating a potential issue.
if (level < THRESHOLD)
{
ConvaiLogger.Warn("Microphone Issue Detected!", ConvaiLogger.LogCategory.Character);
NotificationSystemHandler.Instance.NotificationRequest(NotificationType.MicrophoneIssue);
}
else
{
// Log that the microphone is working fine.
ConvaiLogger.Info("Microphone is working fine.", ConvaiLogger.LogCategory.Character);
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 596b00c62fa88c645938df61b488e084
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,35 @@
using Convai.Scripts.Runtime.LoggerSystem;
using UnityEngine;
namespace Convai.Scripts.Runtime.Addons
{
public class NetworkReachabilityChecker : MonoBehaviour
{
private void Start()
{
// Variable to store the debug text for network reachability status
string networkStatusDebugText = "";
switch (Application.internetReachability)
{
// Check the current internet reachability status
case NetworkReachability.NotReachable:
// If the device is not reachable over the internet, set debug text and send a notification.
networkStatusDebugText = "Not Reachable";
NotificationSystemHandler.Instance.NotificationRequest(NotificationType.NetworkReachabilityIssue);
break;
case NetworkReachability.ReachableViaCarrierDataNetwork:
// Reachable via mobile data network
networkStatusDebugText = "Reachable via Carrier Data Network";
break;
case NetworkReachability.ReachableViaLocalAreaNetwork:
// Reachable via local area network
networkStatusDebugText = "Reachable via Local Area Network";
break;
}
// Log the network reachability status for debugging
ConvaiLogger.Info("Network Reachability: " + networkStatusDebugText, ConvaiLogger.LogCategory.Character);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0001b07d59270994ba1cacc80c615eb4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,35 @@
using Convai.Scripts.Runtime.UI;
namespace Convai.Scripts.Runtime.Addons
{
public class NotificationSystemActiveStatusHandler : ActiveStatusHandler
{
protected override void UISaveLoadSystem_OnLoad()
{
// Retrieve the saved notification system activation status.
bool newValue = UISaveLoadSystem.Instance.NotificationSystemActiveStatus;
// Update the UI and internal status based on the loaded value.
OnStatusChange(newValue);
_activeStatusToggle.isOn = newValue;
}
protected override void UISaveLoadSystem_OnSave()
{
// Save the current notification system activation status.
UISaveLoadSystem.Instance.NotificationSystemActiveStatus = _activeStatusToggle.isOn;
}
/// <summary>
/// Set the activation status of the notification system.
/// </summary>
/// <param name="value"> The new activation status. </param>
public override void OnStatusChange(bool value)
{
// Call the NotificationSystemHandler to update the activation status.
NotificationSystemHandler.Instance.SetNotificationSystemActiveStatus(value);
}
}
}
// Handles the activation status of the notification system based on Settings Panel Toggle.

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2c5289bfc72186f40b90ff7b9d45894a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,87 @@
using System;
using Convai.Scripts.Runtime.LoggerSystem;
using UnityEngine;
namespace Convai.Scripts.Runtime.Addons
{
/// <summary>
/// Handles the notification system's behavior and interactions.
/// </summary>
[DefaultExecutionOrder(-100)]
public class NotificationSystemHandler : MonoBehaviour
{
/// <summary>
/// Array containing predefined notification configurations.
/// This array can be modified in the Unity Editor to define different types of notifications.
/// </summary>
[SerializeField] private SONotificationGroup _notificationGroup;
/// <summary>
/// Flag indicating whether the notification system is currently active.
/// </summary>
private bool _isNotificationSystemActive = true;
/// <summary>
/// Event triggered when a notification is requested.
/// </summary>
public Action<SONotification> OnNotificationRequested;
/// <summary>
/// Singleton instance of the NotificationSystemHandler.
/// </summary>
public static NotificationSystemHandler Instance { get; private set; }
/// <summary>
/// Ensure there is only one instance of NotificationSystemHandler.
/// </summary>
private void Awake()
{
if (Instance != null)
{
ConvaiLogger.DebugLog("<color=red> There's More Than One NotificationSystemHandler </color> " + transform + " - " + Instance, ConvaiLogger.LogCategory.UI);
Destroy(gameObject);
return;
}
Instance = this;
}
/// <summary>
/// Requests a notification of the specified type.
/// </summary>
/// <param name="notificationType">The type of notification to request.</param>
public void NotificationRequest(NotificationType notificationType)
{
// Check if the notification system is currently active.
if (!_isNotificationSystemActive) return;
// Search for the requested notification type in the predefined array.
SONotification requestedSONotification = null;
foreach (SONotification notification in _notificationGroup.SONotifications)
if (notification.NotificationType == notificationType)
{
requestedSONotification = notification;
break;
}
// If the requested notification is not found, log an error.
if (requestedSONotification == null)
{
ConvaiLogger.Error("There is no Notification defined for the selected Notification Type!", ConvaiLogger.LogCategory.UI);
return;
}
// Invoke the OnNotificationRequested event with the requested notification.
OnNotificationRequested?.Invoke(requestedSONotification);
}
/// <summary>
/// Sets the activation status of the notification system.
/// </summary>
/// <param name="value">The new activation status.</param>
public void SetNotificationSystemActiveStatus(bool value)
{
_isNotificationSystemActive = value;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d911410153c6d594098cac3c3bfa456d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,44 @@
namespace Convai.Scripts.Runtime.Addons
{
/// <summary>
/// Enumeration defining various types of in-app notifications.
/// Each enum value represents a specific scenario or issue that can trigger a notification.
/// </summary>
public enum NotificationType
{
/// <summary>
/// Indicates a notification related to microphone problems.
/// </summary>
MicrophoneIssue,
/// <summary>
/// Indicates a notification related to network reachability issues.
/// </summary>
NetworkReachabilityIssue,
/// <summary>
/// Indicates a notification when the user is not in proximity to initiate a conversation.
/// </summary>
NotCloseEnoughForConversation,
/// <summary>
/// Indicates a notification when a user releases the talk button prematurely during a conversation.
/// </summary>
TalkButtonReleasedEarly,
/// <summary>
/// Indicates that no microphone device was detected in the system
/// </summary>
NoMicrophoneDetected,
/// <summary>
/// Indicates that no API key was found.
/// </summary>
APIKeyNotFound,
/// <summary>
/// Indicates that usage limit for current plan has exceeded
/// </summary>
UsageLimitExceeded
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 18458e12a4b7457da0eb049ea8d56d4c
timeCreated: 1698156821

View File

@ -0,0 +1,34 @@
using UnityEngine;
namespace Convai.Scripts.Runtime.Addons
{
/// <summary>
/// This class represents a notification in the game.
/// </summary>
[CreateAssetMenu(menuName = "Convai/Notification System/Notification", fileName = "New Notification")]
public class SONotification : ScriptableObject
{
/// <summary>
/// The type of the notification.
/// </summary>
[Tooltip("The type of the notification.")]
public NotificationType NotificationType;
/// <summary>
/// The icon to be displayed with the notification.
/// </summary>
[Tooltip("The icon to be displayed with the notification.")]
public Sprite Icon;
/// <summary>
/// The notification title.
/// </summary>
[Tooltip("The notification title.")] public string NotificationTitle;
/// <summary>
/// The text content of the notification.
/// </summary>
[TextArea(10, 10)] [Tooltip("The text content of the notification.")]
public string NotificationMessage;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6b33bf54ff467c742a84ac58d34105ec
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,18 @@
using UnityEngine;
namespace Convai.Scripts.Runtime.Addons
{
/// <summary>
/// Represents a group of notifications as a ScriptableObject.
/// This allows for easy configuration and management of different notifications in the Unity Editor.
/// </summary>
[CreateAssetMenu(menuName = "Convai/Notification System/Notification Group", fileName = "New Notification Group")]
public class SONotificationGroup : ScriptableObject
{
/// <summary>
/// Array of SONotification objects.
/// Each object represents a unique notification that can be triggered in the application.
/// </summary>
public SONotification[] SONotifications;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 73c98f07d31af334ba49c31a867600b2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 0323cdb3f17fa914cae382e617430dd2
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 87748cd0f7abedf4e8dd7cf60e5fb99a
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: fe9295a7cd110d545b49f77fcc49c489
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 5e8394cce5330644594a848783844973
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7a58608b2e0aa77418e15e4b4ef0a1fa
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ee7d034a751672c449ab90856e05919c
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 374f6f70a1f7d9546926f20184467b32
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1dbb77ab53e0d714a9f00cba95a25a46
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 8c855f56ec2cd0f4d9c2458cc1c3db31
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,118 @@
using Convai.Scripts.Runtime.Core;
using Convai.Scripts.Runtime.UI;
using TMPro;
using UnityEngine;
namespace Convai.Scripts.Runtime.Addons
{
/// <summary>
/// Monitors the duration of the talk button press and notifies the Notification System if released prematurely.
/// </summary>
public class TalkButtonDurationChecker : MonoBehaviour
{
/// <summary>
/// Minimum duration required for a valid talk action.
/// </summary>
private const float MIN_TALK_DURATION = 0.5f;
/// <summary>
/// Flag indicating whether the talk button was released prematurely.
/// </summary>
[HideInInspector] public bool isTalkKeyReleasedEarly;
private TMP_InputField _activeInputField;
/// <summary>
/// Timer to track the duration of the talk button press.
/// </summary>
private float _timer;
private UIAppearanceSettings _uiAppearanceSettings;
private void Awake()
{
_uiAppearanceSettings = FindObjectOfType<UIAppearanceSettings>();
}
/// <summary>
/// Update is called once per frame.
/// It checks if the talk button is being held down or released.
/// </summary>
private void Update()
{
// Check if the talk button is being held down and increment the timer based on the time passed since the last frame.
if (ConvaiInputManager.Instance.IsTalkKeyHeld && !UIUtilities.IsAnyInputFieldFocused()) _timer += Time.deltaTime;
}
private void OnEnable()
{
ConvaiNPCManager.Instance.OnActiveNPCChanged += ConvaiNPCManager_OnActiveNPCChanged;
_uiAppearanceSettings.OnAppearanceChanged += UIAppearanceSettings_OnAppearanceChanged;
ConvaiInputManager.Instance.talkKeyInteract += HandleTalkButtonRelease;
}
private void OnDisable()
{
ConvaiNPCManager.Instance.OnActiveNPCChanged -= ConvaiNPCManager_OnActiveNPCChanged;
_uiAppearanceSettings.OnAppearanceChanged -= UIAppearanceSettings_OnAppearanceChanged;
}
private void HandleTalkButtonRelease(bool releaseState)
{
if (releaseState || UIUtilities.IsAnyInputFieldFocused()) return;
if (_activeInputField != null && _activeInputField.isFocused)
{
_timer = 0;
return;
}
CheckTalkButtonRelease();
// Reset the timer for the next talk action.
_timer = 0;
}
private void ConvaiNPCManager_OnActiveNPCChanged(ConvaiNPC convaiNpc)
{
if (convaiNpc == null)
{
_activeInputField = null;
return;
}
_activeInputField = convaiNpc.playerInteractionManager.FindActiveInputField();
}
private void UIAppearanceSettings_OnAppearanceChanged()
{
ConvaiNPC convaiNpc = ConvaiNPCManager.Instance.activeConvaiNPC;
if (convaiNpc == null)
{
_activeInputField = null;
return;
}
_activeInputField = convaiNpc.playerInteractionManager.FindActiveInputField();
}
/// <summary>
/// Checks if the talk button was released prematurely and triggers a notification if so.
/// </summary>
private void CheckTalkButtonRelease()
{
// Initialize the flag to false.
isTalkKeyReleasedEarly = false;
// Trigger a notification if the talk button is released before reaching the minimum required duration.
if (_timer < MIN_TALK_DURATION)
{
// Check if there is an active ConvaiNPC.
if (ConvaiNPCManager.Instance.activeConvaiNPC == null) return;
// Set the flag to true and request a notification.
isTalkKeyReleasedEarly = true;
NotificationSystemHandler.Instance.NotificationRequest(NotificationType.TalkButtonReleasedEarly);
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ce5db0c0354de754f99bd35c9f7fb96a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,67 @@
using System;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace Convai.Scripts.Runtime.Addons
{
/// <summary>
/// Represents a UI notification element that can be activated or deactivated.
/// </summary>
public class UINotification : MonoBehaviour
{
/// <summary>
/// The RectTransform of the notification UI element.
/// </summary>
public RectTransform NotificationRectTransform;
/// <summary>
/// The image component for displaying the notification icon.
/// </summary>
[SerializeField] private Image _notificationIcon;
/// <summary>
/// The TextMeshProUGUI component for displaying the notification title.
/// </summary>
[SerializeField] private TextMeshProUGUI _notificationTitleText;
/// <summary>
/// The TextMeshProUGUI component for displaying the notification text.
/// </summary>
[SerializeField] private TextMeshProUGUI _notificationMessageText;
/// <summary>
/// Deactivates the notification UI element on awake.
/// </summary>
private void Awake()
{
SetActive(false);
}
/// <summary>
/// Initializes the UI notification with the provided Notification data.
/// </summary>
/// <param name="soNotification">The notification data to initialize the UI notification with.</param>
public void Initialize(SONotification soNotification)
{
if (soNotification == null) throw new ArgumentNullException(nameof(soNotification), "SONotification is null.");
// Set the notification icon and text based on the provided Notification.
_notificationIcon.sprite = soNotification.Icon;
_notificationTitleText.text = soNotification.NotificationTitle;
_notificationMessageText.text = soNotification.NotificationMessage;
// Activate the notification UI element.
SetActive(true);
}
/// <summary>
/// Sets the active state of the notification UI element.
/// </summary>
/// <param name="value">The new active state.</param>
public void SetActive(bool value)
{
gameObject.SetActive(value);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 894cc2b4c2298fb4a98bd3d9f2e8d6ba
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,317 @@
using System.Collections;
using System.Collections.Generic;
using Convai.Scripts.Runtime.UI;
using UnityEngine;
namespace Convai.Scripts.Runtime.Addons
{
/// <summary>
/// This class is responsible for controlling the UI notifications in the game.
/// It handles the creation, activation, deactivation, and animation of notifications.
/// </summary>
public class UINotificationController : MonoBehaviour
{
/// <summary>
/// Maximum number of notifications that can be displayed at the same time.
/// </summary>
private const int MAX_NUMBER_OF_NOTIFICATION_AT_SAME_TIME = 3;
/// <summary>
/// References to the UI notification prefab and other necessary components.
/// </summary>
[Header("References")] [SerializeField]
private UINotification _uiNotificationPrefab;
/// <summary>
/// Spacing between Notifications
/// </summary>
[Header("Configurations")] [SerializeField]
private int _spacing = 100;
/// <summary>
/// Position for Active Notification
/// </summary>
[Tooltip("Starting position for the first notification; Y value adjusts sequentially for each subsequent notification.")] [SerializeField]
private Vector2 _activeNotificationPos;
/// <summary>
/// Position for Deactivated Notification
/// </summary>
[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;
/// <summary>
/// List to keep track of the order in which pending notifications were requested.
/// </summary>
private readonly List<SONotification> _pendingNotificationsOrder = new();
private Queue<UINotification> _activeUINotifications;
private CanvasGroup _canvasGroup;
private Queue<UINotification> _deactivatedUINotifications;
private FadeCanvas _fadeCanvas;
/// <summary>
/// Flag indicating whether a UI notification movement animation is currently in progress.
/// Used to prevent overlapping animation coroutines for UI notifications.
/// </summary>
private bool _isNotificationAnimationInProgress;
/// <summary>
/// Awake is called when the script instance is being loaded.
/// It is used to initialize any variables or game state before the game starts.
/// </summary>
private void Awake()
{
// Get necessary components and initialize UI notifications.
_canvasGroup = GetComponent<CanvasGroup>();
_fadeCanvas = GetComponent<FadeCanvas>();
InitializeUINotifications();
}
/// <summary>
/// This function is called when the object becomes enabled and active.
/// It is used to subscribe to the OnNotificationRequested event.
/// </summary>
private void OnEnable()
{
NotificationSystemHandler.Instance.OnNotificationRequested += NotificationSystemHandler_OnNotificationRequested;
}
/// <summary>
/// This function is called when the behaviour becomes disabled or inactive.
/// It is used to unsubscribe from the OnNotificationRequested event.
/// </summary>
private void OnDisable()
{
NotificationSystemHandler.Instance.OnNotificationRequested -= NotificationSystemHandler_OnNotificationRequested;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="SONotification">The requested SONotification to be processed.</param>
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);
}
/// <summary>
/// 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.
/// </summary>
private void InitializeUINotifications()
{
// Initialize the queues for active and deactivated UI notifications.
_activeUINotifications = new Queue<UINotification>();
_deactivatedUINotifications = new Queue<UINotification>();
// 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);
}
}
/// <summary>
/// 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.
/// </summary>
/// <param name="SONotification">The SONotification to be used for initializing the UI notification.</param>
/// <param name="uiNotification">The initialized UINotification if successful, otherwise null.</param>
/// <returns>True if initialization is successful, false otherwise.</returns>
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;
}
/// <summary>
/// Initiates the coroutine for UI notification animations and adds the notification to the active queue.
/// </summary>
/// <param name="uiNotification">The UINotification to be animated and added to the active queue.</param>
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));
}
/// <summary>
/// Coroutine for managing the lifecycle of a UI notification, including its activation, display duration, and
/// deactivation.
/// </summary>
/// <param name="uiNotification">The UINotification to be managed.</param>
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();
}
/// <summary>
/// Moves the UI notification to its active position.
/// </summary>
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));
}
/// <summary>
/// Moves the UI notification to its hidden position.
/// </summary>
private IEnumerator MoveUINotificationToHiddenPosition(UINotification uiNotification)
{
Vector2 targetPos = _deactivatedNotificationPos;
yield return StartCoroutine(MoveUINotificationToTargetPos(uiNotification, targetPos));
}
/// <summary>
/// Deactivates the UI notification, updates positions, and enqueues it for later use.
/// </summary>
private void DeactivateAndEnqueueUINotification(UINotification uiNotification)
{
uiNotification.SetActive(false);
_activeUINotifications.Dequeue();
_deactivatedUINotifications.Enqueue(uiNotification);
UpdateUINotificationPositions();
}
/// <summary>
/// Checks if there are pending notifications and initializes and starts a new one if available.
/// </summary>
private void TryInitializeAndStartNewNotification()
{
if (TryInitializeNewNotification(_pendingNotificationsOrder[0], out UINotification newUiNotification)) StartNotificationUICoroutine(newUiNotification);
}
/// <summary>
/// Smoothly moves the UI notification to the target position over a specified duration.
/// </summary>
/// <param name="uiNotification">The UINotification to be moved.</param>
/// <param name="targetPos">The target position to move the UINotification to.</param>
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;
}
/// <summary>
/// Updates the positions of active UI notifications along the Y-axis.
/// </summary>
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;
}
}
/// <summary>
/// Gets an available UI notification from the deactivated queue.
/// </summary>
/// <returns>The available UI notification, or null if the deactivated queue is empty.</returns>
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();
}
/// <summary>
/// Checks if there are pending notifications in the order list.
/// </summary>
/// <returns>True if there are pending notifications, false otherwise.</returns>
private bool AreTherePendingNotifications()
{
// Check if there are any pending notifications in the order list
return _pendingNotificationsOrder.Count >= 1;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ad1fae26184a1504bbf417585440fe12
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: