[Project Talisman] #1. Data Converter 구현하기

2024. 4. 19. 14:57·활동/게임제작동아리 브릿지

대학교 졸업과 동시에 게임 제작 동아리 브릿지에 들어왔다. 3월 팀빌딩 이후, 서포터를 포함한 9명의 인원으로 타이쿤 + 어드벤처가 혼합된 게임 프로젝트를 개발 진행중에 있다.

대학생 게임 제작 동아리 브릿지 : https://bridgegames.tistory.com/

 

 

개발 가능한 정도의 기획서가 쌓이기 전이라 게임에서 범용적으로 사용되는 기능들을 우선으로 개발하기로 했고 평소 개발하고 싶었던 Excel To S.O 변환기를 구현하기로 했다.

 

Excel To S.O 변환기 특징

  • xlsx 확장자를 Unity ScriptableObject 파일로 변환한다.
  • 자동으로 ScriptableObject의 Script를 작성한다.
  • Unity Editor를 통해서 사용 가능하며, 런타임이 아니라 Editor 모드에서 사용 가능하다.

Excel To S.O 변환기 일명, Data Converter는 위와 같은 내용이 특징이다. Editor 모드에서 사용 가능하게 한 것은 런타임에서 진행될 경우, 게임 시작이나 로딩에서 이뤄지게 될 것이 자명하므로 xslx 파일을 읽고 작성하는 것을 런타임 이전에 부담을 가하는 것이 좋다고 판단했다.

 

위와 같은 생각뿐만 아니라, 아래 사진에 담겨있듯이 Data Converter를 만들기로 하면서 성능에 있어서 생각하는 방법으로 변환기를 구현하는 것이 올바른가에 대한 고민이 있었다.

생각이 정리되고는 S.O로 변환하는 과정을 제외하고 Excel에서 바로 데이터를 읽어서 관리하려고 했으나, 그냥 만들고 싶은 대로 만들기로 결정했다. 한 번 이런 도구도 만들어보고 싶었으니까..

 

xlsx를 유니티에서 불러오기

xlsx 확장자를 유니티로 불러오기 위해서 NPOI 라이브러리를 사용하기로 했다. xlsx 확장자를 읽기/쓰기가 가능한 라이브러리는 NPOI 외에도 다양했으나, 내 생각에는 가장 대중적이라 판단했고 그로인해서 참고할 수 있는 정보가 많을 것이라 생각했기 때문이다.

 

NPOI 설치는 아래 블로그를 참고했다.

 

[Unity] 유니티에서 Nuget 패키지 사용하기

유니티에서 Nuget 패키지를 사용하기 위해 쉽게 세팅하는 방법을 작성해봤습니다.

velog.io

 

UI Toolkit을 사용하여 Editor UI 만들기

Editor Window를 만들기 위해서 UI Toolkit을 사용하기로 했다. 이전부터 관심이 있어서 블로그에 잠깐 다뤄본 기록을 적은 적이 있었는데, 그 이후로 다룰 기회가 없어서 이 참에 사용해보기로 했다.

 

Script로 UI를 작성하다가 웹 프로그래밍을 손 놓은지 오래 되어서 그런지 개념이 가물가물 하길래 UI Builder를 통해서 다음과 같이 작업했다. 나중에 웹 프로그래밍도 다시 관심을 가져봐야겠다.

Unity - UI Builder

 

FileDialog 기능 추가

기능을 담을 EditorWindow 뼈대가 완성이 되었으니, 기능을 개발해야 한다. 우선 xslx 파일을 불러오는 기능을 개발했다.

 

이상하게도 Unity에서 FileDialog 기능이 추가된 것을 본적이 별로 없는 듯 하다. 이번 변환기는 Excel이 Unity 프로젝트의 Assets 폴더에 위치하지 않아도 불러오는 것이 중요했기에 EditorUtility 클래스를 통해서 Excel 파일을 불러오는 FileDialog 기능을 추가했다.

_excelPath = EditorUtility.OpenFilePanel("Open Excel File", Application.streamingAssetsPath, "xlsx");

 

 

Excel Read/Write 기능

public static class ExcelReader
{
    public static IWorkbook OpenFile(string filePath)
    {
        return GetFile(filePath);
    }

    private static IWorkbook GetFile(string filePath)
    { 
        using var workBook = File.Open(filePath, FileMode.Open, FileAccess.Read);
        return new XSSFWorkbook(workBook);
    }
    
    ...
    ...
}

Excel 파일을 읽고/쓰기 위해서 ExcelReader라는 정적 클래스를 생성하고 실질적인 구현 메서드인 GetFile의 캡슐화를 위해 Private로 접근 지시자를 설정하고 해당 메서드를 호출할 수 있는 OpenFile 메서드를 추가로 구현했다.

 

