공부/Unity

NuGetForUnity, CSVHelper, Resources, 에디터 커스터마이징

월러비 2025. 9. 9. 18:55
  • 메모리공간을 구성하는 객체의 요소 → 필드
  • 외부 라이브러리로 직렬화를 하기 위해서는 명시적으로 직렬화라는것을 알려야한다.
    • [Serializable] : 직렬화 어트리뷰트
    • private 접근도 직렬화가 되지 않는다.
    • 직렬화가 되지 않는다면 해당 작업이 스킵되는 것이다.
    • [NonSerialized]과의 차이점 : 이것은 직렬화되는 클래스에서 특정 필드를 직렬화시키고싶지 않을때 사용한다.
    [Serializable]
    public class SaveDatas
    {
        public Vector3 position;
        public Quaternion rotation;
        public Vector3 scale;
        
        [NonSerialized]
        public Color color;
    }
    
    • [HideInInspector] : 인스펙터에서 가려진다.
    • [SerializeField] : private여도 직렬화가 되어 인스펙터에 노출되는 어트리뷰트다.
      • 사실 지금은 연습이기에 public으로 작성중이지만, 현업에서는 private로 선언하고 작성해야한다.
  • 외부 라이브러리는 Vector같은 구조를 직렬화하지 못한다.
    • 이러한 경우는 JsonConverter를 ‘재정의’할 수 있다.
  • Load로 오브젝트를 추가하는 방법
    • 기존의 정보에 추가하는 경우
    • 기존의 정보를 싹 초기화하고 추가하는 경우
  • 인스펙터에 public으로 할당된 데이터는 어디에 저장될까? : 씬 파일(에셋)에 기록되어있다.
    • 즉, 이것도 직렬화 역 직렬화다.
    • Json 직렬화 역직렬화처럼, 해당 씬에서 작업한 내용들(오브젝트 생성, 인스펙터 값 조정 등)이 해당 씬 파일에 직렬화되어서 저장되는 것이다.

NuGetForUnity

  • 라이브러리 연결을 편하게 해주는 툴이다.
  • 외부 라이브러리에 연결된 또 다른 라이브러리가 있을 때 그것을 또 다운받아서 연결해야한다.
    • 또, 다운받은 라이브러리가 호환이 안되는 경우가 생길 수 있다.
    • 이러한 문제들을 해결해주는 패키지가 ‘누겟 라이브러리’다.

설치 방법

  • https://github.com/GlitchEnzo/NuGetForUnity
  • 깃허브 사이트에서 How do I install NuGetForUnity 항목 확인
  • Package Manager를 이용하는 2번째 방법 확인
  • 해당 링크 복사 후 유니티 Package Manager에서 Install by URL로 다운로드
  • 상단에 Nuget 항목이 생긴다.
  • 만약 안된다면?
    • 깃허브 홈페이지에 릴리즈가 있다.
    • 그곳에 UnityPackgae 파일이 있으니 그것을 에셋 임포트 하면 된다.

사용 방법

  • Nuget → Manage Nuget Packages : 누겟으로 설치할 수 있는 외부 라이브러리들을 확인할 수 있다.
    • NewtonJson도 이곳에서 다운로드할 수 있다.

CSVHelper

사용 이유

  • 아이템 하나만 보더라도 아이디, 기능 등 여러 데이터들이 있고, 이 아이템은 하나가 아니다.
    • 또, 아이템 뿐만이 아니라 여러 클래스의 데이터들이 매우 많다.
    • 이것을 하드코딩으로 짠다면? : 하나하나 찾으러 돌아다녀야한다.

사용 방법

  • 데이터 테이블을 생성해야한다.
  • 데이터의 구별은 이름이 아니라 ‘ID’로 구분해야한다.
    • 아이디별 내용도 가서 수정해줘야하나 전부 바꾸기엔 좀 어렵다.
    • 되도록 하드코딩 없이만 작성하도록 해야한다.
  • 셀 하나마다 파싱해서 구조체처럼 사용이 가능하다.
  • csv 파일을 엑셀로 사용하는것은 추천하지 않는다.
    • 인코딩이 저장할때마다 바뀌어서 그렇다.
      • 저장할때마다 계속 수동으로 수정해줘야한다.
    • vscode 또는 구글 스프레드 시트를 사용하는것을 추천한다.
    • Edit CSV가 vscode의 확장프로그렘이다.
  • 아예 메모리에 항상 놓고 빠르게 가져와서 사용하는 것이다.
    • 언어가 여러개면 조금 불필요한 면이 있다.
