구형 CSVImporter 제거

This commit is contained in:
2026-02-01 01:58:08 +09:00
parent bc0967dec6
commit 9d870625ce
9 changed files with 0 additions and 561 deletions

View File

@@ -51,12 +51,9 @@
<Compile Include="Assets\Scripts\Editor\MonsterPrefabSetup.cs" /> <Compile Include="Assets\Scripts\Editor\MonsterPrefabSetup.cs" />
<Compile Include="Assets\FlatKit\[Render Pipeline] URP\Water\Editor\Tooltips.cs" /> <Compile Include="Assets\FlatKit\[Render Pipeline] URP\Water\Editor\Tooltips.cs" />
<Compile Include="Assets\Scripts\Editor\EnemyPortalEditor.cs" /> <Compile Include="Assets\Scripts\Editor\EnemyPortalEditor.cs" />
<Compile Include="Assets\Editor\DataImporter\ImporterWindow.cs" />
<Compile Include="Assets\Scripts\Editor\ObstacleSpawnerEditor.cs" /> <Compile Include="Assets\Scripts\Editor\ObstacleSpawnerEditor.cs" />
<Compile Include="Assets\Editor\DataImporter\CSVDebugger.cs" />
<Compile Include="Assets\Scripts\Editor\IPrefabSetup.cs" /> <Compile Include="Assets\Scripts\Editor\IPrefabSetup.cs" />
<Compile Include="Assets\FlatKit\[Render Pipeline] URP\Water\Editor\WaterEditor.cs" /> <Compile Include="Assets\FlatKit\[Render Pipeline] URP\Water\Editor\WaterEditor.cs" />
<Compile Include="Assets\Editor\DataImporter\CSVToSOImporter.cs" />
<Compile Include="Assets\FlatKit\Shaders\Editor\ObjectOutlineEditorUtils.cs" /> <Compile Include="Assets\FlatKit\Shaders\Editor\ObjectOutlineEditorUtils.cs" />
<Compile Include="Assets\FlatKit\Demos\Common\Scripts\Motion\Editor\LinearMotionEditor.cs" /> <Compile Include="Assets\FlatKit\Demos\Common\Scripts\Motion\Editor\LinearMotionEditor.cs" />
<Compile Include="Assets\FlatKit\Shaders\GradientSkybox\Editor\GradientSkyboxEditor.cs" /> <Compile Include="Assets\FlatKit\Shaders\GradientSkybox\Editor\GradientSkyboxEditor.cs" />

View File

@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 208723e08bdfdc347bc1af53f4be5c3c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 8f4724b7534e0a443a8eabbe6f5483ad
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,94 +0,0 @@
// Assets/Editor/DataImporter/CSVDebugger.cs
using UnityEngine;
using UnityEditor;
using System.IO;
using System.Text;
namespace Northbound.Editor
{
public static class CSVDebugger
{
[MenuItem("Northbound/Debug CSV Files")]
public static void DebugCSVFiles()
{
string gameDataPath = Path.Combine(Application.dataPath, "..", "GameData");
var csvFiles = Directory.GetFiles(gameDataPath, "*.csv");
Debug.Log("=== CSV 파일 디버그 ===\n");
foreach (var csvPath in csvFiles)
{
string fileName = Path.GetFileName(csvPath);
if (fileName.StartsWith("."))
continue;
Debug.Log($"📄 파일: {fileName}");
// 파일 크기
FileInfo fileInfo = new FileInfo(csvPath);
Debug.Log($" 📊 크기: {fileInfo.Length} bytes");
// 인코딩 테스트
try
{
// UTF-8로 읽기
var lines = File.ReadAllLines(csvPath, Encoding.UTF8);
Debug.Log($" ✅ UTF-8 읽기 성공: {lines.Length}줄");
// ⭐ 모든 줄 출력
for (int i = 0; i < lines.Length; i++)
{
string line = lines[i];
Debug.Log($" 📋 [{i}] 길이:{line.Length} | 내용: '{line}'");
// 특수문자 확인
if (string.IsNullOrWhiteSpace(line))
{
Debug.Log($" ⚠️ 빈 줄 또는 공백만 있음");
}
// 바이트 출력 (첫 20바이트만)
byte[] bytes = Encoding.UTF8.GetBytes(line);
string byteStr = "";
for (int j = 0; j < Mathf.Min(bytes.Length, 20); j++)
{
byteStr += $"{bytes[j]:X2} ";
}
if (bytes.Length > 20) byteStr += "...";
Debug.Log($" 바이트: {byteStr}");
}
// BOM 체크
byte[] fileBytes = File.ReadAllBytes(csvPath);
if (fileBytes.Length >= 3 && fileBytes[0] == 0xEF && fileBytes[1] == 0xBB && fileBytes[2] == 0xBF)
{
Debug.Log($" UTF-8 BOM 있음");
}
else
{
Debug.Log($" BOM 없음");
}
// 전체 파일 바이트 (처음 100바이트만)
string fileBytesStr = "";
for (int i = 0; i < Mathf.Min(fileBytes.Length, 100); i++)
{
fileBytesStr += $"{fileBytes[i]:X2} ";
if ((i + 1) % 20 == 0) fileBytesStr += "\n ";
}
Debug.Log($" 📦 파일 바이트 (처음 100):\n {fileBytesStr}");
}
catch (System.Exception e)
{
Debug.LogError($" ❌ 읽기 실패: {e.Message}");
}
Debug.Log("");
}
Debug.Log("=== 디버그 완료 ===");
}
}
}

