지금 아이템코드는 아이템의 타입에 따라 Switch문을 통해 다른 사용효과를 부여하고 있는데 이렇게 하면 나중에 코드가 복잡해질 수 도 있고 확장성에 좋지 않다고 생각해서 인터페이스를 만들고 이를 상속받는 다양한 아이템 클래스를 만들어주겠다.
IItemBase.cs
using Controllers.Entity;
using UnityEngine;
using static Define;
public interface IItemBase
{
ItemType ItemType { get; } // 아이템 유형
ItemData ItemData { get; set; }
int Quantity { get; }
void Use(); // 아이템 사용
void Initialize();
}
이를 상속받는 장비 클래스와 소모품 클래스를 만들어주자.
이때 각각의 클래스는 주울 수 있어야하고 실제 프리펩에 부착되어야 하기 때문에 Monobehaviour와 IPickable 또한 상속받도록 해주었다.
Equiment.cs
using Controllers.Entity;
using UnityEngine;
using static Define;
public class Equipment : MonoBehaviour, IItemBase, IPickable
{
[SerializeField] private ItemData itemData; // Inspector에서 할당할 수 있는 필드
[SerializeField] private int quantity;
public ItemType ItemType => ItemType.Equipment;
public ItemData ItemData
{
get => itemData;
set => itemData = value;
}
public int Quantity
{
get => quantity;
set => quantity = 1;
}
public void Initialize()
{
if (ItemData == null)
{
Debug.LogError("ItemData가 설정되지 않았습니다.");
return;
}
// 공통 초기화 로직 (예: 아이콘 설정)
var spriteRenderer = GetComponent<SpriteRenderer>();
if (spriteRenderer != null && ItemData.icon != null)
{
spriteRenderer.sprite = ItemData.icon;
}
}
public void Pickup()
{
bool addedSuccessfully = Managers.Inventory.TryAddItem(this.ItemData.itemID,quantity);
if (addedSuccessfully)
{
Destroy(gameObject);
}
else
{
Debug.LogWarning("아이템을 인벤토리에 추가할 수 없습니다.");
}
}
public void Use()
{
if( Managers.Inventory.EquippedWeapon != null )
{
Managers.Inventory.UnEquipCurrentWeapon();
}
else
{
Managers.Inventory.EquipWeapon(this.ItemData.itemID);
}
}
}
Consumable.cs
using Controllers.Entity;
using UnityEngine;
using static Define;
public class Consumable : MonoBehaviour, IItemBase, IPickable
{
[SerializeField] private ItemData itemData; // Inspector에서 할당할 수 있는 필드
[SerializeField] private int quantity;
public ItemType ItemType => ItemType.Consumable;
public ItemData ItemData
{
get => itemData;
set => itemData = value;
}
public int Quantity
{
get=> quantity;
set => quantity = value;
}
public void Initialize()
{
if (ItemData == null)
{
Debug.LogError("ItemData가 설정되지 않았습니다.");
return;
}
// 공통 초기화 로직 (예: 아이콘 설정)
var spriteRenderer = GetComponent<SpriteRenderer>();
if (spriteRenderer != null && ItemData.icon != null)
{
spriteRenderer.sprite = ItemData.icon;
}
}
public void Pickup()
{
bool addedSuccessfully = Managers.Inventory.TryAddItem(this.ItemData.itemID,quantity);
if (addedSuccessfully)
{
Destroy(gameObject);
}
else
{
Debug.LogWarning("아이템을 인벤토리에 추가할 수 없습니다.");
}
}
public void Use()
{
Debug.Log($"장비 아이템 '{ItemData.itemName}'를 장착합니다.");
Managers.Inventory.UseItem(this.ItemData.itemID);
}
}
이를 실제 프리펩에 부착하고 테스트해보자.
이렇게 나타나는 것을 볼 수 있다. 이에 맞춰 인벤토리 코드도 수정을 해주자. 주요한 수정사항은 아이템 추가시에 맵에 배치된 아이템과 관련된 정보를 가져와서 추가해준다는 점이다. 이때 나는 계속 아이템을 찾는 부하를 줄이기 위해 시작시에 모든 아이템의정보를 캐싱해서 사용했다.
using System;
using System.Collections.Generic;
using UnityEngine;
public class InventoryManager : MonoBehaviour
{
private Dictionary<int, ItemSlot> ownedItems = new Dictionary<int, ItemSlot>(); // 소유한 아이템과 수량
private Dictionary<int, ItemData> itemDataCache = new Dictionary<int, ItemData>(); // 캐싱된 아이템 데이터
private Dictionary<int, IItemBase> ItemCache = new Dictionary<int, IItemBase>(); // 캐싱된 아이템 데이터
public Equipment EquippedWeapon { get; private set; }
public void Init()
{
CacheItemData();
EquippedWeapon = null;
}
// 1. 아이템 데이터를 캐싱하여 메모리에 저장
private void CacheItemData()
{
ItemData[] items = Resources.LoadAll<ItemData>("Items");
GameObject[] itemDatas = Resources.LoadAll<GameObject>("Prefabs/Items");
foreach (var item in items)
{
if (!itemDataCache.ContainsKey(item.itemID))
{
//Debug.Log(item.itemName + "아이템 데이터 캐싱 완료");
itemDataCache[item.itemID] = item;
}
}
foreach (var item in itemDatas)
{
IItemBase itemBase = item.GetComponent<IItemBase>();
if (!ItemCache.ContainsKey(itemBase.ItemData.itemID))
{
Debug.Log(itemBase.ItemData.itemName + "아이템 데이터 캐싱 완료");
ItemCache[itemBase.ItemData.itemID] = itemBase;
}
}
}
// 2. 아이템 데이터 검색
public ItemData FindItemDataByID(int itemID)
{
if (itemDataCache.TryGetValue(itemID, out var itemData))
{
return itemData;
}
Debug.LogWarning($"아이템 데이터를 찾을 수 없습니다: ID = {itemID}");
return null;
}
public IItemBase FindItemBaseByID(int itemID)
{
if (ItemCache.TryGetValue(itemID, out var itemBase))
{
return itemBase;
}
Debug.LogWarning($"아이템 데이터를 찾을 수 없습니다: ID = {itemID}");
return null;
}
// 3. 아이템 추가
public bool TryAddItem(int itemID, int quantity = 1)
{
// 아이템 데이터를 가져옴
if (!itemDataCache.TryGetValue(itemID, out var itemData))
{
Debug.LogWarning($"추가하려는 아이템 데이터(ID: {itemID})가 유효하지 않습니다.");
return false;
}
// 기존 아이템이 있는 경우 수량 증가
if (ownedItems.TryGetValue(itemID, out var existingSlot))
{
existingSlot.Quantity += quantity;
Debug.Log($"아이템 '{itemData.itemName}'의 수량이 {existingSlot.Quantity}로 증가했습니다.");
}
else
{
// 새로운 아이템 슬롯 추가
ownedItems[itemID] = new ItemSlot(FindItemBaseByID(itemID), quantity);
Debug.Log($"새로운 아이템 '{itemData.itemName}'이(가) {quantity}개 추가되었습니다.");
}
RefreshUI();
return true;
}
// 4. 아이템 제거
public void RemoveItem(int itemID, int quantity = 1)
{
if (!ownedItems.TryGetValue(itemID, out var itemSlot))
{
Debug.LogWarning($"제거하려는 아이템(ID: {itemID})이(가) 없습니다.");
return;
}
itemSlot.Quantity -= quantity;
if (itemSlot.Quantity <= 0)
{
ownedItems.Remove(itemID);
Debug.Log($"아이템 '{FindItemDataByID(itemID)?.itemName}'이(가) 인벤토리에서 제거되었습니다.");
}
else
{
Debug.Log($"아이템 '{FindItemDataByID(itemID)?.itemName}'의 수량이 {itemSlot.Quantity}로 줄었습니다.");
}
RefreshUI();
}
// 5. 아이템 사용
public void UseItem(int itemID)
{
if (!ownedItems.TryGetValue(itemID, out var itemSlot))
{
Debug.LogWarning($"사용하려는 아이템(ID: {itemID})이(가) 없습니다.");
return;
}
var itemData = FindItemDataByID(itemID);
if (itemData == null)
{
Debug.LogWarning($"아이템 데이터를 찾을 수 없습니다: ID = {itemID}");
return;
}
Debug.Log($"아이템 '{itemData.itemName}' 사용됨.");
itemSlot.Item?.Use();
// 소비 아이템은 사용 후 제거
if (itemData.itemType != Define.ItemType.Equipment)
{
RemoveItem(itemID, 1);
}
RefreshUI();
}
// 6. 무기 장착
public void EquipWeapon(int itemID)
{
if (!ownedItems.TryGetValue(itemID, out var itemSlot))
{
Debug.LogWarning($"장착하려는 무기(ID: {itemID})이(가) 없습니다.");
return;
}
if (EquippedWeapon != null)
{
UnEquipCurrentWeapon();
}
EquippedWeapon = (Equipment)itemSlot.Item;
RemoveItem(itemID, 1);
Debug.Log($"무기 '{EquippedWeapon.ItemData.itemName}' 장착 완료.");
RefreshUI();
}
// 7. 장착 해제
public void UnEquipCurrentWeapon()
{
if (EquippedWeapon == null)
{
Debug.LogWarning("장착된 무기가 없습니다.");
return;
}
TryAddItem(EquippedWeapon.ItemData.itemID, 1);
Debug.Log($"무기 '{EquippedWeapon.ItemData.itemName}' 해제 완료.");
EquippedWeapon = null;
RefreshUI();
}
// 8. 인벤토리 UI 갱신
public void RefreshUI()
{
UI_ItemSel uI_ItemSel = FindAnyObjectByType<UI_ItemSel>();
if (uI_ItemSel != null)
{
Managers.UI.ClosePopupUI(uI_ItemSel);
}
UI_Inventory inventoryUI = Managers.UI.GetTopPopupUI() as UI_Inventory;
if (inventoryUI != null)
{
inventoryUI.RefreshUI();
}
}
// 9. 저장
public void SaveInventory(ref SaveData saveData)
{
saveData.inventoryData = new List<SerializedItemSlot>();
foreach (var slot in ownedItems)
{
saveData.inventoryData.Add(new SerializedItemSlot
{
itemID = slot.Key,
quantity = slot.Value.Quantity
});
}
saveData.equippedWeaponID = EquippedWeapon?.ItemData.itemID ?? -1;
}
// 10. 로드
public void LoadInventory(SaveData saveData)
{
if (saveData.inventoryData == null) return;
ownedItems.Clear();
foreach (var serializedSlot in saveData.inventoryData)
{
var itemBase = FindItemBaseByID(serializedSlot.itemID);
if (itemBase != null)
{
ownedItems[serializedSlot.itemID] = new ItemSlot(itemBase, serializedSlot.quantity);
}
}
if (saveData.equippedWeaponID != -1)
{
var weaponData = FindItemBaseByID(saveData.equippedWeaponID);
Debug.Log("현재 장착된 무기 : " + weaponData.ItemData.itemName + " 로드완료");
if (weaponData != null)
{
EquippedWeapon = (Equipment)weaponData;
}
}
RefreshUI();
}
public List<ItemSlot> GetOwnedItems()
{
return new List<ItemSlot>(ownedItems.Values);
}
}
이제 이에 맞춰서 인벤토리의 장비칸, 아이템칸의 코드도 수정해주고
아이템을 선택했을 때 장착, 사용하기가 나오는 선택UI도 하나로 통합해주자.
SlotButton.cs
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
public class SlotButton : UI_Base, IPointerClickHandler
{
[SerializeField] private Image itemIcon;
[SerializeField] private TextMeshProUGUI itemName;
[SerializeField] private TextMeshProUGUI itemInfo;
[SerializeField] private TextMeshProUGUI itemCount;
[SerializeField] public ItemSlot currentItem;
public override void Init() { }
public void UpdateSlotUI(ItemSlot item)
{
currentItem = item;
if(item!= null)
{
Debug.Log(item.Item.ItemData.itemName + " " + item.Quantity);
}
if (currentItem == null)
{
ClearSlotUI();
return;
}
itemIcon.sprite = currentItem.Item.ItemData.icon;
itemIcon.gameObject.SetActive(true);
itemName.text = currentItem.Item.ItemData.itemName;
itemCount.text = $"{currentItem.Quantity} 개";
}
private void ClearSlotUI()
{
currentItem = null;
itemIcon.gameObject.SetActive(false);
itemName.text = "";
itemCount.text = "";
}
public void OnPointerClick(PointerEventData eventData)
{
Debug.Log("클릭 이벤트 호출!");
UI_ItemSel uI_ItemSel = FindAnyObjectByType<UI_ItemSel>();
if (uI_ItemSel == null)
{
uI_ItemSel = Managers.UI.ShowPopupUI<UI_ItemSel>();
uI_ItemSel.transform.SetParent(transform, false);
Vector3 location = eventData.position;
location.y -= 50;
uI_ItemSel.transform.SetPositionAndRotation(location, Quaternion.identity);
}
else
{
Managers.UI.ClosePopupUI(uI_ItemSel);
}
}
}
EquipSlot.cs
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
public class EquipSlot : UI_Base, IPointerClickHandler
{
[SerializeField] private Image itemIcon; // 아이템 아이콘 표시
[SerializeField] private TextMeshProUGUI itemName; // 아이템 이름
[SerializeField] private TextMeshProUGUI itemInfo; // 아이템 설명
[SerializeField] private TextMeshProUGUI AttackInfo; // 공격력 설명
public Equipment currentItem; // 현재 슬롯에 연결된 아이템 데이터
public override void Init()
{
Equipment equippedItem = Managers.Inventory.EquippedWeapon;
if (equippedItem != null)
{
UpdateSlotUI(equippedItem);
}
else
{
ClearSlotUI();
}
}
// 슬롯 UI 업데이트
public void UpdateSlotUI(Equipment item)
{
currentItem = item;
if (currentItem != null)
{
itemIcon.sprite = currentItem.ItemData.icon;
itemIcon.gameObject.SetActive(true);
itemName.text = currentItem.ItemData.itemName;
itemInfo.text = currentItem.ItemData.description;
if (currentItem is Equipment equipment)
{
AttackInfo.text = $"공격력: {equipment.ItemData.AttackDamage}";
}
else
{
AttackInfo.text = "";
}
}
else
{
ClearSlotUI();
}
}
// 슬롯 UI 초기화
private void ClearSlotUI()
{
currentItem = null;
itemIcon.gameObject.SetActive(false);
itemName.text = "장착 없음";
itemInfo.text = "";
AttackInfo.text = "";
}
public void OnPointerClick(PointerEventData eventData)
{
Debug.Log("클릭 이벤트 호출!");
if (currentItem == null) return;
UI_ItemSel equipPopup = FindAnyObjectByType<UI_ItemSel>();
if (equipPopup == null)
{
equipPopup = Managers.UI.ShowPopupUI<UI_ItemSel>();
equipPopup.transform.SetParent(transform, false);
Vector3 location = eventData.position;
location.y -= 50;
equipPopup.transform.SetPositionAndRotation(location, Quaternion.identity);
}
else
{
Managers.UI.ClosePopupUI(equipPopup);
}
}
}
UI_ItemSel.cs
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
public class UI_ItemSel : UI_Popup
{
public enum Buttons
{
UseButton,
RemoveButton,
}
public override void Init()
{
Bind<Button>(typeof(Buttons));
//현재 장비를 클릭했을 때
if (transform.parent.GetComponent<EquipSlot>() != null)
{
GetButton((int)Buttons.UseButton).GetComponentInChildren<TextMeshProUGUI>().text = "장착해제";
}
else //인벤토리 창을 클릭했을 때
{
if (GetComponentInParent<SlotButton>().currentItem.ItemType == Define.ItemType.Equipment)
{
GetButton((int)Buttons.UseButton).GetComponentInChildren<TextMeshProUGUI>().text = "장착하기";
}
else if (GetComponentInParent<SlotButton>().currentItem.ItemType == Define.ItemType.Consumable)
{
GetButton((int)Buttons.UseButton).GetComponentInChildren<TextMeshProUGUI>().text = "사용하기";
}
else
{
GetButton((int)Buttons.UseButton).GetComponentInChildren<TextMeshProUGUI>().text = "";
}
}
GetButton((int)Buttons.UseButton).gameObject.AddUIEvent(UseItem);
}
void UseItem(PointerEventData eventData)
{
//현재 장비를 클릭했을 때
if (transform.parent.GetComponent<EquipSlot>() != null)
{
GetComponentInParent<EquipSlot>().currentItem.Use();
}
else
{
GetComponentInParent<SlotButton>().currentItem.Use();
}
}
}
이렇게 해주고 테스트해보면 정상적으로 작동하는 것을 볼 수 있다.
실행화면
'게임공부 > Unity' 카테고리의 다른 글
[C#][Unity][팬 게임]카드 짝 맞추기 게임 만들기1 (0) | 2025.02.03 |
---|---|
[C#][Unity][나만의 탑뷰 게임 만들기]9. 적 시스템구현1(FSM) (0) | 2025.01.30 |
[C#][디자인 패턴]State 패턴 (0) | 2025.01.09 |
[Unity]최적화 관련 팁 (0) | 2025.01.08 |
[C#][Unity][나만의 탑뷰 게임 만들기]7. 인벤토리 시스템 만들기2 (0) | 2025.01.02 |