본문 바로가기
게임 프로그래밍/유니티 활용

[유니티 활용] ScriptableObject 를 이용한 아이템 관리

by 레오란다 2023. 1. 4.
반응형

ScriptableObject 는 활용할 수 있는 용도가 아주 많습니다. 이번에는 아이템을 생성하고 사용하는 방법에 대해 알아보도록 하겠습니다.

 

아이템을 착용하거나 사용하는 것도 일종의 버프라고 생각하면 이전에 작성한 "ScriptableObject와 Reflection 을 이용한 버프 시스템 만들기" 와 유사한 면이 많습니다. 이번에도 이 두가지를 이용하여 아이템을 만들고 사용할 것입니다.

 

시작하기에 앞서 아이템을 ScriptableObject 로 만들면 메모리 관리면에서 큰 이득을 볼 수 있습니다.  예를 들어 동일한 체력 물약 10개를 화면에 만든다고 했을 때 기존의 방식대로 하면 회복량과 관련된 변수만 각각 10개가 필요합니다. 하지만 ScriptableObject 를 이용하면 회복량 변수 1개를 체력 물약이 모두 공유하기 때문에 체력 물약을 아무리 많이 만들어도 회복량 변수는 단 한 개만 메모리에 생성하기 때문에 상당한 이득을 볼 수 있습니다.

 

메모리 사용량 비교

 

▶ ScriptableObject 로 만드는 Item

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

[CreateAssetMenu]
public class SOItem : ScriptableObject
{
   public enum ItemType
   {
      Weapon,
      Shield,
      Armor,
      Accessory,
      Potion,
      Resource,
   }

   public ItemType itemType;

   [System.Serializable]
   public struct STAT {
      public string name;
      public int value;
   }

   public List<STAT> stats = new List<STAT>();

   public int maxStack;
   public int price;

   public Sprite icon;
   public string description;
}
  • STAT 구조체의 name 은 Player 스크립트 등에 선언되는 힘, 데미지 등의 스탯 관련 변수와 동일한 이름이어야 합니다. 이는 Reflection 을 이용해 스탯 수치를 적용시키기 위한 것입니다. 이와 관련된 자세한 예제는 아래 링크의 이전 글을 참고해 주세요.
 

[유니티 활용] ScriptableObject 와 Reflection 을 이용한 간단한 버프 시스템 만들기

안녕하세요. 이번 글에서는 간단한 버프 시스템을 만들어 보는 방법에 대해서 알아보도록 하겠습니다. 이번 글은 이전에 작성한 아래의 글을 베이스로 작성하도록 하겠습니다. 이 글에서 작성

ugames.tistory.com

 

 

▶ 인벤토리에 아이템 추가 및 Save 와 Load

ScriptableObject 로 만든 에셋은 Serializable 할 수 없기 때문에 인벤토리 정보에 SOItem 자체를 포함하는 건 의미가 없습니다. 전 다음과 같은 인벤토리 코드를 사용합니다.

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

public class Inventory : MonoBehaviour
{
   [System.Serializable]
   public class InventoryItem
   {
      public string itemName;
      public int stack;

      public InventoryItem(string itemName)
      {
         this.itemName = itemName;
         stack = 1;
      }
   }

   public class Bag
   {
      public List<InventoryItem> items = new List<InventoryItem>();
   }

   Bag bag;

   public void AddItem(SOItem soItem)
   {
      var item = bag.items.Where(x => x.itemName == soItem.name).FirstOrDefault();
      if (item != null && item.stack < soItem.maxStack)
      {
         item.stack++;
      }
      else
      {
         bag.items.Add(new InventoryItem(soItem.name));
         Save();
      }
   }

   public void Save()
   {
      var json = JsonUtility.ToJson(bag);
      PlayerPrefs.SetString("SaveData", json);
   }

   public void Load()
   {
      var json = PlayerPrefs.GetString("SaveData", "");
      if (json == "")
      {
         bag = new Bag();
      }
      else
      {
         bag = JsonUtility.FromJson<Bag>(json);
      }
   }
}

인벤토리에 추가할 때는 인자로 SOItem 을 받습니다. SOItem 으로 만든 에셋의 이름과 maxStack 정보가 필요하기 때문입니다. 위의 코드엔 UI 관련 코드가 없지만 만약 UI 관련 코드를 이 스크립트에 같이 포함시킨다면 Load 후에 SOItem 이 필요하게 됩니다. 이 문제를 해결하기 위해 모든 SOItem 을 관리하는 ItemLibrary 를 만들어 사용합니다.

 

 

▶ ItemLibrary

ItemLibrary 는 싱글톤 클래스로 이고 모든 SOItem 목록을 갖고 있습니다. SOItem 은 이름으로 검색합니다. 만약 SOItem 으로 만든 에셋의 이름이 RedPotion 이라고 하면 이 에셋을 참조하기 위해 GetItem 의 인자로 "RedPotion" 을 넘겨줘야 합니다.

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

public class ItemLibrary : MonoBehaviour
{
   public static ItemLibrary instance;

   public List<SOItem> items = new List<SOItem>();

   private void Start()
   {
      instance = this;
   }

   public SOItem GetItem(string name)
   {
      return items.Where(x => x.name == name).FirstOrDefault();
   }
}

 

▶ Item 객체 생성

아이템 객체는 3D 모델을 포함하는 객체에 스크립트를 만들고 스크립트에 SOItem 을 참조하게 만들면 됩니다. 만약 RedPotion 아이템 객체를 생성한다고 하면 객체를 생성할 때마다 각 객체는 새롭게 만들어지지만 아이템의 효과에 대한 정의는 ScriptableObject 로 작성되었기 때문에 중복되어 복사되지 않고 링크된 SOItem 을 참조하게 되므로 메모리를 절약할 수 있습니다.

 

지금까지 ScriptableObject 를 이용해 게임의 아이템을 관리하는 방법에 대해 알아보았습니다. ScriptableObject 를 꼭 사용할 필요는 없지만 처음에 언급했던 것과 같이 메모리 사용에 있어서 장점이 있기 때문에 아이템의 사용 빈도가 높은 게임이라면 ScriptableObject 를 이용한 설계도 고려해 보는 것이 좋을 것입니다.

반응형

댓글