2026-06-19 UI, UI로직
This commit is contained in:
14
Assets/My project/Inventory/Scripts/InventoryItemType.cs
Normal file
14
Assets/My project/Inventory/Scripts/InventoryItemType.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// 인벤토리에서 관리할 아이템 종류입니다.
|
||||
/// 문자열 대신 enum을 사용하면 오타로 인한 버그를 줄일 수 있습니다.
|
||||
/// </summary>
|
||||
public enum InventoryItemType
|
||||
{
|
||||
Fish,
|
||||
OldCompass,
|
||||
Trash,
|
||||
Bottle,
|
||||
MemoryPiece
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9fc654a578c829244936abec2cf40091
|
||||
185
Assets/My project/Inventory/Scripts/InventoryManager.cs
Normal file
185
Assets/My project/Inventory/Scripts/InventoryManager.cs
Normal file
@@ -0,0 +1,185 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
[Serializable]
|
||||
public class InventoryItemStack
|
||||
{
|
||||
public InventoryItemType itemType;
|
||||
[Min(0)] public int count;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class InventoryItemChangedEvent : UnityEvent<InventoryItemType, int>
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 실제 아이템 개수를 관리하는 중심 스크립트입니다.
|
||||
/// UI를 직접 수정하지 않고, 아이템 개수 변경 이벤트만 알려줍니다.
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
public class InventoryManager : MonoBehaviour
|
||||
{
|
||||
[Header("Initial Items")]
|
||||
[SerializeField] private List<InventoryItemStack> initialItems = new List<InventoryItemStack>();
|
||||
|
||||
[Header("Events")]
|
||||
public InventoryItemChangedEvent onItemCountChanged;
|
||||
public UnityEvent onInventoryChanged;
|
||||
|
||||
[Header("Debug")]
|
||||
[SerializeField] private bool showDebugLog = true;
|
||||
|
||||
private readonly Dictionary<InventoryItemType, int> itemCounts = new Dictionary<InventoryItemType, int>();
|
||||
|
||||
public event Action<InventoryItemType, int> ItemCountChanged;
|
||||
public event Action InventoryChanged;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
InitializeFromInspector();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
NotifyAllItemsChanged();
|
||||
}
|
||||
|
||||
private void InitializeFromInspector()
|
||||
{
|
||||
itemCounts.Clear();
|
||||
|
||||
foreach (InventoryItemType itemType in Enum.GetValues(typeof(InventoryItemType)))
|
||||
itemCounts[itemType] = 0;
|
||||
|
||||
for (int i = 0; i < initialItems.Count; i++)
|
||||
{
|
||||
InventoryItemStack stack = initialItems[i];
|
||||
if (stack == null)
|
||||
continue;
|
||||
|
||||
itemCounts[stack.itemType] = Mathf.Max(0, stack.count);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddItem(InventoryItemType itemType)
|
||||
{
|
||||
AddItem(itemType, 1);
|
||||
}
|
||||
|
||||
public void AddItem(InventoryItemType itemType, int amount)
|
||||
{
|
||||
if (amount <= 0)
|
||||
return;
|
||||
|
||||
int newCount = GetItemCount(itemType) + amount;
|
||||
SetItemCount(itemType, newCount);
|
||||
|
||||
if (showDebugLog)
|
||||
Debug.Log($"[InventoryManager] {itemType} +{amount} => {newCount}");
|
||||
}
|
||||
|
||||
public bool RemoveItem(InventoryItemType itemType)
|
||||
{
|
||||
return RemoveItem(itemType, 1);
|
||||
}
|
||||
|
||||
public bool RemoveItem(InventoryItemType itemType, int amount)
|
||||
{
|
||||
if (amount <= 0)
|
||||
return false;
|
||||
|
||||
int current = GetItemCount(itemType);
|
||||
|
||||
if (current < amount)
|
||||
{
|
||||
if (showDebugLog)
|
||||
Debug.LogWarning($"[InventoryManager] {itemType} 부족: 현재 {current}, 필요 {amount}");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
SetItemCount(itemType, current - amount);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void SetItemCount(InventoryItemType itemType, int count)
|
||||
{
|
||||
int clampedCount = Mathf.Max(0, count);
|
||||
int previousCount = GetItemCount(itemType);
|
||||
|
||||
if (previousCount == clampedCount)
|
||||
return;
|
||||
|
||||
itemCounts[itemType] = clampedCount;
|
||||
NotifyItemChanged(itemType, clampedCount);
|
||||
}
|
||||
|
||||
public int GetItemCount(InventoryItemType itemType)
|
||||
{
|
||||
if (itemCounts.TryGetValue(itemType, out int count))
|
||||
return count;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public bool HasItem(InventoryItemType itemType)
|
||||
{
|
||||
return GetItemCount(itemType) > 0;
|
||||
}
|
||||
|
||||
public bool HasItem(InventoryItemType itemType, int requiredAmount)
|
||||
{
|
||||
return GetItemCount(itemType) >= Mathf.Max(1, requiredAmount);
|
||||
}
|
||||
|
||||
public void ClearInventory()
|
||||
{
|
||||
foreach (InventoryItemType itemType in Enum.GetValues(typeof(InventoryItemType)))
|
||||
itemCounts[itemType] = 0;
|
||||
|
||||
NotifyAllItemsChanged();
|
||||
}
|
||||
|
||||
public IReadOnlyDictionary<InventoryItemType, int> GetAllItemCounts()
|
||||
{
|
||||
return itemCounts;
|
||||
}
|
||||
|
||||
private void NotifyItemChanged(InventoryItemType itemType, int count)
|
||||
{
|
||||
ItemCountChanged?.Invoke(itemType, count);
|
||||
InventoryChanged?.Invoke();
|
||||
|
||||
onItemCountChanged?.Invoke(itemType, count);
|
||||
onInventoryChanged?.Invoke();
|
||||
}
|
||||
|
||||
private void NotifyAllItemsChanged()
|
||||
{
|
||||
foreach (KeyValuePair<InventoryItemType, int> pair in itemCounts)
|
||||
{
|
||||
ItemCountChanged?.Invoke(pair.Key, pair.Value);
|
||||
onItemCountChanged?.Invoke(pair.Key, pair.Value);
|
||||
}
|
||||
|
||||
InventoryChanged?.Invoke();
|
||||
onInventoryChanged?.Invoke();
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private void OnValidate()
|
||||
{
|
||||
if (initialItems == null)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < initialItems.Count; i++)
|
||||
{
|
||||
if (initialItems[i] != null)
|
||||
initialItems[i].count = Mathf.Max(0, initialItems[i].count);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 74034ab98942d224e9550c24fede0c04
|
||||
159
Assets/My project/Inventory/Scripts/InventorySlotUI.cs
Normal file
159
Assets/My project/Inventory/Scripts/InventorySlotUI.cs
Normal file
@@ -0,0 +1,159 @@
|
||||
using System.Collections;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
|
||||
/// <summary>
|
||||
/// 인벤토리 슬롯 하나를 담당하는 UI 스크립트입니다.
|
||||
/// FishSlot, CompassSlot, TrashSlot, BottleSlot 등에 각각 붙입니다.
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
public class InventorySlotUI : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler, ISelectHandler, IDeselectHandler
|
||||
{
|
||||
[Header("Item")]
|
||||
[SerializeField] private InventoryItemType itemType;
|
||||
|
||||
[Header("UI References")]
|
||||
[SerializeField] private Image slotBackground;
|
||||
[SerializeField] private Image itemIcon;
|
||||
[SerializeField] private TMP_Text countText;
|
||||
[SerializeField] private GameObject newBadge;
|
||||
[SerializeField] private CanvasGroup canvasGroup;
|
||||
|
||||
[Header("Tooltip")]
|
||||
[SerializeField] private InventoryTooltipUI tooltipUI;
|
||||
|
||||
[Header("Settings")]
|
||||
[SerializeField] private bool hideWhenZero = false;
|
||||
[SerializeField] private bool dimWhenZero = true;
|
||||
[SerializeField] private float zeroAlpha = 0.35f;
|
||||
[SerializeField] private float ownedAlpha = 1f;
|
||||
[SerializeField] private float newBadgeShowTime = 1.2f;
|
||||
|
||||
private int currentCount;
|
||||
private Coroutine newBadgeRoutine;
|
||||
|
||||
public InventoryItemType ItemType => itemType;
|
||||
public int CurrentCount => currentCount;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (canvasGroup == null)
|
||||
canvasGroup = GetComponent<CanvasGroup>();
|
||||
|
||||
if (newBadge != null)
|
||||
newBadge.SetActive(false);
|
||||
}
|
||||
|
||||
public void SetItemType(InventoryItemType newItemType)
|
||||
{
|
||||
itemType = newItemType;
|
||||
}
|
||||
|
||||
public void SetCount(int count, bool showNewBadge)
|
||||
{
|
||||
int previousCount = currentCount;
|
||||
currentCount = Mathf.Max(0, count);
|
||||
|
||||
UpdateVisualState();
|
||||
|
||||
if (showNewBadge && currentCount > previousCount)
|
||||
ShowNewBadge();
|
||||
}
|
||||
|
||||
public void SetIcon(Sprite sprite)
|
||||
{
|
||||
if (itemIcon == null)
|
||||
return;
|
||||
|
||||
itemIcon.sprite = sprite;
|
||||
itemIcon.enabled = sprite != null;
|
||||
}
|
||||
|
||||
public void SetSlotBackground(Sprite sprite)
|
||||
{
|
||||
if (slotBackground == null)
|
||||
return;
|
||||
|
||||
slotBackground.sprite = sprite;
|
||||
slotBackground.enabled = sprite != null;
|
||||
}
|
||||
|
||||
public void ShowNewBadge()
|
||||
{
|
||||
if (newBadge == null)
|
||||
return;
|
||||
|
||||
if (newBadgeRoutine != null)
|
||||
StopCoroutine(newBadgeRoutine);
|
||||
|
||||
newBadgeRoutine = StartCoroutine(NewBadgeRoutine());
|
||||
}
|
||||
|
||||
public void HideNewBadge()
|
||||
{
|
||||
if (newBadgeRoutine != null)
|
||||
{
|
||||
StopCoroutine(newBadgeRoutine);
|
||||
newBadgeRoutine = null;
|
||||
}
|
||||
|
||||
if (newBadge != null)
|
||||
newBadge.SetActive(false);
|
||||
}
|
||||
|
||||
private IEnumerator NewBadgeRoutine()
|
||||
{
|
||||
newBadge.SetActive(true);
|
||||
yield return new WaitForSeconds(newBadgeShowTime);
|
||||
newBadge.SetActive(false);
|
||||
newBadgeRoutine = null;
|
||||
}
|
||||
|
||||
private void UpdateVisualState()
|
||||
{
|
||||
bool hasItem = currentCount > 0;
|
||||
|
||||
if (countText != null)
|
||||
countText.text = $"x{currentCount}";
|
||||
|
||||
if (hideWhenZero)
|
||||
gameObject.SetActive(hasItem);
|
||||
|
||||
if (canvasGroup != null && dimWhenZero)
|
||||
canvasGroup.alpha = hasItem ? ownedAlpha : zeroAlpha;
|
||||
}
|
||||
|
||||
public void OnPointerEnter(PointerEventData eventData)
|
||||
{
|
||||
ShowTooltip();
|
||||
}
|
||||
|
||||
public void OnPointerExit(PointerEventData eventData)
|
||||
{
|
||||
HideTooltip();
|
||||
}
|
||||
|
||||
public void OnSelect(BaseEventData eventData)
|
||||
{
|
||||
ShowTooltip();
|
||||
}
|
||||
|
||||
public void OnDeselect(BaseEventData eventData)
|
||||
{
|
||||
HideTooltip();
|
||||
}
|
||||
|
||||
public void ShowTooltip()
|
||||
{
|
||||
if (tooltipUI != null)
|
||||
tooltipUI.ShowTooltip(itemType, currentCount);
|
||||
}
|
||||
|
||||
public void HideTooltip()
|
||||
{
|
||||
if (tooltipUI != null)
|
||||
tooltipUI.HideTooltip();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 24ab5cc89178ea34f8cd1187877397ce
|
||||
77
Assets/My project/Inventory/Scripts/InventoryTooltipUI.cs
Normal file
77
Assets/My project/Inventory/Scripts/InventoryTooltipUI.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
|
||||
[Serializable]
|
||||
public class InventoryTooltipEntry
|
||||
{
|
||||
public InventoryItemType itemType;
|
||||
public string displayName;
|
||||
[TextArea(2, 4)] public string description;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 아이템 슬롯에 마우스/VR 포인터가 올라갔을 때 간단한 설명을 표시합니다.
|
||||
/// InventorySlotUI에서 ShowTooltip, HideTooltip을 호출합니다.
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
public class InventoryTooltipUI : MonoBehaviour
|
||||
{
|
||||
[Header("UI")]
|
||||
[SerializeField] private GameObject tooltipPanel;
|
||||
[SerializeField] private TMP_Text titleText;
|
||||
[SerializeField] private TMP_Text descriptionText;
|
||||
[SerializeField] private TMP_Text countText;
|
||||
|
||||
[Header("Entries")]
|
||||
[SerializeField] private List<InventoryTooltipEntry> entries = new List<InventoryTooltipEntry>()
|
||||
{
|
||||
new InventoryTooltipEntry { itemType = InventoryItemType.Fish, displayName = "생선", description = "고양이 합창단이 좋아합니다." },
|
||||
new InventoryTooltipEntry { itemType = InventoryItemType.OldCompass, displayName = "낡은 나침반", description = "미로에서 길을 찾는 데 도움이 됩니다." },
|
||||
new InventoryTooltipEntry { itemType = InventoryItemType.Trash, displayName = "쓰레기", description = "낚시터를 정화하는 데 필요합니다." },
|
||||
new InventoryTooltipEntry { itemType = InventoryItemType.Bottle, displayName = "마법병", description = "바다 속에서 발견한 수상한 병입니다." },
|
||||
new InventoryTooltipEntry { itemType = InventoryItemType.MemoryPiece, displayName = "기억의 조각", description = "제페토를 구출하기 위한 중요한 조각입니다." }
|
||||
};
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
HideTooltip();
|
||||
}
|
||||
|
||||
public void ShowTooltip(InventoryItemType itemType, int count)
|
||||
{
|
||||
InventoryTooltipEntry entry = FindEntry(itemType);
|
||||
|
||||
if (tooltipPanel != null)
|
||||
tooltipPanel.SetActive(true);
|
||||
|
||||
if (titleText != null)
|
||||
titleText.text = entry != null && !string.IsNullOrWhiteSpace(entry.displayName)
|
||||
? entry.displayName
|
||||
: itemType.ToString();
|
||||
|
||||
if (descriptionText != null)
|
||||
descriptionText.text = entry != null ? entry.description : string.Empty;
|
||||
|
||||
if (countText != null)
|
||||
countText.text = $"보유: x{Mathf.Max(0, count)}";
|
||||
}
|
||||
|
||||
public void HideTooltip()
|
||||
{
|
||||
if (tooltipPanel != null)
|
||||
tooltipPanel.SetActive(false);
|
||||
}
|
||||
|
||||
private InventoryTooltipEntry FindEntry(InventoryItemType itemType)
|
||||
{
|
||||
for (int i = 0; i < entries.Count; i++)
|
||||
{
|
||||
if (entries[i] != null && entries[i].itemType == itemType)
|
||||
return entries[i];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1d5f4bd0659b7de4c91738c98012e95f
|
||||
115
Assets/My project/Inventory/Scripts/InventoryUI.cs
Normal file
115
Assets/My project/Inventory/Scripts/InventoryUI.cs
Normal file
@@ -0,0 +1,115 @@
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// 전체 인벤토리 UI를 갱신하는 스크립트입니다.
|
||||
/// InventoryManager의 변경 이벤트를 받아 각 InventorySlotUI에 전달합니다.
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
public class InventoryUI : MonoBehaviour
|
||||
{
|
||||
[Header("References")]
|
||||
[SerializeField] private InventoryManager inventoryManager;
|
||||
[SerializeField] private GameObject inventoryPanel;
|
||||
[SerializeField] private InventorySlotUI[] slots;
|
||||
|
||||
[Header("Settings")]
|
||||
[SerializeField] private bool autoFindManager = true;
|
||||
[SerializeField] private bool refreshOnEnable = true;
|
||||
[SerializeField] private bool showNewBadgeOnIncrease = true;
|
||||
|
||||
private bool subscribed;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (inventoryPanel == null)
|
||||
inventoryPanel = gameObject;
|
||||
|
||||
if (inventoryManager == null && autoFindManager)
|
||||
inventoryManager = FindFirstObjectByType<InventoryManager>();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
Subscribe();
|
||||
|
||||
if (refreshOnEnable)
|
||||
RefreshAllSlots();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
Unsubscribe();
|
||||
}
|
||||
|
||||
public void SetVisible(bool visible)
|
||||
{
|
||||
if (inventoryPanel != null)
|
||||
inventoryPanel.SetActive(visible);
|
||||
}
|
||||
|
||||
public void ToggleVisible()
|
||||
{
|
||||
if (inventoryPanel != null)
|
||||
inventoryPanel.SetActive(!inventoryPanel.activeSelf);
|
||||
}
|
||||
|
||||
public void SetManager(InventoryManager manager)
|
||||
{
|
||||
if (inventoryManager == manager)
|
||||
return;
|
||||
|
||||
Unsubscribe();
|
||||
inventoryManager = manager;
|
||||
Subscribe();
|
||||
RefreshAllSlots();
|
||||
}
|
||||
|
||||
public void RefreshAllSlots()
|
||||
{
|
||||
if (inventoryManager == null || slots == null)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < slots.Length; i++)
|
||||
{
|
||||
InventorySlotUI slot = slots[i];
|
||||
if (slot == null)
|
||||
continue;
|
||||
|
||||
int count = inventoryManager.GetItemCount(slot.ItemType);
|
||||
slot.SetCount(count, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshSlot(InventoryItemType itemType, int count)
|
||||
{
|
||||
if (slots == null)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < slots.Length; i++)
|
||||
{
|
||||
InventorySlotUI slot = slots[i];
|
||||
if (slot == null || slot.ItemType != itemType)
|
||||
continue;
|
||||
|
||||
slot.SetCount(count, showNewBadgeOnIncrease);
|
||||
}
|
||||
}
|
||||
|
||||
private void Subscribe()
|
||||
{
|
||||
if (subscribed || inventoryManager == null)
|
||||
return;
|
||||
|
||||
inventoryManager.ItemCountChanged += RefreshSlot;
|
||||
subscribed = true;
|
||||
}
|
||||
|
||||
private void Unsubscribe()
|
||||
{
|
||||
if (!subscribed || inventoryManager == null)
|
||||
return;
|
||||
|
||||
inventoryManager.ItemCountChanged -= RefreshSlot;
|
||||
subscribed = false;
|
||||
}
|
||||
}
|
||||
2
Assets/My project/Inventory/Scripts/InventoryUI.cs.meta
Normal file
2
Assets/My project/Inventory/Scripts/InventoryUI.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 91d9260d1c4221a438aed201fd7f6947
|
||||
80
Assets/My project/Inventory/Scripts/ItemPickup.cs
Normal file
80
Assets/My project/Inventory/Scripts/ItemPickup.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
/// <summary>
|
||||
/// 월드에 떠 있는 아이템 오브젝트를 직접 주울 때 사용합니다.
|
||||
/// Collider의 Is Trigger를 켜고, Player 태그와 충돌하면 인벤토리에 추가됩니다.
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
public class ItemPickup : MonoBehaviour
|
||||
{
|
||||
[Header("Reference")]
|
||||
[SerializeField] private InventoryManager inventoryManager;
|
||||
|
||||
[Header("Item")]
|
||||
[SerializeField] private InventoryItemType itemType = InventoryItemType.Fish;
|
||||
[SerializeField] private int amount = 1;
|
||||
|
||||
[Header("Pickup Settings")]
|
||||
[SerializeField] private string playerTag = "Player";
|
||||
[SerializeField] private bool destroyAfterPickup = true;
|
||||
[SerializeField] private bool disableAfterPickup = false;
|
||||
[SerializeField] private bool autoFindManager = true;
|
||||
|
||||
[Header("Events")]
|
||||
public UnityEvent onPickedUp;
|
||||
|
||||
[Header("Debug")]
|
||||
[SerializeField] private bool showDebugLog = true;
|
||||
|
||||
private bool pickedUp;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (inventoryManager == null && autoFindManager)
|
||||
inventoryManager = FindFirstObjectByType<InventoryManager>();
|
||||
}
|
||||
|
||||
private void OnTriggerEnter(Collider other)
|
||||
{
|
||||
if (pickedUp)
|
||||
return;
|
||||
|
||||
if (!other.CompareTag(playerTag))
|
||||
return;
|
||||
|
||||
PickUp();
|
||||
}
|
||||
|
||||
public void PickUp()
|
||||
{
|
||||
if (pickedUp)
|
||||
return;
|
||||
|
||||
pickedUp = true;
|
||||
|
||||
if (inventoryManager == null)
|
||||
{
|
||||
Debug.LogWarning("[ItemPickup] InventoryManager가 연결되지 않았습니다.");
|
||||
pickedUp = false;
|
||||
return;
|
||||
}
|
||||
|
||||
int addAmount = Mathf.Max(1, amount);
|
||||
inventoryManager.AddItem(itemType, addAmount);
|
||||
onPickedUp?.Invoke();
|
||||
|
||||
if (showDebugLog)
|
||||
Debug.Log($"[ItemPickup] {itemType} +{addAmount}");
|
||||
|
||||
if (destroyAfterPickup)
|
||||
Destroy(gameObject);
|
||||
else if (disableAfterPickup)
|
||||
gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
public void ResetPickup()
|
||||
{
|
||||
pickedUp = false;
|
||||
}
|
||||
}
|
||||
2
Assets/My project/Inventory/Scripts/ItemPickup.cs.meta
Normal file
2
Assets/My project/Inventory/Scripts/ItemPickup.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0ff46cf1114d1d4428b9657ea0c386a4
|
||||
85
Assets/My project/Inventory/Scripts/ItemReward.cs
Normal file
85
Assets/My project/Inventory/Scripts/ItemReward.cs
Normal file
@@ -0,0 +1,85 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
/// <summary>
|
||||
/// 미니게임 성공, 퀘스트 완료, 버튼 이벤트 등에서 아이템을 지급할 때 사용합니다.
|
||||
/// 예: 낚시 성공 이벤트 -> ItemReward.GiveReward()
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
public class ItemReward : MonoBehaviour
|
||||
{
|
||||
[Header("Reference")]
|
||||
[SerializeField] private InventoryManager inventoryManager;
|
||||
|
||||
[Header("Reward")]
|
||||
[SerializeField] private InventoryItemType rewardItemType = InventoryItemType.Fish;
|
||||
[SerializeField] private int rewardAmount = 1;
|
||||
[SerializeField] private bool giveOnlyOnce = false;
|
||||
|
||||
[Header("Auto Find")]
|
||||
[SerializeField] private bool autoFindManager = true;
|
||||
|
||||
[Header("Events")]
|
||||
public UnityEvent onRewardGiven;
|
||||
public UnityEvent onRewardAlreadyGiven;
|
||||
|
||||
[Header("Debug")]
|
||||
[SerializeField] private bool showDebugLog = true;
|
||||
|
||||
private bool alreadyGiven;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (inventoryManager == null && autoFindManager)
|
||||
inventoryManager = FindFirstObjectByType<InventoryManager>();
|
||||
}
|
||||
|
||||
public void GiveReward()
|
||||
{
|
||||
if (giveOnlyOnce && alreadyGiven)
|
||||
{
|
||||
onRewardAlreadyGiven?.Invoke();
|
||||
|
||||
if (showDebugLog)
|
||||
Debug.Log("[ItemReward] 이미 지급된 아이템 보상입니다.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (inventoryManager == null)
|
||||
{
|
||||
Debug.LogWarning("[ItemReward] InventoryManager가 연결되지 않았습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
int amount = Mathf.Max(1, rewardAmount);
|
||||
inventoryManager.AddItem(rewardItemType, amount);
|
||||
alreadyGiven = true;
|
||||
|
||||
onRewardGiven?.Invoke();
|
||||
|
||||
if (showDebugLog)
|
||||
Debug.Log($"[ItemReward] {rewardItemType} +{amount}");
|
||||
}
|
||||
|
||||
public void GiveRewardAmount(int amount)
|
||||
{
|
||||
rewardAmount = Mathf.Max(1, amount);
|
||||
GiveReward();
|
||||
}
|
||||
|
||||
public void ResetRewardState()
|
||||
{
|
||||
alreadyGiven = false;
|
||||
}
|
||||
|
||||
public void SetRewardItemType(InventoryItemType itemType)
|
||||
{
|
||||
rewardItemType = itemType;
|
||||
}
|
||||
|
||||
public void SetInventoryManager(InventoryManager manager)
|
||||
{
|
||||
inventoryManager = manager;
|
||||
}
|
||||
}
|
||||
2
Assets/My project/Inventory/Scripts/ItemReward.cs.meta
Normal file
2
Assets/My project/Inventory/Scripts/ItemReward.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 014ed6d95d853964f813511684df367a
|
||||
359
Assets/My project/Inventory/Scripts/README_KR.md
Normal file
359
Assets/My project/Inventory/Scripts/README_KR.md
Normal file
@@ -0,0 +1,359 @@
|
||||
# 인벤토리 UI 스크립트 설명서
|
||||
|
||||
이 패키지는 Unity 인벤토리 HUD를 만들기 위한 스크립트 세트입니다.
|
||||
아이템 개수 관리와 UI 표시를 분리해서, 낚시/리듬게임/미로 같은 다른 시스템과 연결하기 쉽게 만든 구조입니다.
|
||||
|
||||
## 포함 파일
|
||||
|
||||
| 파일 | 역할 |
|
||||
|---|---|
|
||||
| `InventoryItemType.cs` | 아이템 종류 enum 정의 |
|
||||
| `InventoryManager.cs` | 실제 아이템 개수 관리 |
|
||||
| `InventoryUI.cs` | 전체 인벤토리 UI 갱신 |
|
||||
| `InventorySlotUI.cs` | 슬롯 하나의 아이콘/개수/NEW 표시 관리 |
|
||||
| `ItemReward.cs` | 미니게임 성공, 이벤트 완료 시 아이템 지급 |
|
||||
| `ItemPickup.cs` | 월드 오브젝트를 직접 주워서 아이템 획득 |
|
||||
| `InventoryTooltipUI.cs` | 아이템 설명 툴팁 표시 |
|
||||
|
||||
필수로 먼저 쓰는 파일은 아래 5개입니다.
|
||||
|
||||
```text
|
||||
InventoryItemType.cs
|
||||
InventoryManager.cs
|
||||
InventoryUI.cs
|
||||
InventorySlotUI.cs
|
||||
ItemReward.cs
|
||||
```
|
||||
|
||||
선택 파일은 아래 2개입니다.
|
||||
|
||||
```text
|
||||
ItemPickup.cs
|
||||
InventoryTooltipUI.cs
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 권장 폴더 구조
|
||||
|
||||
```text
|
||||
Assets
|
||||
└── My project
|
||||
└── Inventory
|
||||
├── Scripts
|
||||
├── Sprites
|
||||
└── Prefabs
|
||||
```
|
||||
|
||||
스크립트는 여기에 넣습니다.
|
||||
|
||||
```text
|
||||
Assets/My project/Inventory/Scripts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Unity Hierarchy 예시
|
||||
|
||||
```text
|
||||
InventorySystem
|
||||
└── InventoryManager
|
||||
|
||||
InventoryCanvas
|
||||
└── InventoryMiniUI
|
||||
├── InventoryUI
|
||||
├── InventoryBG
|
||||
├── TitleText
|
||||
├── ItemSlotRoot
|
||||
│ ├── FishSlot
|
||||
│ │ ├── SlotBG
|
||||
│ │ ├── ItemIcon
|
||||
│ │ ├── CountText
|
||||
│ │ └── NewBadge
|
||||
│ ├── CompassSlot
|
||||
│ ├── TrashSlot
|
||||
│ └── BottleSlot
|
||||
└── TooltipText 또는 TooltipPanel
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 1. InventorySystem 만들기
|
||||
|
||||
Hierarchy에서 빈 오브젝트를 만듭니다.
|
||||
|
||||
```text
|
||||
Create Empty → InventorySystem
|
||||
```
|
||||
|
||||
`InventorySystem`에 붙일 스크립트:
|
||||
|
||||
```text
|
||||
InventoryManager.cs
|
||||
```
|
||||
|
||||
Inspector 설정:
|
||||
|
||||
```text
|
||||
Initial Items: 테스트용 시작 아이템을 넣고 싶으면 사용
|
||||
Show Debug Log: 체크
|
||||
```
|
||||
|
||||
예시:
|
||||
|
||||
```text
|
||||
Fish 1
|
||||
OldCompass 0
|
||||
Trash 0
|
||||
Bottle 0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. InventoryMiniUI에 InventoryUI 붙이기
|
||||
|
||||
`InventoryCanvas > InventoryMiniUI` 오브젝트에 붙입니다.
|
||||
|
||||
```text
|
||||
InventoryUI.cs
|
||||
```
|
||||
|
||||
Inspector 연결:
|
||||
|
||||
```text
|
||||
Inventory Manager → InventorySystem
|
||||
Inventory Panel → InventoryMiniUI
|
||||
Slots Size → 슬롯 개수만큼 설정
|
||||
```
|
||||
|
||||
예시:
|
||||
|
||||
```text
|
||||
Slots Size: 4
|
||||
Element 0 → FishSlot
|
||||
Element 1 → CompassSlot
|
||||
Element 2 → TrashSlot
|
||||
Element 3 → BottleSlot
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 각 슬롯에 InventorySlotUI 붙이기
|
||||
|
||||
각 슬롯 오브젝트에 붙입니다.
|
||||
|
||||
```text
|
||||
FishSlot → InventorySlotUI.cs
|
||||
CompassSlot → InventorySlotUI.cs
|
||||
TrashSlot → InventorySlotUI.cs
|
||||
BottleSlot → InventorySlotUI.cs
|
||||
```
|
||||
|
||||
각 슬롯의 Inspector 연결:
|
||||
|
||||
```text
|
||||
Item Type → Fish / OldCompass / Trash / Bottle
|
||||
Slot Background → SlotBG
|
||||
Item Icon → ItemIcon
|
||||
Count Text → CountText
|
||||
New Badge → NewBadge
|
||||
Canvas Group → 슬롯 오브젝트에 CanvasGroup이 있으면 연결
|
||||
Tooltip UI → InventoryTooltipUI를 쓸 경우 연결
|
||||
```
|
||||
|
||||
각 슬롯의 아이템 타입 예시:
|
||||
|
||||
```text
|
||||
FishSlot: Fish
|
||||
CompassSlot: OldCompass
|
||||
TrashSlot: Trash
|
||||
BottleSlot: Bottle
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 테스트 버튼으로 확인하기
|
||||
|
||||
임시 UI Button을 만들고 Button OnClick에 아래 함수를 연결합니다.
|
||||
|
||||
```text
|
||||
InventorySystem → InventoryManager.AddItem(InventoryItemType, int)
|
||||
```
|
||||
|
||||
Unity 이벤트에서 enum+int가 바로 안 보일 수 있으므로, 테스트는 `ItemReward`를 쓰는 방식이 더 쉽습니다.
|
||||
|
||||
테스트 방법:
|
||||
|
||||
1. 빈 오브젝트 `TestFishReward` 생성
|
||||
2. `ItemReward.cs` 붙이기
|
||||
3. 설정:
|
||||
|
||||
```text
|
||||
Inventory Manager → InventorySystem
|
||||
Reward Item Type → Fish
|
||||
Reward Amount → 1
|
||||
Give Only Once → 해제
|
||||
```
|
||||
|
||||
4. 테스트 버튼 OnClick에 연결:
|
||||
|
||||
```text
|
||||
TestFishReward → ItemReward.GiveReward()
|
||||
```
|
||||
|
||||
Play 후 버튼을 누르면 FishSlot의 CountText가 `x1`, `x2`로 증가해야 합니다.
|
||||
|
||||
---
|
||||
|
||||
## 5. 미니게임 성공 보상 연결
|
||||
|
||||
예: 낚시 성공 시 생선 지급
|
||||
|
||||
`FishingGameSystem`에 `ItemReward.cs`를 붙입니다.
|
||||
|
||||
```text
|
||||
Reward Item Type → Fish
|
||||
Reward Amount → 1
|
||||
Give Only Once → 필요에 따라 체크
|
||||
```
|
||||
|
||||
낚시 게임 매니저의 성공 이벤트에 연결:
|
||||
|
||||
```text
|
||||
On Fishing Success
|
||||
→ FishingGameSystem
|
||||
→ ItemReward.GiveReward()
|
||||
```
|
||||
|
||||
예: 리듬게임 시작 전 생선 보유 확인은 별도 게임 매니저에서 처리합니다.
|
||||
|
||||
```csharp
|
||||
if (inventoryManager.HasItem(InventoryItemType.Fish))
|
||||
{
|
||||
rhythmGameManager.StartGameWithFishBonus();
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. ItemPickup 사용법
|
||||
|
||||
월드에 떠 있는 아이템을 직접 줍게 만들 때 사용합니다.
|
||||
|
||||
아이템 오브젝트에 필요한 것:
|
||||
|
||||
```text
|
||||
Collider
|
||||
Is Trigger 체크
|
||||
ItemPickup.cs
|
||||
```
|
||||
|
||||
Inspector 설정:
|
||||
|
||||
```text
|
||||
Inventory Manager → InventorySystem
|
||||
Item Type → Fish / OldCompass / Trash / Bottle
|
||||
Amount → 1
|
||||
Player Tag → Player
|
||||
Destroy After Pickup → 체크
|
||||
```
|
||||
|
||||
플레이어 오브젝트에는 `Player` 태그가 있어야 합니다.
|
||||
|
||||
---
|
||||
|
||||
## 7. Tooltip 사용법
|
||||
|
||||
툴팁을 쓰려면 UI에 이런 구조를 만듭니다.
|
||||
|
||||
```text
|
||||
InventoryMiniUI
|
||||
└── TooltipPanel
|
||||
├── TooltipTitleText
|
||||
├── TooltipDescriptionText
|
||||
└── TooltipCountText
|
||||
```
|
||||
|
||||
`TooltipPanel` 또는 별도 오브젝트에 붙입니다.
|
||||
|
||||
```text
|
||||
InventoryTooltipUI.cs
|
||||
```
|
||||
|
||||
Inspector 연결:
|
||||
|
||||
```text
|
||||
Tooltip Panel → TooltipPanel
|
||||
Title Text → TooltipTitleText
|
||||
Description Text → TooltipDescriptionText
|
||||
Count Text → TooltipCountText
|
||||
```
|
||||
|
||||
그리고 각 `InventorySlotUI`의 `Tooltip UI` 슬롯에 이 `InventoryTooltipUI`를 연결합니다.
|
||||
|
||||
---
|
||||
|
||||
## 초기 상태 추천
|
||||
|
||||
```text
|
||||
FishSlot CountText: x0
|
||||
CompassSlot CountText: x0
|
||||
TrashSlot CountText: x0
|
||||
BottleSlot CountText: x0
|
||||
NewBadge 전부 비활성화
|
||||
TooltipPanel 비활성화
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 자주 생기는 문제
|
||||
|
||||
### 슬롯 숫자가 안 바뀜
|
||||
|
||||
확인할 것:
|
||||
|
||||
```text
|
||||
InventoryUI의 Inventory Manager가 연결되어 있는가?
|
||||
InventoryUI의 Slots 배열에 각 슬롯이 들어가 있는가?
|
||||
각 슬롯에 InventorySlotUI가 붙어 있는가?
|
||||
각 InventorySlotUI의 Item Type이 올바른가?
|
||||
```
|
||||
|
||||
### NEW 표시가 계속 켜져 있음
|
||||
|
||||
`NewBadge` 오브젝트를 처음에는 비활성화하세요.
|
||||
`InventorySlotUI`가 아이템 증가 시 자동으로 잠깐 켰다가 끕니다.
|
||||
|
||||
### 0개 아이템이 너무 밝게 보임
|
||||
|
||||
각 슬롯에 `CanvasGroup`을 추가하고 `InventorySlotUI`의 `Canvas Group`에 연결하세요.
|
||||
`Dim When Zero`가 켜져 있으면 0개일 때 흐리게 보입니다.
|
||||
|
||||
### 아이템 줍기가 안 됨
|
||||
|
||||
확인할 것:
|
||||
|
||||
```text
|
||||
아이템 오브젝트에 Collider가 있는가?
|
||||
Is Trigger가 켜져 있는가?
|
||||
플레이어 오브젝트 태그가 Player인가?
|
||||
ItemPickup의 Inventory Manager가 연결되어 있는가?
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 최종 작업 순서
|
||||
|
||||
```text
|
||||
1. 스크립트들을 Inventory/Scripts 폴더에 넣기
|
||||
2. InventorySystem 오브젝트 만들기
|
||||
3. InventoryManager 붙이기
|
||||
4. InventoryMiniUI 만들기
|
||||
5. InventoryUI 붙이기
|
||||
6. FishSlot, CompassSlot, TrashSlot, BottleSlot 만들기
|
||||
7. 각 슬롯에 InventorySlotUI 붙이기
|
||||
8. InventoryUI의 Slots 배열에 슬롯 연결
|
||||
9. ItemReward로 테스트 보상 만들기
|
||||
10. Play 후 아이템 개수 증가 확인
|
||||
11. 낚시/리듬/미로 이벤트와 연결
|
||||
```
|
||||
7
Assets/My project/Inventory/Scripts/README_KR.md.meta
Normal file
7
Assets/My project/Inventory/Scripts/README_KR.md.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1bed071ffaa74d5499aa4f730b384f9c
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user