공부/Unity

아이템 슬롯, 스크롤 뷰, 슬롯 데이터 저장, 세이브 로드, 인벤토리 정렬, 인벤토리 필터링, 드롭다운

월러비 2025. 9. 9. 18:57
  • UI는 계층적으로 만들어야한다. → 재사용이 가능하면 좋다.
    • 장비 슬롯 같은 경우 → 부위별 슬롯
  • 먼저 인벤토리를 한다면 슬롯 하나를 만들고 함수를 만들고 테스트를 하고 된다면 그 다음으로 넘어가야한다.
    • 비어있는 상태
    • 아이템이 들어있는 상태
    • 클릭을 했을때의 경우
      • 정보 표시
      • 클릭을 한 다음 삭제버튼을 누르면 삭제되는 경우

아이템 슬롯

  • 슬롯 프리펩 생성 - 빈상태 함수, 아이템 있는 상태 함수 틀 선언
    • 지금은 아이템 정보를 한번에 받지만, 원래는 개별적인 아이템 정보를 받아야한다.
    • 언제 획득했냐, 랜덤으로 능력치가 붙는게 있지 않나 같은 정보를 넣어야한다.
  • 빈상태 함수 : 아이템 데이터, 이미지, 이름 텍스트 비우기
  • 슬롯 채우기 함수 ; 아이템 데이터, 이미지, 이름 텍스트 채우기 → 아이템 데이터로 할당한다.
//슬롯 비우기
public void SetEmpty()
{
    ItemData = null;
    imageIcon.sprite = null; //null이면 아무것도 안그려진다.
    textName.text = string.Empty;
}

//슬롯 채우기
public void SetItem(SaveItemData data)
{
    ItemData = data;
    imageIcon.sprite = ItemData.itemData.SpriteIcon;
    textName.text = ItemData.itemData.StringName;
}

스크롤 뷰

  • Viewport안의 Content에 슬롯같은 요소들이 들어간다.
  • 아무리 많아도 viewport의 영역만큼만 보이게 된다.
    • 요소를 드래그하면 스크롤바가 움직이게 된다.
  • 스크립트에서 content 오브젝트의 참조하기 위해선 ScrollView 클래스가 아니라 ScrollRect 클래스로 객체를 선언한 다음에 content 프로퍼티를 사용한다.
public ScrollRect scrollRect;

public void AddRandomItem()
{
    var itemData = DataTableManager.ItemTable.GetRandom();
    var newSlot = Instantiate(prefab, scrollRect.content); //UI객체를 생성할때는 부모 오브젝트 트랜스폼이 중요하다. / 부모 오브젝트 설정
    newSlot.SetItem(itemData);
}

content 레이아웃 배치 제한

  • Add Component로 레이아웃 제한이 가능하다.
  • Grid Layout Group : content안에 요소를 좌우 몇개까지 배치가능한지 정하는 컴포넌트다.
  • Content Size Fitter : 레이아웃 요소의 크기를 제어하는 레이아웃 컨트롤러의 기능이다.
    • Unconstrained : 레이아웃 요소에 기반하여 높이를 조정하지 않는다.
    • Min Size : 최소 높이에 기반하여 높이를 조정한다.
    • Preferred Size : 기본 높이에 기반하여 높이를 조정한다.

스크롤 뷰 드래그 제한

  • Scroll View - Scroll Rect - Horizontal 또는 Vertical : 상하 또는 좌우 드래그 제한

슬롯 데이터 저장

  • 고유한 아이디는 중복되면 안된다.
    • 서버가 있는게임은 아이디를 서버가 줘서 받아서 사용하면 된다.
  • 고유 아이디 아이디어
    • 흘러가는 시간 → 가장 속 편한 방법
    • 파일로 숫자 지정 → 증가할때마다 1씩 증가
      • 전제 : 서버가 없고 클라이언트만 존재할때 이 파일이 제대로 보존되어야한다.

슬롯 변경 시 업데이트

private void UpdateSlots(List<SaveItemData> itemList)
{
    if (slotList.Count < itemList.Count)
    {
        for (int i = slotList.Count; i < itemList.Count; ++i)
        {
            var newSlot = Instantiate(prefab, scrollRect.content); //UI객체를 생성할때는 부모 오브젝트 트랜스폼이 중요하다. / 부모 오브젝트 설정
            newSlot.slotIndex = i; //슬롯 번호
            newSlot.SetEmpty();
            newSlot.gameObject.SetActive(false);
            slotList.Add(newSlot);
        }
    }
    for (int i = 0; i < slotList.Count; ++i)
    {
        if (i < itemList.Count)
        {
            slotList[i].gameObject.SetActive(true);
            slotList[i].SetItem(itemList[i]);
        }
        else
        {
            slotList[i].SetEmpty();
            slotList[i].gameObject.SetActive(false);
        }
    }
}

