Study/Unity

[Unity System Programming Pt.1] 7-8주차 - 유저 인벤토리 구현

해달 2024. 10. 30. 12:57

개요

게임에서 인벤토리는 아이템이 수납되는 장소를 일컫는다.* 이번에는 강의에서 언급된 Scroll View의 한계에 대해서 살펴보고, 간단한 필터와 정렬 기능이 들어간 인벤토리를 구현해보고자 한다. 리소스는 강의에서 제공한 것을 그대로 사용하도록 하고 여기서는 핵심 코드에만 집중하도록 한다.
* 인벤토리에 대한 자세한 설명이 필요하다면 나무위키를 참고하기를 바란다.

Scroll View의 한계

Unity로 채팅창과 같이 스크롤링이 필요한 UI 요소를 구현하고 싶다면 Scroll View를 이용하면 된다. Scroll View에 대한 자세한 설명은 이 영상UGUI 매뉴얼로 대신한다. Scroll View에는 2가지 아쉬운 점이 있다. 첫 번째는 최적화다. UGUI는 자동으로 Content의 자식오브젝트를 재사용하지 않는다. 그래서 이를 부주의하게 사용한다면 급격한 프레임 드랍을 맛볼 수 있다.* 두 번째는 Scroll View를 제어하는 기능이 부족하다. 그래서 애셋 스토어에서 관련된 애셋을 구매하거나 직접 확장하여 구현하는 수밖에 없다. 강의에서는 NHN에서 개발한 GPM을 소개하고 있다. GPM은 무료기에 GPM에서 제공하는 기능만으로도 충분하다면 괜찮은 선택지가 될 것으로 보인다. 다른 애셋은 여기서 직접 찾아볼 수 있다.
* 이에 대한 것은 이 영상에 잘 나타난다.

데이터 클래스 정의

우선 아이템을 나타낼 ItemModel부터 정의한다. 아이템은 탕탕특공대처럼 장비 아이템만 나타낼 것이며, 등급과 종류가 있다. 식별자로부터 ItemGradeItemType을 추출할 메소드도 포함시켰다.

using System.Text;
using System;

public enum ItemType
{
    Weapon = 1,
    Shield,
    ChestArmor,
    Gloves,
    Boots,
    Accessery
}

public enum ItemGrade
{
    Common = 1,
    Uncommon,
    Rare,
    Epic,
    Legendary
}

[Serializable]
public class ItemModel
{
    public int item_id;
    public string item_name;
    public int attack_power;
    public int defense;

    public static ItemGrade GetGrade(int id)
    {
        return (ItemGrade)((id / 1000) % 10);
    }

    public static ItemType GetType(int id)
    {
        return (ItemType)(id / 10000);
    }

    public static string GetIconPath(int id)
    {
        StringBuilder sb = new(id.ToString());
        sb[1] = '1';

        return sb.ToString();
    }
}

이를 바탕으로 인벤토리에 넣을 데이터인 UserInventoryData 클래스를 정의한다. 레벨 별 정렬을 위해서 던전앤파이터 모바일처럼 획득 시 아이템 레벨이 결정된다고 가정하고 item_level 필드를 추가했다. serial_number는 게임 내에서 고유하게 식별되어야 하는 번호이다.

[Serializable]
public class UserInventoryData
{
    public long serial_number;
    public int item_level;
    public int item_id; 
}

아이템 종류로 필터링하기

아이템 종류로 필터링을 해보자. 우선 FilterType을 정의했다.

enum FilterType
{
    All,
    Weapon,
    Shield,
    ChestArmor,
    Gloves,
    Boots,
    Accessery,
    Max
}

GPM의 설명에 따르면 Predicate<InfiniteScrollData>으로 필터링 함수를 만들어야 한다.

private bool OnFilter(InfiniteScrollData data)
{
    if (_filterType == FilterType.All)
    {
        return false;
    }

    var slotData = data as InventoryItemSlotData;
    return (int)_filterType != (int)ItemModel.GetType(slotData.UserInventoryData.item_id);
}

그리고 InfiniteScroll.SetFilter()에 위 함수를 전달하면 된다.

 private void Refresh()
 {
     _infiniteScroll.Clear();
     foreach (var item in _userItems)
     {
         _infiniteScroll.InsertData(new InventoryItemSlotData() { UserInventoryData = item });
     }
     _infiniteScroll.SetFilter(OnFilter);

    // 후략
 }

아이템 등급으로 정렬하기

다음으로는 정렬 기능을 구현해보자. 정렬의 경우 GPM에서 기본으로 제공되는 것이 아니기 때문에 코드를 고쳐야 한다. InfiniteScroll.ItemData.cs 파일을 열어보면 InfiniteScrollDataContext 타입의 객체를 다루는 것을 확인할 수 있다. 해당 파일 아래에 메소드를 추가한다.

public void SortList(Comparison<DataContext> comparison)
{
    dataList.Sort(comparison);
    needUpdateItemList = true;
}

그리고 코드에서 DataContext 내부의 data 필드에 접근해야 하므로 internalpublic으로 바꿔준다.

 public class DataContext
 {
     public DataContext(InfiniteScrollData data, int index)
     {
         this.index = index;
         this.data = data;
     }

     public InfiniteScrollData data; // public으로 변경
     internal int index = -1;
     // 후략
}

정렬 방식은 등급, 레벨 2가지만 제공한다. 각각에 대응하는 함수를 InventoryUI에 추가한다.

private int CompareByLevel(InventoryItemSlotData lhs, InventoryItemSlotData rhs)
{
    int lhsLevel = lhs.UserInventoryData.item_level;
    int rhsLevel = rhs.UserInventoryData.item_level;

    return rhsLevel.CompareTo(lhsLevel);
}

private int CompareByGrade(InventoryItemSlotData lhs, InventoryItemSlotData rhs)
{
    var lhsGrade = ItemModel.GetGrade(lhs.UserInventoryData.item_id);
    var rhsGrade = ItemModel.GetGrade(rhs.UserInventoryData.item_id);

    return rhsGrade.CompareTo(lhsGrade);
}

SortInventory()를 만들어준다.

private void SortInventory()
{
    _infiniteScroll.SortList((lhs, rhs) =>
    {
        var lhsData = lhs.data as InventoryItemSlotData;
        var rhsData = rhs.data as InventoryItemSlotData;

        int compareResult = 0;
        switch (_sortType)
        {
            case SortType.Grade:
                compareResult = CompareByGrade(lhsData, rhsData);
                if (compareResult == 0)
                {
                    compareResult = CompareByLevel(lhsData, rhsData);
                }
                break;
            case SortType.Level:
                compareResult = CompareByLevel(lhsData, rhsData);
                if (compareResult == 0)
                {
                    compareResult = CompareByGrade(lhsData, rhsData);
                }
                break;
        }

        return compareResult;
    });
}

결과물과 모든 코드는 여기에서 볼 수 있다.

 

참고자료