using CsvHelper;

 private void Start()
 {
     //Resources 폴더에 있는 에셋을 가져오는 코드
     TextAsset csv = Resources.Load<TextAsset>("DataTables/StringTable");
     //Resources.UnloadAsset(csv); //한번 로드된 에셋은 메모리에서 내려오지 않는다. / 즉, 언로드를 하지 않으면 계속 메모리에 남아있게 된다.

     //CultureInfo.InvariantCulture : 지역설정 무관하게 처리하는 옵션이다.
     using (var reader = new StringReader(csv.text))
     using(var csvReader = new CsvReader(reader, CultureInfo.InvariantCulture))
     {
         var records = csvReader.GetRecords<CsvData>();
         foreach(var record in records)
         {
             Debug.Log($"{record.Id} : {record.String}");
         }
     }

     //Debug.Log(csv.text);
 }

Resources

  • 데이터 테이블들은 Json과는 달리 빌드에 포함시킬 데이터들이다.
  • 빌드하면 패킹되어서 나오기 때문에 Asset에 넣은 파일들의 경로가 맞지 않게된다.
    • Resources 폴더를 이용해서 경로(파일 이름)로 받는다.
    • Addressable
  • 유니티에서 약속한 이름이기 때문에 폴더를 사용한다면 이름을 틀리면 안된다.
    • 다른 폴더에서도 리소스 폴더가 있다면 모든 리소스 폴더가 합쳐져서 가져오게 된다.
    • 문제 : 이름이 중복될 가능성이 있다.
      • 겹치지 않게 수동으로 관리해줘야한다.
  • CSV는 Text 에셋으로 유니티에 들어가게 된다.

데이터 테이블 매니저

  • 필요한 데이터 테이블을 가져오는 매니저를 생성해야한다.

언어 변경

#if UNITY_EDITOR
    public Languages editorLang;
#endif
    
    //빌드할때 이렇게 하면 된다.
    private void OnEnable()
    {
#if UNITY_EDITOR
        if (Application.isPlaying)
        {
            OnChangeLanguage();
        }
        else
        {
            OnChangeLanguage(editorLang);
        }
#else
        OnChangeLanguage();
#endif
    }

    public void OnChangeLanguage()
    {
        var stringTable = DataTableManager.StringTable;
        text.text = stringTable.Get(stringId);
    }

#if UNITY_EDITOR
    public void OnChangeLanguage(Languages lang)
    {
        var tableId = DataTableIds.StringTableIds[(int)lang];
        var stringTable = DataTableManager.Get<StringTable>(tableId);
        text.text = stringTable.Get(stringId);
    }
#endif

에디터 커스터마이징

  • 인스펙터 public 오버라이드
  • Editor 폴더 : Resources 폴더처럼 유니티에서 약속된 이름이다.
    • 이 이외에서 에디터를 바꾼다면 빌드 이후로 에러가 난다.

CSV 내용 정리

  • 하드코딩 피하는 방법 : 외부파일 또는 서버를 통해 데이터를 전달받는다.
  • 데이터 테이블이란? : 서식을 정해서 키와 벨류를 작성하는 파일이다.
  • CSV 포멧 파싱 방법
    • 내가 만든다 → 가능하지만 원하는 기능이 제대로 동작하지 않을 수 있다. (최적화 , 성능)
    • 오픈소스 라이브러리 사용
      • 외부 라이브러리 사용방법
        1. 잘 만든 유니티 패키지 임포트(NetonJson)
        2. Nuget 패키지 매니저 사용
          1. 플러그인 설치가 필요하다.

데이터 테이블 추상 클래스 생성

  • Load(string filename) :
  • Load(strig csvText)
    • 텍스트를 받으면 stringReader에 넘기고 파싱된 값을 받는다.
    • GetRecords : 키와 벨류 짝에 맞춰서 객체로 리턴한다.
    • 그것을 ToList로 리스트를 만들어 반환한다.
  • public static List<T> LoadCSV<T>(string csvText) { using (var reader = new StringReader(csvText)) using (var csvReader = new CsvReader(reader, CultureInfo.InvariantCulture)) { var records = csvReader.GetRecords<T>(); return records.ToList(); } }

StringTable

  • 데이터 클래스 선언
    • Id와 문자열을 저장한다. → 키와 벨류
  • 딕셔너리 선언 → 키와 벨류 저장
  • Load(string filename) : 딕셔너리에 키와 벨류를 저장하는 함수다.
