✅ 기능

이전 글에서 아이템을 데이터화하여 관리하는 ScriptableObject을 이용한 아이템을 생성하였습니다.  본 글에서는 해당 아이템의 데이터를 활용하여 인벤토리 슬롯에 저장하고 보관할 수 있도록 하는 기능을 정리해 보았습니다.

 

🔨 로드맵

1. 유니티 인벤토리 시스템(1) - 인벤토리 관리자

2. 유니티 인벤토리 시스템(2) - 아이템(ScriptableObject)

[📌현재 글] 3. 유니티 인벤토리 시스템(3) - 아이템 슬롯(인벤토리 슬롯)

4. 유니티 인벤토리 시스템(4) - 아이템 획득

5. 유니티 인벤토리 시스템(5) - 아이템 슬롯 관리

6. 유니티 인벤토리 시스템(6) - 아이템 사용

 

✅ 구현

1. InventorySlot

using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Collections;
using System.Collections.Generic;

/// <summary>
/// 인벤토리 슬롯 하나를 담당
/// </summary>
public class InventorySlot : MonoBehaviour
{
    private Item mItem; //현재 아이템 인스턴스
    public Item Item
    {
        get
        {
            return mItem;
        }
    }

    [Header("해당 슬롯에 어떠한 타입만 들어올 수 있는지 타입 마스크")]
    [SerializeField] private ItemType mSlotMask;

    private int mItemCount; //획득한 아이템의 개수


    [Header("아이템 슬롯에 있는 UI 오브젝트")]
    [SerializeField] private Image mItemImage; //아이템의 이미지
    [SerializeField] private Image mCooltimeImage; //아이템 쿨타임 이미지
    [SerializeField] private Text mTextCount; //아이템의 개수 텍스트


    // 아이템 이미지의 투명도 조절
    private void SetColor(float _alpha)
    {
        Color color = mItemImage.color;
        color.a = _alpha;
        mItemImage.color = color;
    }

    /// <summary>
    /// mSlotMask에서 설정된 값에 따라 비트연산을한다.
    /// 현재 마스크값이 비트연산으로 0이 나온다면 현재 슬롯에 마스크가 일치하지 않는다는 뜻.
    /// 0이 아닌 수는 현재 비트위치(10진수로 1, 2, 4, 8)로 값이 나온다.
    /// </summary>
    public bool IsMask(Item item)
    {
        return ((int)item.Type & (int)mSlotMask) == 0 ? false : true;
    }

    // 인벤토리에 새로운 아이템 슬롯 추가
    public void AddItem(Item item, int count = 1)
    {
        mItem = item;
        mItemCount = count;
        mItemImage.sprite = mItem.Image;

        if (mItem.Type <= ItemType.Equipment_SHOES)
        {
            mTextCount.text = "";
        }
        else
        {
            mTextCount.text = mItemCount.ToString();
        }

        SetColor(1);
    }

    // 해당 슬롯의 아이템 개수 업데이트
    public void UpdateSlotCount(int count)
    {
        mItemCount += count;
        mTextCount.text = mItemCount.ToString();

        if (mItemCount <= 0)
            ClearSlot();
    }

    // 해당 슬롯 하나 삭제
    public void ClearSlot()
    {
        mItem = null;
        mItemCount = 0;
        mItemImage.sprite = null;
        SetColor(0);

        mTextCount.text = "";
    }
}
  • 인벤토리 슬롯 하나를 관리하는 클래스입니다.
  • 자기 자신이 가지고 있는 데이터를 인벤토리 관리자에게 건네주고, 또는 아이템 데이터를 받아옵니다.

 

private Item mItem; //현재 아이템 인스턴스
  • 이전 글에서 구현한 아이템 데이터를 가지는 변수입니다.
  • 이 변수에 아이템 데이터가 저장되며, 데이터를 건네줄 수 있습니다.

 

[Header("해당 슬롯에 어떠한 타입만 들어올 수 있는지 타입 마스크")]
[SerializeField] private ItemType mSlotMask;
  • 해당 아이템 슬롯의 Type Mask입니다.
  • 인스펙터에서 설정하며, 설정된 마스크만 해당 슬롯에 아이템이 들어올 수 있습니다.
  • 인스펙터에서 다중으로 선택이 가능합니다.

 

private int mItemCount; //획득한 아이템의 개수
  • 아이템의 개수입니다.

 

public bool IsMask(Item item)
  • 마스크가 참인지 계산하는 함수입니다.
  • Enum으로 생성된 마스크를 비트연산인 &연산을 통해 해당 비트가 서로 참이면 마스크가 참으로 리턴됩니다.

 

public void AddItem(Item item, int count = 1)
  • 아이템을 현재 슬롯에 가져옵니다.
  • Item 파라미터를 받아오며, 해당 Item을 슬롯의 mItem 변수로 설정하여 저장합니다.
  • ItemType이 SHOES(장비아이템) 보다 작다면, 개수를 출력하지 않습니다.
  • Item에서 Sprite를 가져와 자신의 mItemImage(Image)로 설정합니다.

 

public void UpdateSlotCount(int count)
  • 아이템을 획득할 때 이미 아이템이 있는 상태에서 중첩 가능한 아이템을 또 획득했다면, 새로운 슬롯에 저장하지 않습니다.
  • 해당 아이템 슬롯에서 개수를 업데이트합니다.

 

