말감로그

[Unity] Valley - 인벤토리 시스템 본문

TIL

[Unity] Valley - 인벤토리 시스템

habbn 2024. 10. 16. 20:17
728x90

인벤토리 시스템

 

인벤토리 클래스를 정의해서 인벤토리 시스템을 관리할 것이다.

//Inventory.cs
[System.Serializable]
public class Inventory
{
    public List<Slot> slots = new List<Slot>();

    [System.Serializable]
    public class Slot
    {
        public CollectableType type;
        public Sprite icon;
        public int count;
        public int maxAllowed;

        public Slot()
        {
            type = CollectableType.NONE;
            count = 0;
            maxAllowed = 99;
        }
    }

    public Inventory(int numSlots)
    {
        for (int i = 0; i < numSlots; i++)
        {
            Slot slot = new Slot();
            slots.Add(slot);
        }
    }

 

List<Slot> slots 

Slot 객체들의 리스트로, 인벤토리에 들어갈 각 슬롯을 나타낸다.

 

Slot 클래스

인벤토리의 각 슬롯을 정의하는 내부 클래스로, 각 슬롯이 어떤 아이템을 담고 있는지, 아이템의 갯수, 아이콘 등을 저장한다.

 

Slot() 생성자

Slot 객체가 생성될 때 기본적으로 저 값으로 초기화된다.

 

Inventory(int numSlots) 생성자

인벤토리의 슬롯 수를 받아서 Slot 객체들을 해당 개수만큼 생성하여 slots 리스트에 추가한다.

인벤토리를 초기화할 때 이 생성자를 사용하여 빈 슬롯들이 채워진 인벤토리가 생성된다.

// Player.cs
void Awake()
{
    inventory = new Inventory(24);
}

 

실제 Player.cs에서 24개의 슬롯들이 채워진 인벤토리를 생성한다.

 

기본적인 설정을 마쳤으니 이제 아이템을 추가하고 삭제하는 일을 해야 한다.

 

슬롯에 아이템을 추가하고 삭제하는 역할을 하는 메서드들이다.

public class Slot
{
        :

    public bool CanAddItem()
    {
        if (count < maxAllowed)
        {
            return true;
        }
        return false;
    }

    public void AddItem(Collectable item)
    {
        this.type = item.type;
        this.icon = item.icon;
        count++;
    }

    public void RemoveItem()
    {
        if(count > 0)
        {
            count--;

            if(count == 0)
            {
                icon = null;
                type = CollectableType.NONE;
            }
        }
    }
}

 

인벤토리에 아이템을 추가하고 삭제하는 메서드들 

		:
    public void Add(Collectable item)
    {
        // 슬롯의 타입이 추가하려는 아이템의 타입과 같고 maxAllowed보다 적은 count이면
        foreach (Slot slot in slots)
        {
            if (slot.type == item.type && slot.CanAddItem())
            {
                slot.AddItem(item);
                return;
            }
        }
		
        // 같은 타입의 슬롯이 없거나 슬롯이 꽉차서 아이템으 추가할 수 없는 경우
        foreach (Slot slot in slots)
        {
            // 빈 슬롯이면
            if (slot.type == CollectableType.NONE)
            {
                slot.AddItem(item);
                return;
            }
        }
    }

    public void Remove(int index)
    {
        slots[index].RemoveItem();
    }
    public void Remove(int index, int numToRemove)
    {
        if(slots[index].count >= numToRemove)
        {
            for(int i = 0; i < numToRemove; i++)
            {
                Remove(index);
            }
        }
    }

 

 

 

슬롯에도 해당 아이템의 아이콘과 개수를 나타내기 위한 스크립트를 작성해서 붙여준다.

// Slot_UI.cs
using UnityEngine;
using UnityEngine.UI;

public class Slot_UI : MonoBehaviour
{
    public int slotID;
    public Image itemIcon;
    public Text quantityText;

    public void SetItem(Inventory.Slot slot)
    {
        if (slot != null)
        {
            itemIcon.sprite = slot.icon;
            itemIcon.color = new Color(1, 1, 1, 1);
            quantityText.text = slot.count.ToString();
        }
    }

    public void EmptyItem()
    {
        itemIcon.sprite = null;
        itemIcon.color = new Color(1, 1, 1, 0);
        quantityText.text = "";
    }
}

 

 

수집 가능한 아이템을 나타내는 스크립트를 작성해서 오브젝트의 타입을 정의하고, 플레이어에 닿으면 해당 오브젝트를 인벤토리에 추가하도록 한다.

// Collectable.cs
using UnityEngine;

public enum CollectableType { NONE, RICE_SEED }

public class Collectable : MonoBehaviour
{
    public CollectableType type;
    public Sprite icon;
    public Rigidbody2D rigid;

    void Start()
    {
        rigid = GetComponent<Rigidbody2D>();
    }

    void OnTriggerEnter2D(Collider2D other)
    {
        Player player = other.GetComponent<Player>();

        if (player)
        {
            player.inventory.Add(this);
            Destroy(this.gameObject);
        }
    }
}

 

 

인벤토리를 Tab 키로 껐다 키고 킬때마다 갱신을 시켜줘야 하기 때문에 Refresh() 함수를 통해서 인벤토리의 슬롯을 갱신해준다.

public class Inventory_UI : MonoBehaviour
{
    public Player player;
    public GameObject inventory;
    public List<Slot_UI> slots = new List<Slot_UI>();