public void AddRandomItem()
{
    //var itemData = DataTableManager.ItemTable.GetRandom();
    //testItemList.Add(itemData);

    var itemInstance = new SaveItemData();
    itemInstance.itemData = DataTableManager.ItemTable.GetRandom();
    testItemList.Add(itemInstance);
    UpdateSlots(testItemList);
}

public void RemoveItem(int slotIndex)
{
    testItemList.RemoveAt(slotIndex);
    UpdateSlots(testItemList);
}

정리

  • ItemData : ItemTable에 포함되어있다.
    • ItemTable은 실제 빌드할때 이미 생성되어있다.
  • SaveItemData : 세이브 로드할때 세이브 파일에 포함될 데이터다.
    • 아이템을 얻는 순간에 하나씩 만들어진다.
    • 얘네끼리도 서로 구분이 가능해야한다. → ID가 중복되야하기 때문이다.
      • 물약은 id가 하나지만 여러개 들 수 있다.
    • 즉, 각 객체의 고유 id가 필요하기에 Guid(또는 DateTime)가 필요한 것이다.
  • 차이점 : ItemData → 각 아이템의 정보다. / SaveItemData : 사용해야할 실제 아이템 정보
    • 사전의 Id가 세이브 아이템 정보와 공유하고있다.
  • 즉, ItemData의 Id는 해당 아이템의 id이고, SaveItemData의 Id는 해당 아이템의 고유 Id이다.
[Serializable]
public class SaveItemData
{
    public Guid instanceId; //고유 아이디 클래스를 사용할때 / 뉴튼 소프트는 이게 자동으로 직렬 역직렬이 되는 기능이 있다.

    [JsonConverter(typeof(ItemDataConverter))] //뉴튼Josn의 제이슨 컨버터 어트리뷰트다.
    public ItemData itemData; //직렬화하기 위해서 JsonConveter를 만들어야한다.

    public DateTime creationTime; //시간으로 아이디를 만들때

    public SaveItemData() 
    {
        instanceId = Guid.NewGuid(); //새로운 고유 아이디 반환 함수
        creationTime = DateTime.Now; //현재시간
    }
}

세이브 로드

public void Save()
{
    var jsontext = JsonConvert.SerializeObject(testItemList);
    var filePath = Path.Combine(Application.persistentDataPath, "test.json");
    File.WriteAllText(filePath, jsontext);
}
public void Load()
{
    var filePath = Path.Combine(Application.persistentDataPath, "test.json");
    if (!File.Exists(filePath))
    {
        return;
    }

    var jsonText = File.ReadAllText(filePath);
    testItemList = JsonConvert.DeserializeObject<List<SaveItemData>>(jsonText);

    UpdateSlots(testItemList);
}

저장 후 종료 및 불러오기

private void OnEnable()
{
    Load();
}

private void OnDisable()
{
    Save();
}

인벤토리 정렬

  • 정렬없고 필터링 없는 상태, 정렬 상태, 필터 상태 3개가 필요하다.
public readonly System.Comparison<SaveItemData>[] comparisons =
{
    (lhs, rhs) => lhs.creationTime.CompareTo(rhs.creationTime),
    (lhs, rhs) => lhs.creationTime.CompareTo(lhs.creationTime),
    (lhs, rhs) => lhs.itemData.StringName.CompareTo(rhs.itemData.StringName),
    (lhs, rhs) => lhs.itemData.StringName.CompareTo(lhs.itemData.StringName),
    (lhs, rhs) => lhs.itemData.Cost.CompareTo(rhs.itemData.Cost),
    (lhs, rhs) => lhs.itemData.Cost.CompareTo(lhs.itemData.Cost),
}; //정렬

private SortingOptions sorting = SortingOptions.NameAccending; //정렬 비교 객체
public SortingOptions Sorting
{
    get => sorting;
    set
    {
        //1. 필터링된 리스트 가져오기 / 2. 정렬 실행 / 3. 슬롯 업데이트
        sorting = value;
        UpdateSlots(testItemList);
    }
}

인벤토리 필터링

public readonly System.Func<SaveItemData, bool>[] filterings =
{
    x => true,
    x => x.itemData.Type == ItemTypes.Weapon,
    x => x.itemData.Type == ItemTypes.Equip,
    x => x.itemData.Type == ItemTypes.Consumable,
}; //필터링

private FilteringOptions filtering = FilteringOptions.None; //필터링 비교 객체
public FilteringOptions Filtering
{
    get => filtering;
    set
    {
        filtering = value;
        UpdateSlots(testItemList);
    }
}

드롭다운

  • 정렬 또는 필터링같은 특정 항목에 따라서 기능이 달라지는 상황에 사용한다.
  • OnChanged 항목에 함수를 연결하면 드롭다운에서 선택한 항목의 index가 함수의 매개변수로 전달된다.
public void OnChangeSorting(int index)
{
    slotList.Sorting = (UiInvenSlotList.SortingOptions)index;
}

public void OnChangeFiltering(int index)
{
    slotList.Filtering = (UiInvenSlotList.FilteringOptions)index;
}