public override void Load(string fileame)
{
    dictionary.Clear(); //비어있는 상태로 만들고 새로 채우는 것이다.

    var path = string.Format(FormatPath, fileame); //파일 경로
    var textAsset = Resources.Load<TextAsset>(path);

    var list = LoadCSV<Data>(textAsset.text);
    foreach( var item in list)
    {
        if (dictionary.ContainsKey(item.Id)) 
        {
            Debug.LogError($"키 중복: {item.Id}");
        }
        else
        {
            dictionary.Add(item.Id, item.String);
        }
    }
}
  • Get(string Key) : 키에 해당하는 벨류 반환
public string Get(string key)
{
    if (!dictionary.ContainsKey(key))
    {
        return Unknown;
    }

    return dictionary[key];
}

DataTableManager

  • 옵션 : 디자인패턴의 일부 넣어서 사용 또는 싱글톤 또는 static 멤버로 만들어 사용
    • 가장 간단 : static 선언
  • tables : 게임에서 사용할 테이블들을 담아놓는 필드다.
    • 데이터 테이블 ID로 미리 저장한 데이터 테이블의 데이터를 사용한다.
private static readonly Dictionary<string, DataTable> tables = new Dictionary<string, DataTable>();
  • Get<T>(string id) : 언제든 원할때 키를 호출해서 값을 사용하는 것이다.
  • Init() : 미리 테이블들을 가져오는 함수다.
    //모든 데이터 테이블 가져오기
    private static void Init()
    {
#if UNITY_EDITOR
        foreach(var id in DataTableIds.StringTableIds)
        {
            var table = new StringTable();

            //table.Load("StringTable");
            table.Load(id);
            tables.Add(id, table);
        }
#else
        var stringTable = new StringTable();

        //table.Load("StringTable");
        stringTable.Load(DataTableIds.String); //현재 언어설정의 언어 테이블 로드
        tables.Add(DataTableIds.String, stringTable);
#endif

    }
  • DataTableIds : 모든 데이터 테이블의 id 저장
  • public static class DataTableIds { public static readonly string[] StringTableIds = { "StringTableKr", "StringTableEn", "StringTableJp", }; public static string String => StringTableIds[(int)Variables.Language]; //언어 선택 } public static class Variables { public static Languages Language = Languages.Korean; }
  • 에디터에서 변경된것을 미리 보기위해 분기를 나눴다.
    • 에디터 : 내가 들고있는 모든 데이터 테이블의 아이디를 전부 가져오기
    • 플레이 중 : 현재 설정된 아이디로만 저장

에디터 커스터마이징

  • [CustomEditor(typeof(LocalizationTest))] : 해당 클래스의 인스펙터 노출을 오버라이드한다는 의미이다.
[CustomEditor(typeof(LocalizationTest))] //해당 클래스의 인스펙터 노출을 오버라이드한다는 의미이다.
public class LocalizationTextEditor : Editor
{
    //인스펙터 GUI 갱신 함수다. / 마우스 클릭처럼 특정 포커스나 입력 이벤트가 있을때만 호출되는 함수다.
    public override void OnInspectorGUI()
    {
        //target : 해당 클래스의 컴포넌트를 의미한다.
        var text = target as LocalizationTest;
        var newId = EditorGUILayout.TextField("String ID", text.stringId); //스트링을 그려주고 읽는 함수다. / (라벨, 내용)
        text.stringId = newId;
        var newLang = (Languages)EditorGUILayout.EnumPopup("Language", text.editorLang); //열거형을 그려주고 읽는 함수다. / 열거형은 System.enum 형으로 반환되기 때문에 형변환이 필수다.

        //아이디나 언어가 바뀌었다면
        if(newId != text.stringId || newLang != text.editorLang)
        {
            text.stringId = newId;
            text.editorLang = newLang;

            text.OnChangeLanguage(text.editorLang);

            EditorUtility.SetDirty(text); //갱신할 객체를 설정하는 함수다. / 사용 이유 : 여기만 갱신되고 다른곳은 갱신이 안되기 때문이다. / 에디터상에서 제대로 갱신되도록 하려하기 위해서다.
            //더티 플래그를 할당받은 객체만 갱신이 된다. -> 즉, 이 함수는 더티 플래그를 할당하는 것이다.
            //이걸 안하면 에디터에서 바꿨는데도 바뀌지 않는 오류가 있다.
        }
    }
}
  • OnInspectorGUI() : 인스펙터 GUI 갱신 함수다.
    • 마우스 클릭처럼 특정 포커스나 입력 이벤트가 있을때만 호출되는 함수다.
  • target : 해당 클래스의 컴포넌트를 의미한다.