오늘은 스파르타 코딩 클럽 unity 게임 개발 과정 43일차땜빵(인벤토리 시스템 구현)

2024. 1. 2. 03:09스파르타코딩클럽 게임개발

반응형

유니티 인벤토리 시스템 구현

오늘은 유튜브에서 공부한 내용을 바탕으로 유니티 게임에서 사용할 수 있는 기본적인 인벤토리 시스템을 구현해 보았다.

아직 완성본은 아니지만.. 프로젝트에 구현하기 위해 학습한 내용을 정리하겠다.

인벤토리 시스템의 핵심 구성 요소

인벤토리 시스템은 주로 다음과 같은 클래스들로 구성

  1. InventorySystem - 인벤토리의 핵심 기능을 관리
  2. InventorySlot - 인벤토리 내의 각각의 슬롯을 나타냄
  3. InventoryItemData - 인벤토리 아이템의 데이터를 저장함.
  4. InventoryPickUp - 아이템을 주워 인벤토리에 추가하는 기능을 담당함
  5. InventoryHolder - 인벤토리 시스템을 가지는 객체를 위한 클래스

InventorySystem 클래스

 

  • 인벤토리 슬롯의 초기화
  • 아이템을 인벤토리에 추가하는 기능
  • 인벤토리에 특정 아이템이 있는지 확인
  • 빈 슬롯이 있는지 확인
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using System.Linq;

[System.Serializable] // Unity 인스펙터에서 보이도록 직렬화
public class InventorySystem
{
    [SerializeField] private List<InventorySlot> inventorySlots; // 인벤토리 슬롯을 저장하는 리스트

    public List<InventorySlot> InventorySlots => inventorySlots; // 인벤토리 슬롯에 대한 공개 접근자
    public int InventroySize => InventorySlots.Count; // 인벤토리 크기 반환
    public UnityAction<InventorySlot> OnIventorySlotChanged; // 슬롯이 변경될 때 호출될 이벤트

    public InventorySystem(int size) // 생성자: 인벤토리 크기를 지정받아 초기화
    {
        inventorySlots = new List<InventorySlot>(size);

        for (int i = 0; i < size; i++) // 지정된 크기만큼 인벤토리 슬롯 생성
        {
            inventorySlots.Add(new InventorySlot());
        }
    }

    public bool AddToInventory(InventoryItemData itemToAdd, int amountToAdd) // 아이템을 인벤토리에 추가하는 메소드
    {
        // 아이템이 이미 인벤토리에 있는지 확인하고, 해당 슬롯 리스트를 가져옴
        if (ContainsItem(itemToAdd, out List<InventorySlot> inventorySlot)) 
        {
            foreach (var slot in inventorySlot) // 각 슬롯을 확인하며 추가 가능 여부 검사
            {
                if (slot.RoomLeftInStack(amountToAdd))
                {
                    slot.AddToStack(amountToAdd);
                    OnIventorySlotChanged?.Invoke(slot); // 슬롯이 변경되었으니 이벤트 발생
                    return true;
                }
            }

        }

        // 빈 슬롯이 있는지 확인하고, 첫 번째 빈 슬롯에 아이템 추가
        if (HasFreeSlot(out InventorySlot freeSlot)) 
        {
            freeSlot.UpdateInventorySlot(itemToAdd, amountToAdd);
            OnIventorySlotChanged?.Invoke(freeSlot); // 슬롯이 변경되었으니 이벤트 발생
            return true;
        }

        return false; // 추가 실패
    }

    public bool ContainsItem(InventoryItemData ItemToAdd, out List<InventorySlot> inventorySlot)
    {
        // 해당 아이템을 가진 슬롯을 찾아 리스트로 반환
        inventorySlot = InventorySlots.Where(i => i.ItemData == ItemToAdd).ToList();
        Debug.Log(inventorySlot.Count);
        return inventorySlot != null && inventorySlot.Count > 0; // 수정: 아이템이 있는지 여부 반환
    }

