using UnityEngine; using DG.Tweening; using Ashwild.Player; namespace Ashwild.Inventory { public class HotbarUI : MonoBehaviour { [Header("References")] [SerializeField] private SlotUI[] hotbarSlot; [SerializeField] private CanvasGroup canvasGroup; [Header("Animation")] [SerializeField] private float idleScale = 0.7f; [SerializeField] private float activeScale = 1f; [SerializeField] private float idleAlpha = 0.5f; [SerializeField] private float activeAlpha = 1f; [SerializeField] private float zoomInDuration = 0.15f; [SerializeField] private float zoomOutDuration = 0.4f; [SerializeField] private float activeHoldDuration = 1.5f; [SerializeField] private Ease zoomInEase = Ease.OutBack; [SerializeField] private Ease zoomOutEase = Ease.InQuad; [Header("Context Menu")] [SerializeField] private SlotContextMenu contextMenu; private PlayerInventory inventory; private Tweener scaleTween; private Tweener alphaTween; private float zoomOutTimer; private bool inventoryLocked; private bool bound; /// /// Waits for the networked local player to spawn before binding to its inventory. /// private void OnEnable() { PlayerEvents.LocalPlayerSpawned += HandleLocalPlayerSpawned; } /// /// Unsubscribes — mirrors OnEnable exactly. /// private void OnDisable() { PlayerEvents.LocalPlayerSpawned -= HandleLocalPlayerSpawned; } private void Start() { // Visual setup that does not need the player. for (int i = 0; i < hotbarSlot.Length; i++) hotbarSlot[i].Initialize(i, InventoryUI.OnSwapRequested, OnSlotClicked); transform.localScale = Vector3.one * idleScale; if (canvasGroup != null) canvasGroup.alpha = idleAlpha; // The player may already exist (late UI init); otherwise we wait for the spawn event. if (PlayerInventory.Instance != null) BindToInventory(); } /// /// Binds to the inventory as soon as the local player has spawned on the network. /// private void HandleLocalPlayerSpawned() => BindToInventory(); /// /// Hooks the hotbar UI onto the local player's inventory; safe to call more than once. /// private void BindToInventory() { if (bound) return; inventory = PlayerInventory.Instance; if (inventory == null) return; bound = true; inventory.onSlotChanged.AddListener(OnSlotChanged); inventory.onSelectedSlotChanged.AddListener(OnSelectedSlotChanged); RefreshAll(); UpdateSelection(inventory.SelectedHotbarIndex); } private void OnDestroy() { scaleTween?.Kill(); alphaTween?.Kill(); if (inventory != null) { inventory.onSlotChanged.RemoveListener(OnSlotChanged); inventory.onSelectedSlotChanged.RemoveListener(OnSelectedSlotChanged); } } private void Update() { if (inventoryLocked || zoomOutTimer <= 0f) return; zoomOutTimer -= Time.deltaTime; if (zoomOutTimer <= 0f) ZoomOut(); } private void OnSelectedSlotChanged(int selectedIndex) { UpdateSelection(selectedIndex); ZoomIn(); } private void ZoomIn() { zoomOutTimer = activeHoldDuration; scaleTween?.Kill(); scaleTween = transform.DOScale(activeScale, zoomInDuration).SetEase(zoomInEase); if (canvasGroup != null) { alphaTween?.Kill(); alphaTween = canvasGroup.DOFade(activeAlpha, zoomInDuration); } } private void ZoomOut() { scaleTween?.Kill(); scaleTween = transform.DOScale(idleScale, zoomOutDuration).SetEase(zoomOutEase); if (canvasGroup != null) { alphaTween?.Kill(); alphaTween = canvasGroup.DOFade(idleAlpha, zoomOutDuration); } } public void OnInventoryOpen() { inventoryLocked = true; zoomOutTimer = 0f; ZoomIn(); } public void OnInventoryClose() { inventoryLocked = false; ZoomOut(); } private void OnSlotClicked(int index, bool rightClick) { if (rightClick && PlayerEvents.IsInventoryOpen) { if (contextMenu != null) contextMenu.Show(index); else inventory.UseItem(index); } } private void OnSlotChanged(int index) { if (index >= 0 && index < hotbarSlot.Length) hotbarSlot[index].UpdateVisual(inventory.GetSlot(index)); } private void UpdateSelection(int selectedIndex) { for (int i = 0; i < hotbarSlot.Length; i++) hotbarSlot[i].SetSelected(i == selectedIndex); } private void RefreshAll() { for (int i = 0; i < hotbarSlot.Length; i++) hotbarSlot[i].UpdateVisual(inventory.GetSlot(i)); } } }