본문 바로가기
유니티 이야기

[Unity] 대량의 엑셀데이터 유니티 인스턴스로 밀어넣기

by novices 2022. 8. 28.
반응형

게임을 만들다 보니 반복적인 데이터를 인스턴스화 된 스크립트에 입력해야 하는 경우가 있습니다. 몇 줄 정도는 수작업이 가능하지만 아이템 리스트 같이 대량의 데이터를 반복적으로 할당해야 하는 경우 난감한데요. 이럴 때 엑셀로 데이터를 정리해서 일괄적으로 밀어 넣는 방법을 작성해보겠습니다. 보통 정적인 데이터는 메모리 최적화 때문에 데이터가 필요할 때 스크립터블 오브젝트에서 데이터를 읽어가는데 이번 포스팅에서 작성되는 코드는 대량의 반복 데이터를 지속적으로 계속 참고해야 되는 경우에 고려해 볼만 한 것 같습니다. 예시 방향은 디아블로 2의 세트 아이템 리스트로 잡았습니다. 전제 조건은 세트 아이템의 수는 4개로 고정하고 각 아이템은 투구, 갑옷, 장갑, 신발 순으로 구조화해서 작성하였습니다. 혹 세트별로 숫자가 다를 경우에는 추가 코드가 필요합니다.

 

 

1. 데이터 만들기

저는 구글 스프레드시트 사용해서 아이템 정보를 정리하고 cvs 형식으로 뽑아서 구조화된 스크립터블 오브젝트에 입력했습니다.

 

엑셀 구조는 아래와 같습니다. 

테스트용으로 간단하게 구성하였습니다. 실제 게임에서 사용할 것이라면 더 많은 속성이 필요할 것입니다.

 

작성된 엑셀 데이터 화면
작성된 엑셀 필드 화면

 

 

▶ 스크립터블 오브젝트 ItemBundleBaseData.cs

using UnityEngine;
[CreateAssetMenu(fileName = "ItemBundleBaseData Data",
	menuName = "Scriptable Object/ItemBundleBaseData Data", order = int.MaxValue)]
public class ItemBundleBaseData : ScriptableObject
{
    [SerializeField]
    private Sprite[] ItemIcon; //아이콘에 사용 될 Sprite배열
    public Sprite[] itemIcon {get{ return ItemIcon;}} 
    [SerializeField][TextArea(12,20)] // TextArea는 데이터 가공에 줄바꿈을 인식해야하므로 추가
    private string Data; // 콤마 분활 형식의 통텍스트가 입력되는 변수
    public string data {get{ return Data;}} 
}

 

코드 작성 후 스크립터블 오브젝트를 만들어서 data에 엑셀 추출 값을 넣고, 해당되는 아이콘을 ItemIcon배열에 순서대로 할당했습니다. 아이콘 Sprite를 담을 때는 Properties를 활용합니다.  첫 번째 필드의 숫자는 아이템의 형태를 가리킵니다.(0 - 투구, 1 - 갑옷, 2 - 장갑, 3 - 신발)

 

Properties 사용 이미지스크립터블 오브젝트에 입력된 데이터 이미지
스크립터블 오브젝트에 데이터 입력 화면

 

 

2. 스크립트 구조

ItemBundleData.cs (스크립터블 오브젝트에 있는 데이터를 처리한 뒤 할당할 데이터 구조물)

DataProcessing.cs (데이터 처리 기능을 하는 스크립트)

 

▶ ItemBundleData

여러 아이템을 포함한 세트 아이템의 구조를 리스트 화한 구조물?? 쓰다 보니 이상하지만 말은 맞는 것 같습니다. 아이템 부위 식별을 위한 enum도 하나 선언하였는데 추후에 엑셀 첫 번째 열에 있는 문자 값을 형 변환하여 할당합니다.

using System.Collections.Generic;
using UnityEngine;
// 아이템 부위 식별을 위한 enum
public enum ItemType { HEAD, BODY, HAND, FOOT}
public class ItemBundleData : MonoBehaviour
{
    // 아이템 이름, 형태, 아이콘, 설명을 담을 수 있는 구조
    // 아이템
    [System.Serializable]
    public class Item
    {
        public string itemName;
        public ItemType itemType;
        public Sprite itemIcon;
        public string itemDesc;
    }
    // 세트로 구성된 아이템을 담을 수 있는 구조
    // 세트아이템
    [System.Serializable]
    public class ItemBundle
    {
        public int numberOfBundledItems;
        public Item[] items;
    }
    // 아이템 세트를 담을 수 있는 구조 
    // 세트아이템 리스트
    public List<ItemBundle> itemBundles;
}

 

▶ DataProcessing