정확히는 예외 처리를 하기 위함이었는데, 추가되지 않아서 OpenFile 메서드는... 나중에 정리하거나 보완해야 할 것 같다.

 

public static class ExcelReader
{
    ...
    ...
    
    public static List<string> GetRow(int index, ISheet sheet)
    {
        try
        {
            return sheet.GetRow(index).Cells.ConvertAll(new Converter<ICell, string>(ConvertCellToString));
        }
        catch(Exception e)
        {
            Debug.LogError($"{e.Message}");
        }

        return null;
    }

    public static Dictionary<int, List<string>> GetToLastRow(int startIndex, ISheet sheet)
    {
        var endIndex = sheet.ActiveCell.Row;
        
        try
        {
            var result = new Dictionary<int, List<string>>();
            
            for (int i = startIndex; i <= endIndex; ++i)
            {
                var value = GetRow(i, sheet);
                result.Add(i, value);
            }

            return result;
        }
        catch (Exception e)
        {
            Debug.LogError($"{e.Message}");
        }

        return null;
    }
    
    ...
    ...
}

변환기의 EditorWindow 인터페이스를 보면 xlsx파일을 모두 변환하는 것이 아니라, 선택된 시트에 한해서 변환이 진행된다. 시트를 선택하는 것은 EditorWindow를 책임지고 Event를 관리하는 DataConverterEditor에서 RegisterValueChangedCallback 이벤트를 통해서 값을 받아 관리할 수 있게 했다.

 

public partial class DataConverterEditor : EditorWindow
{
    ...
    ...
        _excelSheetField.RegisterValueChangedCallback((x) =>
        {
            _excelData.sheet = _excelData.workBook.GetSheet(x.newValue);
            
            ShowColumns(_excelData.sheet);
            ShowPath(_excelData.sheet.SheetName);
        });
        
    ...
    ...
}

_excelSheetField는 Sheet를 담고 있는 Dropdown으로 값이 변경되면 RegisterValueChangedCallback 메서드를 통해서 Action을 실행한다.

 

이후 ShowColumns 메서드는 다음과 같이 구성되어있다.

public partial class DataConverterEditor : EditorWindow
{
    ...
    ...
    
    private void ShowColumns(ISheet sheet)
    {
        string viewText = "";
        
        // Data를 가져온다.
        _excelData.typeList = ExcelReader.GetRow(TypeIndex, sheet);
        _excelData.nameList = ExcelReader.GetRow(NameIndex, sheet);

        for (int i = 1; i <= _excelData.typeList.Count; ++i)
        {
            viewText += $"{_excelData.typeList[i - 1]} : {_excelData.nameList[i - 1]} " +
                          $"{((i % 5 == 0) ? "\n" : "\t")}";
        }
        
        _excelColumnViewer.value = viewText;
    }
        
    ...
    ...
}

TypeIndex와 NameIndex에 해당하는 정수를 ExcelReader의 GetRow 메서드에 선택된 Sheet 값이 존재하는 sheet 변수와 함께 인자로 보내어서 List<string> 타입을 반환받아 excelColumnViewer 컴포넌트에 출력한다. 그러면 다음과 같이 출력된다.

 

Script 생성하기

이 부분이 조금 걸렸다. Unity에서 Script를 생성하는 메서드를 찾아볼 수 있을까 했지만 찾지 못했고 파일 입출력을 통해서 해결할 수 있었다.

먼저 Template이 되는 txt 확장자 파일을 추가하고 ScriptableObject 클래스를 상속받아 Template의 뼈대를 만든다. 이후 #Script_name과 #Varaible은 Excel Sheet 이름과 Sheet 데이터로 변환해주는 것이다.

 

위와 같은 작업을 위해서 ScriptGenerator 정적 클래스를 생성하고 Script를 생성하는 CreateScript 메서드, Script 내부를 변환하는 ChangeScriptText 메서드, 확장자를 변경해주는 ChangeFileExtension 메서드를 추가했다.

 

public sealed class ScriptGenerator
{
    private const string _templatePath = @"\Script\Module\Data Converter\TemplateScript.cs.txt";
    
    ...
    ...
}

Template이 위치한 Path를 관리하는 string 변수를 상수로 선언하고 값을 입력한다.

 

public sealed class ScriptGenerator
{
    ...
    ...
    
    public static void CreateScript(ExcelData excelData)
    {
        string resultCreatePath = $"{excelData.scriptPath + "/" + excelData.sheet.SheetName + ".txt"}";
        string resultTemplatePath = $"{Application.dataPath + _templatePath}";
        
        if(File.Exists(resultCreatePath)){ File.Delete(resultCreatePath);}
        
        ChangeScriptText(excelData.sheet.SheetName, resultTemplatePath, resultCreatePath, excelData.typeList, excelData.nameList);
    }
    
    ...
    ...
}

