1. Excel 파일 만들기
엑셀파일의 규격이 정해져 있어야 작동하기 때문에 규격에 맞는 테이블 데이터를 만드는 작업이 필요하다.
테이블의 구조는 아래와 같다.
1행은 Column의 이름을 나타낸다. (ID, Name, Desc, IconPath)
2행은 데이터의 타입을 나타낸다.
=> 2행은 자동화 코드를 만들 때 변수의 타입을 지정해 주기 위해서 사용했다.
3행부터 테이블 데이터를 입력한다.
2. Excel -> CSV 파일 변환
CSV 파일로 바꾸는 방법은 여러가지가 있기 때문에 어떻게든 CSV 파일만 생성하면 된다.
ㆍExcel에서 *.csv 파일로 저장하기
ㆍ웹에서 변환
XLS (EXCEL) CSV 변환 (온라인 무료) — Convertio
xls 파일(들) 업로드 컴퓨터, Google Drive, Dropbox, URL에서 선택하거나 이 페이지에서 드래그하여 선택해 주세요.
convertio.co
ㆍ이럴 때 사용하기 위해 만들어둔 기능
[Python] 엑셀 파일 CSV로 바꾸기
import osimport pandasimport tkinter as tkfrom tkinter import filedialogdef select_excel_folder(): global excel_folder_path excel_folder_path = filedialog.askdirectory(initialdir=f'{os.getcwd()}', title='Excel 폴더 선택') button_excel_folder.configure(
mintchobab.tistory.com
3. 실행 코드
전체 코드는 git에 있으니까
public class TableMaker : MonoBehaviour
{
public static string CSVFolderPath = "Assets/Tables";
public static string ScriptableFolderPath = "Assets/Resources/ScriptableObject";
public static void MakeTableScript()
{
try
{
string[] guids = AssetDatabase.FindAssets("", new string[] { CSVFolderPath });
foreach (var guid in guids)
{
string path = AssetDatabase.GUIDToAssetPath(guid);
TextAsset asset = AssetDatabase.LoadAssetAtPath<TextAsset>(path);
if (asset == null || !path.EndsWith(".csv", StringComparison.OrdinalIgnoreCase))
continue;
List<Dictionary<string, object>> tableDataList = TableCSVReader.Read(asset, out string[] header, out string[] types);
if (header.Length == 0 || types.Length == 0)
throw new Exception($"{nameof(TableMaker)} : Table Header or Type Error");
WriteCode(asset.name, header, types);
}
EditorUtility.SetDirty(scriptableObj);
AssetDatabase.Refresh();
AssetDatabase.SaveAssets();
}
catch(Exception e)
{
Debug.LogError($"{nameof(TableMaker)} : {e.Message}");
}
}
}
특정 폴더의 모든 csv 파일을 읽어와서 테이블로 변환하는 작업
※ 테이블 데이터가 아닌 csv 파일이 있다면 정상작동하지 않는다.
using System.Collections.Generic;
using System.Text.RegularExpressions;
using UnityEngine;
public class TableCSVReader : MonoBehaviour
{
static string SPLIT_RE = @",(?=(?:[^""]*""[^""]*"")*(?![^""]*""))";
static string LINE_SPLIT_RE = @"\r\n|\n\r|\n|\r";
static char[] TRIM_CHARS = { '\"' };
public static List<Dictionary<string, object>> Read(TextAsset data)
{
return ReadInternal(data, out string[] header, out string[] types);
}
public static List<Dictionary<string, object>> Read(TextAsset data, out string[] header, out string[] types)
{
return ReadInternal(data, out header, out types);
}
public static List<Dictionary<string, object>> ReadInternal(TextAsset data, out string[] header, out string[] types)
{
var list = new List<Dictionary<string, object>>();
header = new string[] { };
types = new string[] { };
if (data == null)
{
Debug.LogError($"{nameof(TableCSVReader)} : TextAsset is Null");
return null;
}
var lines = Regex.Split(data.text, LINE_SPLIT_RE);
if (lines.Length <= 1)
return list;
header = Regex.Split(lines[0], SPLIT_RE);
types = Regex.Split(lines[1], SPLIT_RE);
for (var i = 2; i < lines.Length; i++)
{
var values = Regex.Split(lines[i], SPLIT_RE);
if (values.Length == 0 || values[0] == "")
continue;
var entry = new Dictionary<string, object>();
for (var j = 0; j < header.Length && j < values.Length; j++)
{
string value = values[j];
value = value.TrimStart(TRIM_CHARS).TrimEnd(TRIM_CHARS).Replace("\\", "");
object finalvalue = value;
if (int.TryParse(value, out int intValue))
{
finalvalue = intValue;
}
else if (float.TryParse(value, out float floatValue))
{
finalvalue = floatValue;
}
entry[header[j]] = finalvalue;
}
list.Add(entry);
}
return list;
}
}
csv 파일을 읽어서 데이터를 추출하는 작업
앞서 말했듯 테이블의 1행이 Dictionary의 key가 돼서 key의 맞는 데이터를 탐색하는 데 사용되고
2행은 최종 작성되는 Table.cs 파일의 변수 타입이 된다.
private static void WriteCode(string tableName, string[] header, string[] types)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("using System;");
sb.AppendLine("using System.Collections.Generic;");
sb.AppendLine("using UnityEngine;");
sb.AppendLine();
sb.AppendLine($"public class {tableName} : SingletonScriptableObject<{tableName}>");
sb.AppendLine("{");
sb.AppendLine("\t[SerializeField]");
sb.AppendLine("\tpublic List<TableData> datas = new List<TableData>();");
sb.AppendLine();
sb.AppendLine("\tpublic TableData this[int index]");
sb.AppendLine("\t{");
sb.AppendLine("\t\tget");
sb.AppendLine("\t\t{");
sb.AppendLine("\t\t\treturn datas.Find(x => x.ID == index);");
sb.AppendLine("\t\t}");
sb.AppendLine("\t}");
sb.AppendLine();
sb.AppendLine("\t[Serializable]");
sb.AppendLine("\tpublic class TableData");
sb.AppendLine("\t{");
for (int i = 0; i < header.Length; i++)
{
sb.AppendLine($"\t\tpublic {types[i]} {header[i]};");
}
sb.AppendLine("\t}");
sb.AppendLine();
sb.AppendLine("\tpublic void AddData(TableData data)");
sb.AppendLine("\t{");
sb.AppendLine("\t\tdatas.Add(data);");
sb.AppendLine("\t}");
sb.AppendLine("}");
sb.AppendLine();
string textsaver = $"Assets/{tableName}.cs";
if (File.Exists(textsaver))
{
File.Delete(textsaver);
}
File.AppendAllText(textsaver, sb.ToString());
}
cs 파일을 생성하고 미리 정해진 규칙대로 코드를 작성 후 저장
이렇게 결과 파일이 생성된다.
primary key의 역할을 하는 ID 값으로 데이터를 검색하게 하고 싶어서 인덱서를 사용했다.
ex)
string name = TestTableFirst.Instance[101].Name;
string desc = TestTableFirst.Instance[101].Desc;
4. EditorWindow
함수를 실행시켜 줄 에디터 창을 만들었다.
public class TableMakerWindow : EditorWindow
{
[MenuItem("Custom/TableMakerWindow")]
public static void Init()
{
TableMakerWindow window = (TableMakerWindow)EditorWindow.GetWindow(typeof(TableMakerWindow));
window.minSize = new Vector2(500, 300);
window.Show();
}
public void OnGUI()
{
GUILayout.Label("Path Settings", EditorStyles.boldLabel);
TableMaker.CSVFolderPath = EditorGUILayout.TextField("CSV Folder Path", TableMaker.CSVFolderPath);
TableMaker.ScriptableFolderPath = EditorGUILayout.TextField("Scriptable Folder Path", TableMaker.ScriptableFolderPath);
EditorGUILayout.Space(20);
GUILayout.BeginHorizontal();
if (GUILayout.Button("Make Table Script"))
{
TableMaker.MakeTableScript();
}
else if (GUILayout.Button("Make Scriptable Object"))
{
TableMaker.MakeScriptableObject();
}
GUILayout.EndHorizontal();
}
}
5. 실행 결과
후기
초기 목표는 한번의 클릭으로 모든 작업이 완료되는 자동화였지만 방법을 잘 모르겠어서 기능을 나누게 되었다.
그리고 개선해야 될 점이 남아있어서 아직 사용은 할 수 없을 것 같지만 개선 or 다른 방법을 찾아서 좀 더 편하게 할 수 있도록 만들어봐야겠다.
※ 개선 사항
1. Excel -> CSV의 변환 개선
2. cs 파일 만들기 + ScriptableObject 파일 만들기가 한 번에 실행될 수 있도록 변경
- 지금 상태에서도 jenkins를 이용하면 동작하게 할 수 있을 것 같지만 그건 추후에 테스트해보기로...
3. 예외 처리 + 예외 처리 테스트
- 잘못된 규격의 테이블
- 파일 오류
- 빌드 테스트
- 빌드 후 데이터 사용 가능 여부
프로젝트 링크
GitHub - mintchobab/CSVToScriptableObject
Contribute to mintchobab/CSVToScriptableObject development by creating an account on GitHub.
github.com
'Unity > 기능구현' 카테고리의 다른 글
[Unity] Inspector에서 함수 실행하기 (0) | 2024.08.14 |
---|---|
[Unity] Prefab의 Missing Script 자동으로 삭제하기 (0) | 2024.07.19 |
[Unity] 게이지바 구현하기 (0) | 2024.02.25 |
[Unity] 모든 하위 오브젝트의 레이어 변경하기 (0) | 2022.04.19 |
[Unity] Debug Log를 Text 파일의 형태로 저장하기 (0) | 2022.04.19 |