전체적인 과정은 DataProcessing.cs의 게임 오브젝트에 자식으로 ItemBundleData를 인스턴스화 하고 스크립터블 오브젝트 ItemBundleBaseData의 데이터를 처리해서 할당하는 과정입니다. 부분 설명은 주석으로 추가했습니다.

using System.Collections.Generic;
using UnityEngine;
public class DataProcessing : MonoBehaviour
{
    public ItemBundleBaseData itemBundleBaseData;
    ItemBundleData itemBundleData;
    void Start()
    {
        // ItemBundleData.cs가 컨포넌트로 붙을 GameObject를 생성하여 자식으로 붙입니다.
        GameObject ItemBundleDataGO = new GameObject ("ItemBundle DB");
        ItemBundleDataGO.transform.position = transform.position;
		ItemBundleDataGO.transform.parent = transform;

        // 생성한 게임오브젝트에 ItemBundleData 컨포넌트를 추가
        itemBundleData = ItemBundleDataGO.AddComponent<ItemBundleData>();

        // ItemBundleData의 리스트에 메모리를 할당
        itemBundleData.itemBundles = new List<ItemBundleData.ItemBundle>();

        // 세트 아이템 숫자 확인
        // 세트 아이템은 아이템이 4개로 구성되어 4로 나누기
        int itemBundleCount = itemBundleBaseData.data.Split('\n').Length/4;

        // 확인 된 세트아이템의 수만큼 메모리를 할당
        for(int j = 0; j < itemBundleCount; j++)
        {
            itemBundleData.itemBundles.Add(new ItemBundleData.ItemBundle());
        }

        // 생성 된 세트아이템의 Item에 대한 메모리 할당 과정
        for(int i = 0; i < itemBundleCount; i++)
        {
            //세트아이템별 아이템 배열 생성
            itemBundleData.itemBundles[i].items = new ItemBundleData.Item[4];

            //배열내부 아이템 데이터 메모리 할당
            for(int j = 0; j < 4; j++)
            {
                itemBundleData.itemBundles[i].items[j] = new ItemBundleData.Item();
            }
        }

        // 스크립터블 오브젝트에 입력된 엑셀데이터를 줄별로 짤라 string배열에 할당
        string[] row = itemBundleBaseData.data.Split('\n'); 
        string[] columns;

        int rowSize = row.Length; // 행의 숫자 확인
        int columnSize = row[0].Split(',').Length; // 열의 숫자 확인

        int currentColumns = 0; // 현재 열의 커서
        int currentBundle = 0; // 현재 세트아이템의 커서

        for(int j = 0; j < rowSize/4; j++) // 세트아이템별 한바퀴 돌리는 for 구문
        {  
            for(int k = 0; k < 4; k++) //세트아이템내 아이템을 한바퀴 돌리는 for 구문
            {
                columns = row[currentColumns].Split(','); // 현재 행을 열별로 짤라 string배열에 할당

                //string인 숫자를 ItemType으로 형변환 하여 데이를 할당한다.
                itemBundleData.itemBundles[currentBundle].items[k].itemType = ConvertFromString(columns[0]); 
                itemBundleData.itemBundles[currentBundle].items[k].itemName = columns[1]; //이름 할당
                itemBundleData.itemBundles[currentBundle].items[k].itemDesc = columns[2]; //디스크립션 할당
                
                // 아이콘 별도 처리
                if(itemBundleBaseData.itemIcon.Length > currentColumns)
                    itemBundleData.itemBundles[currentBundle].items[k].itemIcon = itemBundleBaseData.itemIcon[currentColumns];

                // 열 커서를 +1
                if(currentColumns < rowSize)
                    currentColumns++;
            }
            // 세트아이템 커서 +1
            currentBundle++;
        }
    }

    // string 입력을 받아 정수로 파싱하고 다시 ItemType으로 형변환 하여 리턴
    ItemType ConvertFromString(string itemTypeString)
    {
        return (ItemType)int.Parse(itemTypeString);
    }
}

 

 

3. 실행 결과

실행해보면 예상했던 데로 ItemBundleData에 정상적으로 값이 할당됩니다.

 

실행결과 화면 gif
실행 결과 화면 gif

 

 

4. 결론

이번 포스팅에 사용된 코드를 작성하면서 조금? 복잡한 데이터 구조에 메모리 생성 과정이 헷갈리는 걸 느꼈는데 저처럼 이런 부분이 아직 자리잡지 않으신 분들이 보면 도움이 될 거라 생각합니다. 마지막으로 이번 포스팅에 사용되었던 스크립터블 오브젝트의 사용 방법 링크를 남깁니다.

 

 

반응형

댓글