이후 CreateScript 메서드를 통해서 생성될 Script의 Path와 Template의 상세 주소를 입력한다. File 클래스는 절대 경로로 설정되어도 보안 액세스 이슈를 나타내지 않기 때문에 절대 경로로 설정을 해줬다.

 

public sealed class ScriptGenerator
{
    ...
    ...
    
    private static void ChangeScriptText(string sheetName, string templatePath, string resultPath, List<string> typeData, List<string> variableData)
    {
        var templateText = File.ReadAllText(templatePath);

        var replaceText = templateText.Replace("#SCRIPT_NAME", sheetName);
        string variableText = "";

        for (int i = 0; i < typeData.Count; ++i)
        {
            variableText += $"\tpublic {typeData[i]} {variableData[i]};{((i == typeData.Count - 1) ? "" : "\n")}";
        }

        var path = ChangeFileExtension(resultPath);
        File.WriteAllText(path, "");
        File.WriteAllText(path, replaceText.Replace("#VARIABLE", variableText));
    }
    
    ...
    ...
}

ChangeScriptText 메서드를 통해서 #Script_Name을 Sheet의 이름으로 변경해주고 #Variable는 Excel 데이터로 변환해줄 수 있었다.

 

그 다음에는 ChangeFileExtension 메서드를 통해서 생성된 파일의 확장자를 변경해주고 파일을 생성한다.

 

ScriptableObjectGenerator 제작하기

원래는 ScriptGenerator 클래스에 포함하여 제작하려다가 똑같이 정적 클래스로 분리하여 개발했다. 클래스의 결합도를 낮추는 것이 책임 분산에 좋다고 생각했다.

 

이전에 개발한 기능을 조립하여 AssetDatabase 클래스의 CreateAsset 메서드로 생성할 수 있었다.

 

DataConverterEditor 클래스 분리

개발 후미에서 DataConverterEditor 클래스가 변수 선언으로 인해서 가독성이 좋지 않다고 생각하고 Partial 키워드를 통해서 클래스를 분리했다.

 

EditorWindow의 컴포넌트를 담당하는 변수와 변환기에 사용되는 변수들을 담당하는 DataConverterVariable 클래스를 추가함으로서 가독성을 살리고, 실질적인 구현 부분과 선언 부분을 나눌 수 있었다.

 

완성!

위와 같이 작업을 하는 Excel To S.O 변환기를 만들 수 있었다. 아직 개선해야 하는 점들이 존재하지만, 그럭저럭 쓸만할 것 같다.

 

개선 예정 사항

추가적으로 조금 더 유연하게 코드를 변경해야 할 것 같다.

'활동 > 게임제작동아리 브릿지' 카테고리의 다른 글

대학생 연합 게임 제작 동아리 브릿지(BRIDGE) 12기 후기  (2) 2024.07.11
[Project Talisman] #4. Status 시스템 개선하기  (0) 2024.06.19
[Project Talisman] #3. Data Converter 리팩터링  (0) 2024.04.22
[Project Talisman] #2. FSM 프레임워크 구현하기  (0) 2024.04.20
'활동/게임제작동아리 브릿지' 카테고리의 다른 글
  • 대학생 연합 게임 제작 동아리 브릿지(BRIDGE) 12기 후기
  • [Project Talisman] #4. Status 시스템 개선하기
  • [Project Talisman] #3. Data Converter 리팩터링
  • [Project Talisman] #2. FSM 프레임워크 구현하기
태역
태역
  • 태역
    RYULAB
    태역
  • 전체
    오늘
    어제
    • 분류 전체보기 N
      • 언어
        • C
        • C++
        • C#
      • 엔진, 프레임워크
        • Unity
        • Unreal
        • Electron
      • 공부 N
        • 디자인 패턴
        • 수학 N
        • CS
        • Git
        • 알고리즘 N
        • 자료구조
      • 코테
        • 프로그래머스
        • 백준
      • 독서
        • Effective C#
        • CLR via C#
        • 뇌를 자극하는 윈도우즈 시스템 프로그래밍
        • 오브젝트
        • CSAPP
        • OSTEP
      • 프로젝트
        • Unity
      • 개발 일지
        • 퓨처리티
        • 골든타임
      • 활동
        • 게임잼 후기
        • 게임제작동아리 브릿지
        • 크래프톤 정글
        • 기타
      • 기타
  • 블로그 메뉴

    • 링크

    • 공지사항

      • 2024 04 17
    • 인기 글

    • 태그

      인프런 #인프런강의후기 #게임개발 #게임개발강의 #인강후기 #강의후기 #게임개발자 #인프런강의
      티스토리챌린지
      오블완
    • 최근 댓글

    • 최근 글

    • hELLO· Designed By정상우.v4.10.3
    태역
    [Project Talisman] #1. Data Converter 구현하기
    상단으로

    티스토리툴바