    void Awake()
    {
        canvas = FindObjectOfType<Canvas>();
        inventory.SetActive(false);
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Tab))
        {
            ToggleInventory();
        }
    }

    public void ToggleInventory()
    {
        if (!inventory.activeSelf)
        {
            inventory.SetActive(true);
            Refresh();
        }
        else
        {
            inventory.SetActive(false);
        }
    }

    // 인벤토리 갱신
    void Refresh()
    {
        if (slots.Count == player.inventory.slots.Count)
        {
            for (int i = 0; i < slots.Count; i++)
            {
                if (player.inventory.slots[i].type != CollectableType.NONE)
                {
                    slots[i].SetItem(player.inventory.slots[i]);
                }
                else
                {
                    slots[i].EmptyItem();
                }
            }
        }
    }
}

 

 

여기까지 하면 플레이어가 아이템을 먹으면 인벤토리에 갯수와 함께 잘 들어가 있는 모습을 볼 수 있다.

 

이제 드래그앤 드랍으로 슬롯을 끌어다가 밖에 놓으면 플레이어 주변으로 아이템이 떨어져있는 모습을 구현할 것이다.

그러기 위해선 Slot 프리팹에 Event Trigger 컴포넌트를 추가해줘야 한다.

 

Event Trigger는 다양한 UI 이벤트를 감지하고 이에 대응할 수 있게 도와준다.

 

1. SlotBeginDrag(Slot_UI slot)

 

슬롯 드래그 시작시 호출되며, 드래그할 슬롯과 아이콘을 준비한다.

public void SlotBeginDrag(Slot_UI slot)
{
    draggedSlot = slot;	 //드래그할 슬롯 저장
    draggedIcon = Instantiate(draggedSlot.itemIcon);   // 슬롯의 아이콘 복사
    draggedIcon.transform.SetParent(canvas.transform);  // 캔버스의 자식으로 설정
    draggedIcon.raycastTarget = false;  // 아이콘이 레이캐스트를 받지 않도록 설정
    draggedIcon.rectTransform.sizeDelta = new Vector2(100, 100);  // 아이콘 크기

    MoveToMousePosition(draggedIcon.gameObject);  // 아이콘을 마우스 위치로 이동
    Debug.Log("Start Drag:" + draggedSlot.name);
}

 

2. SlotDrag()

 

드래그 중 아이콘의 위치를 업데이트한다.

public void SlotDrag()
{
    MoveToMousePosition(draggedIcon.gameObject); // 드래그 중 아이콘 위치 업데이트
    Debug.Log("Dragging:" + draggedSlot.name); // 드래그 중인 슬롯 로그 출력
}

 

 

3. SlotEndDrag()

 

드래그가 끝났을 때 호출되며, 드래그된 아이콘을 정리한다.

public void SlotEndDrag()
{
    Destroy(draggedIcon.gameObject); // 드래그가 끝나면 아이콘 삭제
    draggedIcon = null; // 드래그된 아이콘 변수 초기화
}

 

4.SlotDrop(Slot_UI Slot)

 

아이콘이 다른 슬롯에 드롭될 때 호출된다.

아직 이건 구현 X

public void SlotDrop(Slot_UI slot)
{
    Debug.Log("Dropped :" + draggedSlot.name + " on " + slot.name); // 드롭된 슬롯 로그 출력
}

 

5. MoveToMousePosition(GameObject toMove)

 

주어진 게임 오브젝트를 마우스 위치로 이동한다.

public void MoveToMousePosition(GameObject toMove)
{
    if (canvas != null)
    {
        Vector2 position;

        RectTransformUtility.ScreenPointToLocalPointInRectangle(canvas.transform as RectTransform, Input.mousePosition, null, out position); // 마우스 위치를 로컬 좌표로 변환

        toMove.transform.position = canvas.transform.TransformPoint(position); // 변환된 위치로 이동
    }
}

 

RectTransformUtility.ScreenPointToLocalPointInRectangle

스크린상의 마우스 위치를 캔버스의 로컬 좌표로 변환한다. 이 방법은 UI 요소를 정확한 위치에 배치하는 데 필요하다.

 

TransformPoint

변환된 로컬 위치를 다시 세계 좌표로 변환

 

 

캔버스에서 아이템 제거 패널 오브젝트를 만들고 캔버스에 가득차게 설정한다.

 

UI 배경이 보이게끔 알파값은 0으로 설정하고,

Event Trigger - Drop - Inventory_UI.Remove() 함수를 추가한다.

 

 

RemoveItem_Panel에 드랍한 아이템을 플레이어 주변으로 떨어트리고 인벤토리에서 제거시킨다.

public void Remove()
{
    Collectable itemToDrop = GameManager.instance.itemManager.GetItemByType(player.inventory.slots[draggedSlot.slotID].type);

    if (itemToDrop != null)
    {
        if (dragSingle)
        {
            player.DropItem(itemToDrop);
            player.inventory.Remove(draggedSlot.slotID);
        }
        else
        {
            player.DropItem(itemToDrop, player.inventory.slots[draggedSlot.slotID].count);
            player.inventory.Remove(draggedSlot.slotID, player.inventory.slots[draggedSlot.slotID].count);
        }
        Refresh();
    }

    draggedSlot = null;
}

 

 

플레이어 주변에서 점차 멀어지게끔 떨어지는 효과를 주기 위한 

// Player.cs
public void DropItem(Collectable item)
{
    Vector3 spawnLocation = transform.position;

    Vector3 spawnOffset = Random.insideUnitCircle * 2f;

    Collectable droppedItem = Instantiate(item, spawnLocation + spawnOffset, Quaternion.identity);

    droppedItem.rigid.AddForce(spawnOffset * 0.3f, ForceMode2D.Impulse);
}

 

728x90