View File

@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 95247e4274c204446ac0f9c45c334c56

View File

@@ -1,320 +0,0 @@
using UnityEngine;
using UnityEditor;
using System;
using System.IO;
using System.Reflection;
using System.Collections; // 추가: 리스트 처리를 위해 필요
using System.Collections.Generic;
namespace Northbound.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";
private static readonly string PREFAB_BASE_PATH = "Assets/Prefabs";
private static readonly string TEMPLATE_BASE_PATH = "Assets/Data/Templates";
private static System.Collections.Generic.Dictionary<string, IPrefabSetup> prefabSetups =
new System.Collections.Generic.Dictionary<string, IPrefabSetup>();
static CSVToSOImporter()
{
RegisterPrefabSetups();
}
private static void RegisterPrefabSetups()
{
prefabSetups.Clear();
prefabSetups["Monster"] = new MonsterPrefabSetup();
// To add new data types, create a class implementing IPrefabSetup
// Example:
// prefabSetups["Tower"] = new TowerPrefabSetup();
// prefabSetups["Player"] = new PlayerPrefabSetup();
}
[MenuItem("Tools/Data/Import All CSV")] // 메뉴 추가 (편의성)
public static void ImportAll()
{
Debug.Log("=== 전체 데이터 Import 시작 ===");
if (!Directory.Exists(GAMEDATA_PATH)) { Debug.LogError("GameData 폴더가 없습니다."); return; }
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;
if (ImportSchema(schemaName)) totalSuccess++;
else totalFail++;
}
Debug.Log($"\n🎉 Import 완료: 성공 {totalSuccess}, 실패 {totalFail}");
}
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)) return false;
if (!Directory.Exists(outputPath)) Directory.CreateDirectory(outputPath);
string className = schemaName + "Data";
Type dataType = FindScriptableObjectType(className);
if (dataType == null) return false;
try
{
var lines = File.ReadAllLines(csvPath, System.Text.Encoding.UTF8);
if (lines.Length < 2) return false;
var headers = ParseCSVLine(lines[0]);
var createdSOs = new List<ScriptableObject>();
for (int lineIndex = 1; lineIndex < lines.Length; lineIndex++)
{
string line = lines[lineIndex].Trim();
if (string.IsNullOrEmpty(line)) continue;
var values = ParseCSVLine(line);
var so = ScriptableObject.CreateInstance(dataType);
for (int col = 0; col < headers.Length; col++)
{
if (col >= values.Length) break;
string fieldName = ToCamelCase(headers[col]);
FieldInfo field = dataType.GetField(fieldName, BindingFlags.Public | BindingFlags.Instance);
if (field != null) SetFieldValue(so, field, values[col]);
}
string assetName = GetAssetName(so, lineIndex);
string soPath = Path.Combine(outputPath, $"{assetName}.asset");
AssetDatabase.CreateAsset(so, soPath);
createdSOs.Add(so);
GenerateOrUpdatePrefab(schemaName, assetName, so, lineIndex);
}
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
return true;
}
catch (Exception e)
{
Debug.LogError($"{schemaName} 오류: {e.Message}");
return false;
}
}
private static void SetFieldValue(object obj, FieldInfo field, string value)
{
Type fieldType = field.FieldType;
// 1. 리스트 타입 처리 (List<T>)
if (fieldType.IsGenericType && fieldType.GetGenericTypeDefinition() == typeof(List<>))
{
Type elementType = fieldType.GetGenericArguments()[0];
IList list = (IList)field.GetValue(obj);
if (list == null)
{
list = (IList)Activator.CreateInstance(fieldType);
field.SetValue(obj, list);
}
list.Clear();
if (!string.IsNullOrWhiteSpace(value))
{
// 쉼표(,) 혹은 세미콜론(;)으로 분할 가능하게 설정
string[] items = value.Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var item in items)
{
object convertedItem = ConvertValue(item.Trim(), elementType);
if (convertedItem != null) list.Add(convertedItem);
}
}
return;
}
if (string.IsNullOrWhiteSpace(value))
{
field.SetValue(obj, null);
return;
}
// 2. Nullable 처리
if (fieldType.IsGenericType && fieldType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
Type underlyingType = Nullable.GetUnderlyingType(fieldType);
field.SetValue(obj, ConvertValue(value, underlyingType));
}
else
{
// 3. 일반 타입 처리
field.SetValue(obj, ConvertValue(value, fieldType));
}
}
private static object ConvertValue(string value, Type targetType)
{
if (string.IsNullOrWhiteSpace(value)) return null;
try
{
if (targetType == typeof(int)) return int.Parse(value);
if (targetType == typeof(float)) return float.Parse(value);
if (targetType == typeof(bool))
{
string l = value.ToLower();
return l == "true" || l == "1" || l == "yes";
}
if (targetType == typeof(string)) return value.Replace("\\n", "\n");
return Convert.ChangeType(value, targetType);
}
catch { return null; }
}
private static int GetColumnIndex(string[] headers, string columnName)
{
for (int i = 0; i < headers.Length; i++)
{
if (headers[i].Equals(columnName, System.StringComparison.OrdinalIgnoreCase))
{
return i;
}
}
return -1;
}
private static void GenerateOrUpdatePrefab(string schemaName, string prefabName, ScriptableObject so, int lineNumber)
{
string prefabOutputPath = Path.Combine(PREFAB_BASE_PATH, schemaName);
if (!Directory.Exists(prefabOutputPath))
{
Directory.CreateDirectory(prefabOutputPath);
}
string prefabPath = Path.Combine(prefabOutputPath, $"{prefabName}.prefab");
IPrefabSetup prefabSetup = GetPrefabSetup(schemaName);
if (prefabSetup == null)
{
Debug.LogWarning($"[CSVImporter] No prefab setup found for {schemaName}, skipping prefab generation");
return;
}
GameObject template = LoadTemplate(schemaName);
if (template == null)
{
Debug.LogWarning($"[CSVImporter] No template found for {schemaName}, skipping prefab generation");
return;
}
if (AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath) != null)
{
UpdateExistingPrefab(prefabPath, so, prefabSetup);
}
else
{
GameObject prefabInstance = GameObject.Instantiate(template);
prefabInstance.name = prefabName;
PrefabUtility.SaveAsPrefabAsset(prefabInstance, prefabPath);
GameObject.DestroyImmediate(prefabInstance);
UpdateExistingPrefab(prefabPath, so, prefabSetup);
Debug.Log($"[CSVImporter] Created prefab: {prefabPath}");
}
AssetDatabase.SaveAssets();
}
private static GameObject LoadTemplate(string schemaName)
{
string templatePath = Path.Combine(TEMPLATE_BASE_PATH, $"{schemaName}Template.prefab");
return AssetDatabase.LoadAssetAtPath<GameObject>(templatePath);
}
private static IPrefabSetup GetPrefabSetup(string schemaName)
{
prefabSetups.TryGetValue(schemaName, out IPrefabSetup setup);
return setup;
}
private static void UpdateExistingPrefab(string prefabPath, ScriptableObject so, IPrefabSetup prefabSetup)
{
GameObject prefabContents = PrefabUtility.LoadPrefabContents(prefabPath);
prefabSetup.SetupPrefab(prefabContents, so);
PrefabUtility.SaveAsPrefabAsset(prefabContents, prefabPath);
GameObject.DestroyImmediate(prefabContents);
}
private static void RemoveOldModel(GameObject prefab)
{
Transform oldModel = prefab.transform.Find("Model");
if (oldModel != null)
{
GameObject.DestroyImmediate(oldModel.gameObject);
}
}
// --- 유틸리티 메서드 (기존과 동일) ---
private static string[] ParseCSVLine(string line)
{
var result = new List<string>();
bool inQuotes = false;
string current = "";
for (int i = 0; i < line.Length; i++)
{
char c = line[i];
if (c == '"') inQuotes = !inQuotes;
else if (c == ',' && !inQuotes) { result.Add(current.Trim()); current = ""; }
else current += c;
}
result.Add(current.Trim());
return result.ToArray();
}
private static Type FindScriptableObjectType(string className)
{
string fullName = $"Northbound.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 string GetAssetName(object so, int lineNumber)
{
Type type = so.GetType();
FieldInfo idField = type.GetField("id", BindingFlags.Public | BindingFlags.Instance);
if (idField != null)
{
var val = idField.GetValue(so);
if (val != null) return $"{type.Name.Replace("Data", "")}{val:D3}";
}
return $"Data_Row{lineNumber}";
}
}
}

