Files
Emberwild/Assets/GAME/Script/Inventory/HotbarUI.cs
T
2026-07-22 12:56:13 +02:00

229 lines
7.5 KiB
C#

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;
[Header("Hover Description")]
[Tooltip("Same panel used by the inventory — assign the shared HoverDescriptionUI here too.")]
[SerializeField] private HoverDescriptionUI hoverDescription;
private PlayerInventory inventory;
private Tweener scaleTween;
private Tweener alphaTween;
private float zoomOutTimer;
private bool inventoryLocked;
private bool bound;
/// <summary>
/// Waits for the networked local player to spawn before binding to its inventory.
/// </summary>
private void OnEnable()
{
PlayerEvents.LocalPlayerSpawned += HandleLocalPlayerSpawned;
}
/// <summary>
/// Unsubscribes — mirrors OnEnable exactly.
/// </summary>
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(SlotContainer.Inventory, i,
InventoryUI.HandleSlotDrop, OnSlotClicked, OnSlotHoverEnter, OnSlotHoverExit);
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();
}
/// <summary>
/// Binds to the inventory as soon as the local player has spawned on the network.
/// </summary>
private void HandleLocalPlayerSpawned() => BindToInventory();
/// <summary>
/// Hooks the hotbar UI onto the local player's inventory; safe to call more than once.
/// </summary>
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();
}
/// <summary>
/// Right-click on a hotbar slot: a quick deposit into the open chest, otherwise the slot context
/// menu (only while the inventory window is open — the hotbar must not pop a menu during play).
/// </summary>
private void OnSlotClicked(SlotContainer container, int index, bool rightClick)
{
if (!rightClick) return;
if (PlayerEvents.IsChestOpen)
{
if (InventoryUI.Instance != null)
InventoryUI.Instance.RequestQuickTransfer(SlotContainer.Inventory, index);
return;
}
if (!PlayerEvents.IsInventoryOpen) return;
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));
}
/// <summary>
/// Shows the shared description panel for a hovered hotbar slot, but only while the inventory
/// is open and no chest is open (chest mode hides the description entirely) — during normal play
/// the hotbar must not pop a description. The duration bar reflects the item's remaining uses.
/// </summary>
private void OnSlotHoverEnter(SlotContainer container, int index)
{
if (PlayerEvents.IsChestOpen) return;
if (hoverDescription == null || inventory == null || !PlayerEvents.IsInventoryOpen) return;
InventorySlot slot = inventory.GetSlot(index);
if (slot == null || slot.IsEmpty)
{
hoverDescription.Hide();
return;
}
hoverDescription.Show(ItemDescriptionView.From(slot));
}
/// <summary>
/// Hides the description panel when the pointer leaves a hotbar slot.
/// </summary>
private void OnSlotHoverExit()
{
if (hoverDescription != null)
hoverDescription.Hide();
}
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));
}
}
}