[Unity] 아이템과 인벤토리

이정석·2023년 9월 13일
0

Unity

목록 보기
20/22

아이템

유니티에서 아이템을 구현하기 위해서 먼저 아이템의 구조를 설계할 필요가 있다. 장비아이템, 소비아이템, 치장아이템과 같은 아이템들은 하나의 Item 클래스로 공통된 기능을 묶을 수 있다. Item 클래스로 공통기능을 묶는 구조는 아래 그림과 같이 나타낼 수 있다.

물론, Item을 상속받는 WeaponArmor에서 더 세분화해 Helmet이나 Bow와 같은 무기 클래스로 나눌 수 있지만 무기별 큰 기능차이가 없다면 하나의 enumType을 정의해 부위를 구분하는 방법을 사용할 수 있다.

Item 정보에 포함될 내용은 데이터시트를 참고할 templateID나 개수를 나타내는 count, 인벤토리의 자리를 의미하는 slot과 같은 공통내용이 포함될 수 있다. 아래코드는 Item과 Weapon을 구현한 예제코드로 다른 클래스나 추가될 클래스도 동일한 구조로 구현할 수 있다.

  public class Item
  {
      public ItemInfo Info { get; } = new ItemInfo();

      public ItemType ItemType { get; private set; }
      public bool Stackable { get; protected set; }

      public Item(ItemType itemtype)
      {
          ItemType = itemtype;
      }

      public static Item MakeItem(ItemInfo itemInfo)
      {
          Item item = null;

          ItemData itemData = null;
          Managers.Data.ItemDict.TryGetValue(itemInfo.TemplateId, out itemData);

          if (itemData == null)
              return null;

          switch (itemData.itemType)
          {
              case ItemType.Weapon:
                  item = new Weapon(itemInfo.TemplateId);
                  break;
              case ItemType.Armor:
                  item = new Armor(itemInfo.TemplateId);
                  break;
              case ItemType.Consumable:
                  item = new Consumable(itemInfo.TemplateId);
                  break;
          }

          if (item != null)
          {
              item.ItemDbId = itemInfo.ItemDbId;
              item.Count = itemInfo.Count;
              item.Slot = itemInfo.Slot;
              item.Equipped = itemInfo.Equipped;
          }

          return item;
      }
  }

  public class Weapon : Item
  {
      public WeaponType WeaponType { get; private set; }
      public int Damage { get; private set; }

      public Weapon(int templateId) : base(ItemType.Weapon)
      {
          Init(templateId);
      }

      void Init(int templateId)
      {
          ItemData itemData = null;
          Managers.Data.ItemDict.TryGetValue(templateId, out itemData);
          if (itemData.itemType != ItemType.Weapon)
              return;

          WeaponData data = itemData as WeaponData;
          {
              TemplateId = data.id;
              Count = 1;
              WeaponType = data.weaponType;
              Damage = data.damage;
              Stackable = false;
          }
      }
  }

1. 아이템 UI

아이템UI는 인벤토리에 표시되는 아이템칸에 사용할 UI로 하나의 prefab으로 나타낼 수 있고 구조는 아래와 같이 나타낼 수 있다.

  1. Background: 아이템의 뒷배경이 되는 부분으로 아이템의 아이콘의 빈공간을 채워준다.
  2. Frame: 착용중인 아이템, 위치고정 된 아이템과 같이 추가적인 UI를 추가하기 위한 부분
  3. ItemIcon: 실제 아이콘이 랜더링될 부분

UI부분은 아이템의 상태를 나타내기 위한 창으로 실제 인게임 내의 아이템정보를 가질 필요는 없다. 물론 보여주고자 하는 아이템의 개수, 장착여부와 같은 정보는 가지고 있는것이 좋지만 아이템의 Type과 같은 세부정보는 UI가 들고 있을 필요가 없다.