    public bool HasFreeSlot(out InventorySlot freeSlot)
    {
        // 빈 슬롯을 찾아 반환
        freeSlot = InventorySlots.FirstOrDefault(i => i.ItemData == null);
        return freeSlot != null; // 수정: 빈 슬롯이 있는지 여부 반환
    }
}

InventorySlot 클래스

  • 슬롯 초기화
  • 아이템 데이터와 스택 크기 관리
  • 슬롯에 남은 공간 계산
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[System.Serializable] // Unity 인스펙터에서 보이도록 직렬화
public class InventorySlot
{
    [SerializeField] private InventoryItemData itemData; // 슬롯에 저장된 아이템 데이터
    [SerializeField] private int stackSize; // 아이템의 수량

    public InventoryItemData ItemData => itemData; // 아이템 데이터에 대한 공개 접근자
    public int StackSize => stackSize; // 스택 크기에 대한 공개 접근자

    // 생성자: 아이템 데이터와 수량을 받아 초기화
    public InventorySlot(InventoryItemData source, int amount)
    {
        itemData = source;
        stackSize = amount;
    }

    // 기본 생성자: 슬롯을 초기화
    public InventorySlot()
    {
        ClearSlot();
    }

    // 슬롯을 비워 초기 상태로 설정
    public void ClearSlot()
    {
        itemData = null;
        stackSize = -1;
    }

    // 슬롯을 새로운 아이템 데이터와 수량으로 업데이트
    public void UpdateInventorySlot(InventoryItemData data, int amount)
    {
        itemData = data;
        stackSize = amount;
    }

    // 스택에 추가할 공간이 있는지 확인하고, 남은 공간을 반환
    public bool RoomLeftInStack(int amountToAdd, out int amountReamining)
    {
        amountReamining = itemData.MaxStackSize - amountToAdd;
        return RoomLeftInStack(amountToAdd);
    }

    // 스택에 추가할 공간이 있는지 확인 (오버로드)
    public bool RoomLeftInStack(int amountToAdd)
    {
        if (stackSize + amountToAdd <= itemData.MaxStackSize) return true;
        else return false;
    }

    // 스택에 아이템 추가
    public void AddToStack(int amount)
    {
        stackSize += amount;
    }

    // 스택에서 아이템 제거
    public void RemoveFromStack(int amount)
    {
        stackSize -= amount;
    }
}

이 InventorySlot 클래스는 인벤토리 시스템에서 하나의 슬롯을 관리한다.

각 슬롯은 특정 아이템(InventoryItemData 객체)과 그 아이템의 수량(stackSize)을 가지고 있다.

클래스는 이러한 아이템과 수량을 관리하는 여러 메소드를 제공한다.

예를 들어, 슬롯을 초기화하거나, 새 아이템으로 업데이트하거나, 아이템을 추가/제거하는 기능 등등이 있다.

InventoryItemData 클래스

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// 유니티 에디터 메뉴에서 새 인벤토리 아이템 데이터를 생성할 수 있게 해주는 어트리뷰트
[CreateAssetMenu(menuName ="Inventory System/Inventory Item")]
public class InventoryItemData : ScriptableObject // ScriptableObject를 상속받음
{
    public int ID; // 아이템의 고유 ID
    public string DisplayName; // 아이템의 표시 이름
    [TextArea(4, 4)] // 인스펙터에서 멀티라인 텍스트 영역으로 표시
    public string Description; // 아이템 설명
    public Sprite Icon; // 아이템 아이콘
    public int MaxStackSize; // 아이템이 쌓일 수 있는 최대 크기
}

InventoryItemData 클래스는 각

아이템의 고유 식별자(ID),

표시 이름(DisplayName),

설명(Description),

아이콘(Icon),

그리고 최대 쌓을 수 있는 개수(MaxStackSize) 등의 필드를 가진다.

