Files
ProjectMD/Assets/Editor/DataImporter/CSVToSOImporter.cs

348 lines
11 KiB
C#

// Assets/Editor/DataImporter/CSVToSOImporter.cs
using UnityEngine;
using UnityEditor;
using System;
using System.IO;
using System.Reflection;
using System.Collections.Generic;
namespace DigAndDefend.Editor
{
public static class CSVToSOImporter
{
private static readonly string GAMEDATA_PATH = Path.Combine(
Application.dataPath,
"..", "GameData"
);
private static readonly string SO_BASE_PATH = "Assets/Data/ScriptableObjects";
public static void ImportAll()
{
Debug.Log("=== 전체 데이터 Import 시작 ===");
var csvFiles = Directory.GetFiles(GAMEDATA_PATH, "*.csv");
int totalSuccess = 0;
int totalFail = 0;
foreach (var csvPath in csvFiles)
{
if (csvPath.Contains("Backups"))
continue;
string schemaName = Path.GetFileNameWithoutExtension(csvPath);
if (schemaName.StartsWith("."))
continue;
Debug.Log($"\n📋 {schemaName} Import 시작...");
if (ImportSchema(schemaName))
{
totalSuccess++;
}
else
{
totalFail++;
}
}
Debug.Log("\n" + new string('=', 60));
Debug.Log($"🎉 전체 Import 완료: 성공 {totalSuccess}개, 실패 {totalFail}개");
Debug.Log(new string('=', 60));
}
public static bool ImportSchema(string schemaName)
{
string csvPath = Path.Combine(GAMEDATA_PATH, $"{schemaName}.csv");
string outputPath = Path.Combine(SO_BASE_PATH, schemaName);
if (!File.Exists(csvPath))
{
Debug.LogError($"❌ CSV 파일을 찾을 수 없습니다: {csvPath}");
return false;
}
if (!Directory.Exists(outputPath))
{
Directory.CreateDirectory(outputPath);
}
string className = schemaName + "Data";
Type dataType = FindScriptableObjectType(className);
if (dataType == null)
{
Debug.LogError($"❌ ScriptableObject 클래스를 찾을 수 없습니다: {className}");
Debug.LogError($"💡 generate_csharp_classes.py를 먼저 실행하세요.");
return false;
}
Debug.Log($" ✅ 클래스 발견: {dataType.FullName}");
int importCount = 0;
try
{
var lines = File.ReadAllLines(csvPath, System.Text.Encoding.UTF8);
if (lines.Length < 2)
{
Debug.LogError("❌ CSV 파일에 데이터가 없습니다.");
return false;
}
var headers = ParseCSVLine(lines[0]);
Debug.Log($" 📊 {lines.Length - 1}개 행, {headers.Length}개 컬럼");
for (int lineIndex = 1; lineIndex < lines.Length; lineIndex++)
{
string line = lines[lineIndex].Trim();
if (string.IsNullOrEmpty(line))
continue;
try
{
var values = ParseCSVLine(line);
if (values.Length != headers.Length)
{
Debug.LogWarning($" ⚠️ 행 {lineIndex + 1}: 컬럼 개수 불일치");
continue;
}
var so = ScriptableObject.CreateInstance(dataType);
for (int col = 0; col < headers.Length; col++)
{
string fieldName = headers[col];
if (string.IsNullOrEmpty(fieldName))
continue;
string camelFieldName = ToCamelCase(fieldName);
Debug.Log(" 🔍 필드 매핑: " + camelFieldName);
FieldInfo field = dataType.GetField(camelFieldName, BindingFlags.Public | BindingFlags.Instance);
if (field == null)
{
if (lineIndex == 1)
{
Debug.LogWarning($" ⚠️ 필드를 찾을 수 없습니다: {camelFieldName}");
}
continue;
}
string cellValue = values[col];
SetFieldValue(so, field, cellValue);
}
string assetName = GetAssetName(so, lineIndex);
string fileName = assetName + ".asset";
string assetPath = Path.Combine(outputPath, fileName);
AssetDatabase.CreateAsset(so, assetPath);
importCount++;
Debug.Log($" ✅ {assetName}");
}
catch (Exception e)
{
Debug.LogError($" ❌ 행 {lineIndex + 1} Import 실패: {e.Message}");
}
}
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
Debug.Log($" 🎉 {schemaName} Import 완료: {importCount}개");
return true;
}
catch (Exception e)
{
Debug.LogError($"❌ {schemaName} Import 실패: {e.Message}");
Debug.LogException(e);
return false;
}
}
private static string[] ParseCSVLine(string line)
{
var result = new List<string>();
bool inQuotes = false;
string currentValue = "";
for (int i = 0; i < line.Length; i++)
{
char c = line[i];
if (c == '"')
{
inQuotes = !inQuotes;
}
else if (c == ',' && !inQuotes)
{
result.Add(currentValue.Trim());
currentValue = "";
}
else
{
currentValue += c;
}
}
result.Add(currentValue.Trim());
return result.ToArray();
}
private static Type FindScriptableObjectType(string className)
{
string fullName = $"DigAndDefend.Data.{className}";
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
Type type = assembly.GetType(fullName);
if (type != null && type.IsSubclassOf(typeof(ScriptableObject)))
{
return type;
}
}
return null;
}
private static string ToCamelCase(string snakeCase)
{
if (string.IsNullOrEmpty(snakeCase))
return snakeCase;
var parts = snakeCase.Split('_');
if (parts.Length == 1)
return snakeCase;
string result = parts[0];
for (int i = 1; i < parts.Length; i++)
{
if (parts[i].Length > 0)
{
result += char.ToUpper(parts[i][0]) + parts[i].Substring(1);
}
}
return result;
}
private static void SetFieldValue(object obj, FieldInfo field, string value)
{
if (string.IsNullOrWhiteSpace(value))
{
field.SetValue(obj, null);
return;
}
Type fieldType = field.FieldType;
if (fieldType.IsGenericType && fieldType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
Type underlyingType = Nullable.GetUnderlyingType(fieldType);
object convertedValue = ConvertValue(value, underlyingType);
field.SetValue(obj, convertedValue);
}
else
{
object convertedValue = ConvertValue(value, fieldType);
field.SetValue(obj, convertedValue);
}
}
private static object ConvertValue(string value, Type targetType)
{
if (string.IsNullOrWhiteSpace(value))
return null;
if (targetType == typeof(int))
{
if (int.TryParse(value, out int result))
return result;
return 0;
}
else if (targetType == typeof(float))
{
if (float.TryParse(value, out float result))
return result;
return 0f;
}
else if (targetType == typeof(double))
{
if (double.TryParse(value, out double result))
return result;
return 0.0;
}
else if (targetType == typeof(bool))
{
string lower = value.ToLower();
return lower == "true" || lower == "1" || lower == "yes";
}
else if (targetType == typeof(string))
{
// ⭐ 이스케이프된 줄바꿈 복원
return value.Replace("\\n", "\n");
}
return value;
}
private static string GetAssetName(object so, int lineNumber)
{
Type type = so.GetType();
// ⭐ 1순위: id 필드 찾기
FieldInfo idField = type.GetField("id", BindingFlags.Public | BindingFlags.Instance);
if (idField != null)
{
var idValue = idField.GetValue(so);
if (idValue != null)
{
// 스키마 이름 추출 (TowersData → Towers, TowerData → Tower)
string typeName = type.Name;
if (typeName.EndsWith("Data"))
{
typeName = typeName.Substring(0, typeName.Length - 4);
}
// Tower1, Enemy5 형식
return $"{typeName}{idValue:D3}";
}
}
// ⭐ 2순위: name 필드 찾기
FieldInfo nameField = type.GetField("name", BindingFlags.Public | BindingFlags.Instance);
if (nameField != null)
{
var nameValue = nameField.GetValue(so);
if (nameValue != null && !string.IsNullOrWhiteSpace(nameValue.ToString()))
{
string name = nameValue.ToString();
name = name.Replace(" ", "");
name = System.Text.RegularExpressions.Regex.Replace(name, @"[^a-zA-Z0-9_가-힣]", "");
return name;
}
}
// 3순위: 행 번호 사용
return $"Data_Row{lineNumber}";
}
}
}