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 을 이용해 스탯 수치를 적용시키기 위한 것입니다. 이와 관련된 자세한 예제는 아래 링크의 이전 글을 참고해 주세요.
▶ 인벤토리에 아이템 추가 및 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 를 이용한 설계도 고려해 보는 것이 좋을 것입니다.
'게임 프로그래밍 > 유니티 활용' 카테고리의 다른 글
[유니티 활용] 3D 캐릭터를 UI 로 표시하는 방법 (10) | 2023.01.06 |
---|---|
[유니티 활용] 간단한 포물선 궤적 그리기 (3) | 2023.01.05 |
[유니티 활용] ScriptableObject 와 Reflection 을 이용한 간단한 버프 시스템 만들기 (1) | 2023.01.03 |
[유니티 활용] ScriptableObject 를 이용한 스킬 시스템 만들기 (7) | 2023.01.01 |
[유니티 활용] ScriptableObject 로 HP Bar 만들기 - #2 (3) | 2022.12.30 |
댓글