View File

@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 79f372743463a5e4cb3a9b915de9bebe

View File

@@ -1,122 +0,0 @@
// Assets/Editor/DataImporter/ImporterWindow.cs
using UnityEngine;
using UnityEditor;
using System.IO;
using System.Linq;
namespace Northbound.Editor
{
public class ImporterWindow : EditorWindow
{
private Vector2 scrollPosition;
[MenuItem("Northbound/Data Importer")]
public static void ShowWindow()
{
var window = GetWindow<ImporterWindow>("Data Importer");
window.minSize = new Vector2(400, 300);
}
private void OnGUI()
{
GUILayout.Label("CSV → ScriptableObject Importer", EditorStyles.boldLabel);
GUILayout.Space(10);
EditorGUILayout.HelpBox(
"GameData 폴더의 CSV 파일을 ScriptableObject로 변환합니다.\n" +
"자동 생성된 C# 클래스를 사용합니다.",
MessageType.Info
);
GUILayout.Space(10);
scrollPosition = GUILayout.BeginScrollView(scrollPosition);
string gameDataPath = Path.Combine(Application.dataPath, "..", "GameData");
if (!Directory.Exists(gameDataPath))
{
EditorGUILayout.HelpBox(
"GameData 폴더를 찾을 수 없습니다.",
MessageType.Warning
);
}
else
{
var csvFiles = Directory.GetFiles(gameDataPath, "*.csv")
.Where(f => !f.Contains("Backups") && !Path.GetFileName(f).StartsWith("."))
.ToArray();
if (csvFiles.Length == 0)
{
EditorGUILayout.HelpBox(
"CSV 파일이 없습니다.\n" +
"sync-from-notion.ps1을 먼저 실행하세요.",
MessageType.Warning
);
}
else
{
GUILayout.Label("발견된 CSV 파일:", EditorStyles.boldLabel);
GUILayout.Space(5);
foreach (var filePath in csvFiles)
{
string fileName = Path.GetFileNameWithoutExtension(filePath);
GUILayout.BeginHorizontal();
GUILayout.Label($"📊 {fileName}", GUILayout.Width(200));
if (GUILayout.Button("Import", GUILayout.Width(100)))
{
ImportSingle(fileName);
}
GUILayout.EndHorizontal();
}
}
}
GUILayout.EndScrollView();
GUILayout.Space(20);
GUI.backgroundColor = Color.green;
if (GUILayout.Button("Import All Data", GUILayout.Height(50)))
{
if (EditorUtility.DisplayDialog(
"전체 데이터 Import",
"모든 CSV 파일을 읽어서 ScriptableObject를 생성합니다.\n" +
"기존 파일은 덮어씌워집니다.",
"Import All",
"Cancel"))
{
CSVToSOImporter.ImportAll();
}
}
GUI.backgroundColor = Color.white;
GUILayout.Space(10);
EditorGUILayout.HelpBox(
"Import 후 Assets/Data/ScriptableObjects 폴더를 확인하세요.",
MessageType.None
);
}
private void ImportSingle(string schemaName)
{
if (EditorUtility.DisplayDialog(
$"{schemaName} Import",
$"{schemaName}.csv를 읽어서 ScriptableObject를 생성합니다.\n" +
"기존 파일은 덮어씌워집니다.",
"Import",
"Cancel"))
{
CSVToSOImporter.ImportSchema(schemaName);
}
}
}
}

View File

@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 017c665c9c855fa43b55f1d61c238903