using UnityEngine; using System.Collections.Generic; using DG.Tweening; using Ashwild.Inventory; using Ashwild.Player; namespace Ashwild.UI { public class NotificationUI : MonoBehaviour { [Header("References")] [SerializeField] private RectTransform container; [SerializeField] private GameObject notificationPrefab; [Header("Layout")] [SerializeField] private float notificationHeight = 40f; [SerializeField] private float spacing = 6f; [SerializeField] private float startOffsetY = 20f; [Header("Timing")] [SerializeField] private float displayDuration = 4f; [Header("Animation")] [SerializeField] private float slideInDuration = 0.35f; [SerializeField] private Ease slideInEase = Ease.OutBack; [SerializeField] private float stackSlideDuration = 0.25f; [SerializeField] private Ease stackSlideEase = Ease.OutQuad; [Header("Colors")] [SerializeField] private Color gainColor = new Color(0.45f, 1f, 0.45f); // picked up / added [SerializeField] private Color lossColor = new Color(1f, 0.45f, 0.45f); // dropped / consumed private readonly List activeNotifications = new List(); private void OnEnable() { PlayerEvents.ItemAdded += OnItemAdded; PlayerEvents.ItemDropped += OnItemDropped; PlayerEvents.ItemConsumed += OnItemConsumed; PlayerEvents.FoodPlacedToCook += OnItemSpent; PlayerEvents.FuelAdded += OnItemSpent; } private void OnDisable() { PlayerEvents.ItemAdded -= OnItemAdded; PlayerEvents.ItemDropped -= OnItemDropped; PlayerEvents.ItemConsumed -= OnItemConsumed; PlayerEvents.FoodPlacedToCook -= OnItemSpent; PlayerEvents.FuelAdded -= OnItemSpent; } private void OnItemAdded(ItemData item, int quantity) => Push(item, quantity); private void OnItemDropped(ItemData item, int quantity) => Push(item, -quantity); private void OnItemConsumed(ItemData item) => Push(item, -1); // Food placed on a cooking station or fuel fed to it both leave the inventory by one. private void OnItemSpent(ItemData item) => Push(item, -1); private void Push(ItemData item, int signedQuantity) { if (item == null || signedQuantity == 0) return; // Clean up destroyed notifications activeNotifications.RemoveAll(n => n == null); bool gain = signedQuantity > 0; // Merge only with a notification for the same item AND same direction for (int i = 0; i < activeNotifications.Count; i++) { if (activeNotifications[i] != null && activeNotifications[i].ItemData == item && activeNotifications[i].IsGain == gain) { activeNotifications[i].AddQuantity(signedQuantity, displayDuration); return; } } // Create new notification GameObject go = Instantiate(notificationPrefab, container); NotificationItemUI notification = go.GetComponent(); float targetY = startOffsetY + activeNotifications.Count * (notificationHeight + spacing); RectTransform rt = notification.RectTransform; rt.anchoredPosition = new Vector2(0f, targetY - 30f); notification.Initialize(item, signedQuantity, gain ? gainColor : lossColor, displayDuration, slideInDuration, slideInEase); // Slide in from below rt.DOAnchorPosY(targetY, slideInDuration).SetEase(slideInEase); activeNotifications.Add(notification); } private void LateUpdate() { // Clean up destroyed notifications and reposition remaining ones int removed = activeNotifications.RemoveAll(n => n == null); if (removed > 0) RepositionAll(); } private void RepositionAll() { for (int i = 0; i < activeNotifications.Count; i++) { if (activeNotifications[i] == null) continue; float targetY = startOffsetY + i * (notificationHeight + spacing); activeNotifications[i].RectTransform .DOAnchorPosY(targetY, stackSlideDuration) .SetEase(stackSlideEase); } } } }