각 아이템UI에서 발생할 이벤트나 주어지는 Item정보를 바탕으로 현재 UI의 정보를 세팅하는 구조를 갖추면 이후에 아이템 관련 교체작업이 수월할 것이다. 아래 코드는 아이템UI를 구현한 예제이다.

  public class UI_Inventory_Item : UI_Base
  {
      [SerializeField]
      Image _icon;

      [SerializeField]
      Image _frame;

      public int ItemDbId { get; private set; }
      public int TemplateId { get; private set; }
      public int Count { get; private set; }
      public bool Equipped { get; private set; }

      public override void Init()
      {
          // 이벤트 설정
      }

      public void SetItem(Item item)
      {
          ItemDbId = item.ItemDbId;
          TemplateId = item.TemplateId;
          Count = item.Count;
          Equipped = item.Equipped;

          Data.ItemData itemData = null;
          Managers.Data.ItemDict.TryGetValue(TemplateId, out itemData);

          Sprite icon = Managers.Resource.Load<Sprite>(itemData.iconPath);
          _icon.sprite = icon;

          _frame.gameObject.SetActive(Equipped);
      }
  }

디자인 패턴의 옵저버 패턴을 적용한다면 아이템의 변화가 생길 때 UI가 정보를 Refresh하는 구조를 가지면 좋다.


인벤토리

사실 인벤토리는 인게임 내부에 가지고 있어야할 정보가 없다. UI가 가지는 변수들은 인벤토리 산하에 있는 아이템UI에 대한 정보면 현재 가지고 있는 아이템의 정보를 나타내는데에는 충분하다.

1. 인벤토리 UI

인벤토리UI의 구조는 아래 사진과 같다.

  1. InventoryText: 인벤토리에 나타낼 텍스트
  2. ItemGrid: 아이템정보를 나타내기 위해 GridLayout을 사용할 수 있는데 ItemGrid 밑의 gameObject들을 순차대로 출력할 GridObject이다. GridLayout 컴포넌트는 각 Cell의 패딩이나 사이즈를 조절할 수 있다.

특정 UI에 접근하기 위해서, 전체 UI를 담당하는 클래스를 하나 정의해놓는 편이 좋다. 앞으로 더 구현할 UI에 대해서, 해당 UI에 접근할 권한이 있는 클래스에 모든 UI코드를 넣는것 보다 Manager를 하나 둠으로 앞으로의 추가된 구현에 영향을 받지 않는 구조를 형성하는 것이 좋다.

위에서 Manager에 해당하는 UI_GameScene에는 각 UI에 접근할 수단과 처음 생성될 때 각 UI를 비활성화 하는 등 여러가지 작업이 필요할 수 있다.

2. UI On/Off

보통 게임에서 UI를 키고 끄는데 키보드 입력을 자주 사용한다. I를 누르면 인벤토리가 켜진다거나, K를 누르면 스킬창이 켜진다거나 다양한 단축키가 존재할 수 있다. 클라이언트에는 어딘가에는 사용자의 입력을 담당하는 부분이 존재할 것이고 어떤 입력이 들어왔는지 게임 전체 로직에 이벤트를 뿌려주는 방식으로 구현할 수 있을 것이다.

아래 코드는 입력을 담당하는 부분에서 UI관련 입력을 받는 함수 중 하나로, I를 입력받을 때 인벤토리를 활성화 시키는 예제코드이다.

    void GetUIKeyInput()
    {
        if (Input.GetKeyDown(KeyCode.I))
        {
            UI_GameScene gameSceneUI = Managers.UI.SceneUI as UI_GameScene;
            UI_Inventory invenUI = gameSceneUI.InvenUI;

            if (invenUI.gameObject.activeSelf)
            {
                invenUI.gameObject.SetActive(false);
            }
            else
            {
                invenUI.gameObject.SetActive(true);
                invenUI.RefreshUI();
            }
        }
    }
profile
게임 개발자가 되고 싶은 한 소?년

0개의 댓글