Notice
Recent Posts
Archives
Today
Total
«   2024/06   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30
Recent Comments
관리 메뉴

우당탕탕 개발일지

[Unity] CSV 파일 파싱 및 자동 인스턴스화 구현 (advanced) 본문

Unity

[Unity] CSV 파일 파싱 및 자동 인스턴스화 구현 (advanced)

devchop 2024. 3. 6. 12:29

파일은 업로드한다. 너무길기때문엥..

CSVReader.cs
0.01MB

 

 

1. CSV파일 제작

csv파일은 다음과 같은 형식으로 작성한다. 맨 윗 행은 설명을위해 비워놓았으며, CSVReader에도 이가 반영되어있다. 첫번째 행부터 변수이름을 입력할 수 있게끔 param에 변수가 저장된 행 번호를 입력하게끔 되어있다. (default 는 2번째 행이다.)

2 행에 변수의 이름이, 아래부터는 값을 입력한다.

2. 클래스정의

클래스 정의는 다음과 같이 선언한다. (위 이미지를 예시로 들겠다.) csv파일에서 정의한 변수의 이름과 대소문자를 모두 동일하게 맞춰야한다.

Class 정의

3. 파일 읽기(Addressable)

csv 파일을 프로젝트 안에 넣고, 읽기작업을 한다. Unity 에서 제공하는 Addressable을 이용하여 로드하고있다 . csv파싱은 보통 한번 읽은 뒤 다시 파일을 열 일이 없기 때문에, 파싱작업이 끝나면 unload를 해서 효율적으로 메모리관리를 할 수 있다. packageManager에서 다운받을 수 있고 사용방법도 so 간단

 

Addressables.LoadAssetAsync<TextAsset>(path).Completed += (handle) =>
        {
            action?.Invoke(CSVReader.Read(handle.Result, 0));
            Addressables.Release(handle);
        };

 

Addressable 사용은 다음과같이한다. 더 자세한 설명은 하지않겠다. 따로정리된게 아마 있을것이다 

Read() 함수에 두번째 인자로 EmptyLineCount 가 있다. 이는 첫 몇행을 띄우고 변수가 정의되어있는지를 나타낸다. 기본값은 1이다. 난 거의 첫행에는 설명을 적어놓기 때문이다.

 

3. 파싱하기

파싱은 여러가지 버전을 제공한다.

 

1. LoadCSVRaw() : 인스턴스화 하지 않고 추출된 데이터를 바로 리턴한다. List<Dictionary<string,object>> 이다.

1-1. LoadCSVLawWithEmptyLint() :1번과 리턴값이 동일하지만, csv에서  첫번째 행에 변수가 정의된 경우에 사용한다.

2. LoadCSVListDict<key, Class>() : 인스턴스화 된 것을 dictionary<key, list<Class>> 형식으로 반환받고싶을때 사용한다. keyId를 인자로 넘겨줘야한다. (거의대부분 "id" 가 key값이다) 한 개의 key값에 여러 개의 인스턴스가 있을때 사용된다. 예를들면 학급에서, Dictionary<반번호, List<학생>> 이런식으로 정리하고싶을때 사용한다.

3. LoadCSVDict<key, Class>() : 3번과 동일하게 Dictionary를 사용하는데, 한개의 키와 한개의 Value가 1:1 대응될때 사용한다.

예를들면 Dictionary<학번,학생> 이럴때 말이다

4. LoadCSVList<Class>() : 그냥 리스트로 반환받고싶을때 사용한다. 제일많이 사용되는듯.

4-1. LoadCSVListWithoutEmptyLine<Class>() : 4번과 리턴값이 동일하지만, csv 에서 첫 번째 행에 변수가 정의된 경우에 사용한다.

 

4. 인스턴스화(업그레이드된부분)

1번을 제외하고는 모두 인스턴스화가 필요하다. 이제 예시로 들었던 FurnitureCSV 로 자동 인스턴스화해서 , 2~4번중 하나의 자료구조에 담으면 된다.

 

static T CreateInstance<T>(Dictionary<string, object> readData) where T : new()
    {
        T instance = new T();
        Type fieldType = typeof(T);
        FieldInfo[] fieldInfo = fieldType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

        for (int i = 0; i < fieldInfo.Length; i++)
        {

            string fieldName = fieldInfo[i].Name.Trim();
            var strs = fieldName.Split('>', '<', ' ');
            if (strs == null || strs.Length <= 0) continue;

            fieldName = strs[0].Equals("") ? strs[1] : strs[0];


            var obj = Parse(fieldInfo[i].FieldType, fieldName, readData);
            fieldInfo[i].SetValue(instance, obj);
        }

        return instance;
    }

 

Reflection을 사용해서 변수 type, 변수이름을 받아온다.

인자로 들어온 Dictionary<string,object> 는 Dictionary<변수이름, 값> 형식으로 들어있기 때문에, object값을 알맞는 변수type으로 파싱해서 넣어주면된다.

FieldInfo를 이용해 SetValue()를 해준뒤, 값이 모두 세팅된 instance변수를 리턴해준다.

 

 

 

5. 호출하기

호출은 아래처럼하면된다. Addressable은 비동기로 로드되기 때문에, 만약 로드가 끝날때까지 대기가 필요할 경우 코루틴을 사용하거나 complete flag 를 넣는것이 좋다. 따라서 아래 함수를 호출하고 바로 값을 확인하려 한다면 오류가 나거나 비어있는 값이 들어있을테니 조심하자!

  Dictionary<int, FurnitureCSV> furnitures;

    string path_furnitures = "Assets/06_CSVs/regions/regions - furnitures.csv";
    public IEnumerator Parse()
    {
        CSVReader.LoadCSVDict<int, FurnitureCSV>(path_furnitures, "id", dict => furnitures = dict);
        yield return null;

    }

 

 

 

참고 블로그

https://masw.tistory.com/entry/C%EC%97%90%EC%84%9C-%ED%81%B4%EB%9E%98%EC%8A%A4-%EB%A9%A4%EB%B2%84-%EB%B3%80%EC%88%98%EB%A5%BC-%EC%9D%B4%EB%A6%84%EC%9C%BC%EB%A1%9C-%EC%A0%91%EA%B7%BC%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95