public void ClearSlot()
  • 아이템을 사용하거나, 버리거나, 퀘스트 등으로 소모된 경우 해당 아이템슬롯을 지웁니다.
  • mItem을 비우고, 개수를 0, 이미지도 제거합니다.

 

2. InventoryMain

using UnityEngine;

/// <summary>
/// 여러 아이템을 담을 가장 기본적인 인벤토리
/// </summary>
public class InventoryMain : InventoryBase
{
    ...

    /// <summary>
    /// 특정 아이템 슬롯에 아이템을 등록시킨다
    /// </summary>
    /// <param name="item">어떤 아이템?</param>
    /// <param name="targetSlot">어느 슬롯에?</param>
    /// <param name="count">개수는?></param>
    public void AcquireItem(Item item, InventorySlot targetSlot, int count = 1)
    {
        //중첩이 가능하다면?
        if (item.CanOverlap)
        {
            //마스크를 사용하여 해당 슬롯이 마스크에 허용되는 위치인경우에만 아이템을 집어넣도록 한다.
            if (targetSlot.Item != null && targetSlot.IsMask(item))
            {
                if (targetSlot.Item.ItemID == item.ItemID)
                {
                    //현재 슬롯의 아이템 개수(Count)를 갱신한다.
                    targetSlot.UpdateSlotCount(count);
                }
            }
        }
        else
        {
            targetSlot.AddItem(item, count);
        }
    }


    public void AcquireItem(Item item, int count = 1)
    {
        //중첩이 가능하다면?
        if (item.CanOverlap)
        {
            for (int i = 0; i < mSlots.Length; i++)
            {
                //마스크를 사용하여 해당 슬롯이 마스크에 허용되는 위치인경우에만 아이템을 집어넣도록 한다.
                if (mSlots[i].Item != null && mSlots[i].IsMask(item))
                {
                    if (mSlots[i].Item.ItemID == item.ItemID)
                    {
                        //현재 슬롯의 아이템 개수(Count)를 갱신한다.
                        mSlots[i].UpdateSlotCount(count);
                        return;
                    }
                }
            }
        }

        //장비 아이템이 아닌경우 새로운 슬롯에 놓는다.
        for (int i = 0; i < mSlots.Length; i++)
        {
            if (mSlots[i].Item == null && mSlots[i].IsMask(item))
            {
                mSlots[i].AddItem(item, count);
                return;
            }
        }
    }
    
    ...
    
}
  • 인벤토리 관리자 글에서 구현한 InventoryMain에 추가 함수를 작성합니다.
  • 아이템을 획득하는 함수로, 인벤토리 슬롯들을 확인하고 적절한 슬롯에 아이템을 넣어주도록 하였습니다.

 

public void AcquireItem(Item item, InventorySlot targetSlot, int count = 1) {}
public void AcquireItem(Item item, int count = 1) {}
  • 아이템을 획득합니다.
  • 인벤토리 슬롯들을 확인하여 주울 수 있는 아이템인지, 중첩은 가능한지 확인하여 해당 슬롯에 아이템 데이터를 저장합니다.
  • 아이템 타입에서 마스크를 적용하여 해당 슬롯에 마스크가 허용 가능한지 확인하여 적절한 슬롯에 들어가도록 하였습니다.

 

if (targetSlot.Item != null && targetSlot.IsMask(item))
  • 해당 아이템이 현재 슬롯의 마스크에서 허용인지 확인합니다.

 

✅ 사용(적용) 예시

  • 각 ItemSlot에 InventorySlot 스크립트를 컴포넌트로 생성합니다.
  • 인스펙터 내 컴포넌트에서 각 오브젝트들을 알맞게 넣어줍니다.

ItemSlot에 구현한 InventorySlot.cs를 넣는다.

 

  • 인벤토리 슬롯에서 허용할 아이템 타입에 대한 마스크를 설정합니다.

해당 슬롯은 SKILL, NONE을 제외한 모든 아이템을 받을 수 있다.

 

✅ 테스트

아이템이 제대로 들어오는지, 마스크가 제대로 작동하는지 테스트합니다.

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

public class SampleScript : MonoBehaviour
{
    [Header("인벤토리 메인")]
    [SerializeField] private InventoryMain mInventoryMain;

    [Header("획득할 아이템")]
    [SerializeField] private Item mHPItem, mManaItem;
    

    private void OnGUI()
    {
        if (GUI.Button(new Rect(20, 20, 300, 40), "체력포션 아이템 획득"))
        {
            mInventoryMain.AcquireItem(mHPItem);
        }

        if (GUI.Button(new Rect(400, 20, 300, 40), "마나포션 아이템 획득"))
        {
            mInventoryMain.AcquireItem(mManaItem);
        }        
    }
}
  • 체력포션, 마나포션 아이템을 획득했다고 가정할 때, 인벤토리에 제대로 들어오는지 확인합니다.

 

  • 위에서 나온 것처럼 NONE, SKILL을 제외한 모든 아이템에 대한 마스크를 허용한 상태에서는 좌측 위(0번 인덱스)부터 아이템이 들어오는 것을 볼 수 있습니다.

 

 

포션 아이템을 Ingredient로 설정하고, 상위 4개의 슬롯은 Consumable을 비활성화 하여 테스트한다.

  • Consumable인 마나포션은 하위슬롯에 저장되고, Ingredient인 체력포션은 상위슬롯에 저장되는 것을 볼 수 있습니다.
  • 마스크에 대한 작동이 제대로 이루어지는 것을 확인하였습니다.

 

bonnate