InventoryPickUp 클래스

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// CircleCollider2D 컴포넌트가 필요함을 명시
[RequireComponent(typeof(CircleCollider2D))]
public class InventoryPickUp : MonoBehaviour
{
    public float PickUpRadius = 1f; // 아이템을 줍는 반경
    public InventoryItemData itemData; // 줍을 아이템의 데이터

    private CircleCollider2D myCollider; // 이 게임 오브젝트의 CircleCollider2D 참조

    private void Awake()
    {
        myCollider = GetComponent<CircleCollider2D>(); // CircleCollider2D 컴포넌트 가져오기
        myCollider.isTrigger = true; // 트리거로 설정하여 물리적 충돌 없이 이벤트만 발생하게 함
        myCollider.radius = PickUpRadius; // 설정된 반경으로 콜라이더의 반경 설정
    }

    // 다른 콜라이더가 이 오브젝트의 트리거 영역에 들어왔을 때 호출됨
    private void OnTriggerEnter2D(Collider2D collision)
    {
        // 충돌한 객체에서 InventoryHolder 컴포넌트를 가져옴
        var inventory = collision.transform.GetComponent<InventoryHolder>();

        // InventoryHolder 컴포넌트가 없으면 함수 종료
        if (!inventory) return;

        // 인벤토리에 아이템을 추가할 수 있으면 아이템을 추가하고, 이 게임 오브젝트를 파괴
        if (inventory.InventorySystem.AddToInventory(itemData, 1))
        {
            Destroy(this.gameObject);
        }
    }
}

이 스크립트는 InventoryHolder 컴포넌트를 가진 객체(일반적으로 플레이어)가 이 오브젝트의 트리거 영역 내로 들어올 때, 해당 객체의 인벤토리에 아이템을 추가하는 기능이다.

OnTriggerEnter2D 메소드는 플레이어가 아이템 줍기 영역에 들어올 때 자동으로 호출되며, 이 때 인벤토리에 아이템을 추가하고, 아이템을 대표하는 게임 오브젝트는 파괴되고..

이렇게 하면 아이템이 인벤토리로 이동한 것처럼(!) 보이게 된다.

InventoryHolder 클래스

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

[System.Serializable] // Unity 인스펙터에서 보이도록 직렬화
public class InventoryHolder : MonoBehaviour
{
    [SerializeField] private int inventorySize; // 인벤토리 크기
    [SerializeField] protected InventorySystem inventorySystem; // 인벤토리 시스템

    public InventorySystem InventorySystem => inventorySystem; // 인벤토리 시스템에 대한 공개 접근자

    // 인벤토리 디스플레이 요청시 사용될 정적 이벤트
    public static UnityAction<InventorySystem> OnDynamicInventoryDisplayRequested;

    private void Awake()
    {
        inventorySystem = new InventorySystem(inventorySize); // 인벤토리 시스템 초기화
    }
}

InventoryHolder 클래스는 인벤토리의 크기(inventorySize)와 실제 인벤토리 시스템(inventorySystem)을 필드로 가진다.

OnDynamicInventoryDisplayRequested는 정적 이벤트로, 인벤토리 시스템의 디스플레이를 동적으로 요청할 때 사용된다.

예를 들어, 게임에서 플레이어가 인벤토리를 열 때 이 이벤트를 통해 인벤토리 시스템을 UI에 표시할 수 있다.

이는 인벤토리 시스템과 UI 사이의 커뮤니케이션을 원활하게 만들어 준다.

구현 방식 및 동작

이 인벤토리 시스템은 아이템의 추가, 삭제, 조회 등의 기능을 제공하며, 각 아이템은 슬롯에 저장된다.

플레이어가 아이템을 주울 때, InventoryPickUp 클래스의 OnTriggerEnter2D 메소드가 호출되어 인벤토리에 아이템이 추가되고(그렇게 보인다.). 이때, InventorySystem 클래스의 AddToInventory 메소드를 통해 아이템이 실제로 인벤토리에 추가되며, 해당 슬롯이 업데이트된다.

반응형