using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; using Northbound.Data; namespace Northbound.Editor { public class CSVToSOImporter : EditorWindow { private static Dictionary prefabSetups; [MenuItem("Tools/Data/Import All CSV")] public static void ImportAllCSV() { InitializePrefabSetups(); string dataPath = Path.Combine(Application.dataPath, "../GameData"); if (!Directory.Exists(dataPath)) { Debug.LogError("[CSVToSOImporter] GameData folder not found!"); return; } string[] csvFiles = Directory.GetFiles(dataPath, "*.csv"); int successCount = 0; int failCount = 0; bool towerImported = false; foreach (string csvFile in csvFiles) { string fileName = Path.GetFileNameWithoutExtension(csvFile); if (ImportCSV(fileName)) { successCount++; if (fileName == "Tower") towerImported = true; } else { failCount++; } } Debug.Log($"[CSVToSOImporter] Import complete: {successCount} succeeded, {failCount} failed"); if (towerImported) { AutoConfigureBuildingManager(); } AssetDatabase.Refresh(); } private static void InitializePrefabSetups() { prefabSetups = new Dictionary { { "Monster", new MonsterPrefabSetup() }, { "Creep", new CreepPrefabSetup() }, { "Tower", new TowerPrefabSetup() }, { "Player", new PlayerPrefabSetup() }, { "Upgrade", new UpgradePrefabSetup() } }; } private static bool ImportCSV(string typeName) { string csvPath = Path.Combine(Application.dataPath, "../GameData", $"{typeName}.csv"); if (!File.Exists(csvPath)) { Debug.LogWarning($"[CSVToSOImporter] CSV file not found: {csvPath}"); return false; } if (!prefabSetups.ContainsKey(typeName)) { Debug.LogWarning($"[CSVToSOImporter] No prefab setup found for type: {typeName}"); return false; } IPrefabSetup prefabSetup = prefabSetups[typeName]; // Upgrade는 템플릿이 필요 없음 GameObject template = null; if (typeName != "Upgrade") { string templateName = prefabSetup.GetTemplateName(); string templatePath = $"Assets/Data/Templates/{templateName}.prefab"; template = AssetDatabase.LoadAssetAtPath(templatePath); if (template == null) { Debug.LogError($"[CSVToSOImporter] Template not found: {templatePath}"); return false; } } string[] csvLines = File.ReadAllLines(csvPath); if (csvLines.Length < 2) { Debug.LogWarning($"[CSVToSOImporter] CSV file is empty or has no data: {csvPath}"); return false; } string[] headers = ParseCSVLine(csvLines[0]); int successCount = 0; for (int i = 1; i < csvLines.Length; i++) { if (string.IsNullOrWhiteSpace(csvLines[i])) continue; string[] values = ParseCSVLine(csvLines[i]); if (CreatePrefabFromRow(typeName, headers, values, template, prefabSetup)) { successCount++; } } if (typeName == "Player") { Debug.Log($"[CSVToSOImporter] {typeName}: Player prefab updated successfully"); } else { Debug.Log($"[CSVToSOImporter] {typeName}: {successCount} prefabs created/updated"); } if (typeName == "Tower") { Debug.Log($"[CSVToSOImporter] Tower import complete!"); } return true; } private static bool CreatePrefabFromRow(string typeName, string[] headers, string[] values, GameObject template, IPrefabSetup prefabSetup) { string soPath; if (typeName == "Upgrade") { // Upgrade는 Resources 폴더에 저장 (런타임 자동 로드용) soPath = "Assets/Resources/Data/ScriptableObjects/Upgrade"; Directory.CreateDirectory(Path.Combine(Application.dataPath, "Resources/Data/ScriptableObjects/Upgrade")); } else { soPath = $"Assets/Data/ScriptableObjects/{typeName}"; Directory.CreateDirectory(Path.Combine(Application.dataPath, $"ScriptableObjects/{typeName}")); } int id = 0; for (int i = 0; i < headers.Length && i < values.Length; i++) { if (headers[i].ToLower() == "id") { int.TryParse(values[i], out id); break; } } if (id == 0) { Debug.LogWarning($"[CSVToSOImporter] No valid ID found in row"); return false; } ScriptableObject data; string soAssetPath = $"{soPath}/{typeName}{id}.asset"; data = AssetDatabase.LoadAssetAtPath(soAssetPath); bool isNew = false; if (data == null) { data = CreateInstance(typeName + "Data"); isNew = true; } for (int i = 0; i < headers.Length && i < values.Length; i++) { string fieldName = CSVToCamelCase(headers[i]); var field = data.GetType().GetField(fieldName); if (field != null) { try { object value = ParseValue(values[i], field.FieldType); field.SetValue(data, value); } catch (System.Exception e) { Debug.LogWarning($"[CSVToSOImporter] Failed to set {fieldName}: {e.Message}"); } } } GameObject prefabObj; if (typeName == "Player") { PlayerPrefabSetup.UpdatePlayerPrefab((PlayerData)data); prefabObj = null; } else if (typeName == "Upgrade") { // Upgrade는 프리팹이 필요 없음 prefabObj = null; } else { string prefabPath = $"Assets/Prefabs/{typeName}/{typeName}{id}.prefab"; Directory.CreateDirectory(Path.Combine(Application.dataPath, $"Prefabs/{typeName}")); GameObject prefabInstance = GameObject.Instantiate(template); prefabInstance.name = $"{typeName}{id}"; prefabSetup.SetupPrefab(prefabInstance, data); prefabObj = PrefabUtility.SaveAsPrefabAsset(prefabInstance, prefabPath); GameObject.DestroyImmediate(prefabInstance); } // Now set the prefab reference on data if (data is TowerData towerData) { towerData.prefab = prefabObj; Debug.Log($"[CSVToSOImporter] Set prefab reference: {towerData.name} -> {prefabObj.name}"); } // Save data asset if (isNew) { AssetDatabase.CreateAsset(data, soAssetPath); } else { EditorUtility.SetDirty(data); } AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); return true; } private static void AutoConfigureBuildingManager() { BuildingManager buildingManager = GameObject.FindFirstObjectByType(); if (buildingManager == null) { Debug.LogError("[CSVToSOImporter] BuildingManager not found in scene! Please add a BuildingManager component to a GameObject in your scene."); return; } string[] towerDataGuids = AssetDatabase.FindAssets("t:TowerData", new[] { "Assets/Data/ScriptableObjects" }); List allTowers = new List(); Debug.Log($"[CSVToSOImporter] Found {towerDataGuids.Length} TowerData assets"); foreach (string guid in towerDataGuids) { string assetPath = AssetDatabase.GUIDToAssetPath(guid); TowerData towerData = AssetDatabase.LoadAssetAtPath(assetPath); if (towerData == null) { Debug.LogWarning($"[CSVToSOImporter] Failed to load TowerData: {assetPath}"); continue; } if (towerData.prefab == null) { Debug.LogWarning($"[CSVToSOImporter] TowerData {towerData.name} has no prefab reference - skipping"); continue; } allTowers.Add(towerData); Debug.Log($"[CSVToSOImporter] Added tower: {towerData.buildingName}"); } if (allTowers.Count == 0) { Debug.LogWarning("[CSVToSOImporter] No TowerData with valid prefabs found!"); Debug.LogWarning("Run 'Northbound > Diagnose Tower System' to see what's wrong"); return; } allTowers.Sort((a, b) => string.Compare(a.buildingName, b.buildingName)); buildingManager.availableBuildings.Clear(); foreach (var towerData in allTowers) { buildingManager.availableBuildings.Add(towerData); } EditorUtility.SetDirty(buildingManager); EditorSceneManager.MarkSceneDirty(EditorSceneManager.GetActiveScene()); Debug.Log($"========================================"); Debug.Log($"🏗️ TOWER IMPORT COMPLETE!"); Debug.Log($"========================================"); Debug.Log($"✓ BuildingManager automatically configured!"); Debug.Log($"✓ Added {allTowers.Count} TowerData to availableBuildings list"); Debug.Log($"✓ Ready to play - all towers will appear in quickslot!"); Debug.Log($"========================================"); foreach (var towerData in allTowers) { Debug.Log($" - {towerData.buildingName}"); } Debug.Log($"========================================"); } private static object ParseValue(string value, System.Type type) { if (string.IsNullOrEmpty(value)) { return type.IsValueType ? System.Activator.CreateInstance(type) : null; } // List 타입 처리 (세미콜론 구분) if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>)) { System.Type elementType = type.GetGenericArguments()[0]; var elements = value.Split(';').Select(s => s.Trim()).Where(s => !string.IsNullOrEmpty(s)).ToList(); IList list = (IList)System.Activator.CreateInstance(type); foreach (string element in elements) { object elementValue = ParseValue(element, elementType); if (elementValue != null) { list.Add(elementValue); } } return list; } if (type == typeof(int)) { int result; if (int.TryParse(value, out result)) return result; } else if (type == typeof(float)) { float result; if (float.TryParse(value, out result)) return result; } else if (type == typeof(bool)) { bool result; if (bool.TryParse(value, out result)) return result; } else if (type == typeof(string)) { return value; } return type.IsValueType ? System.Activator.CreateInstance(type) : null; } private static string CSVToCamelCase(string csvName) { if (string.IsNullOrEmpty(csvName)) return csvName; string[] parts = csvName.Split('_'); for (int i = 1; i < parts.Length; i++) { if (!string.IsNullOrEmpty(parts[i])) { parts[i] = char.ToUpper(parts[i][0]) + parts[i].Substring(1); } } return string.Join("", parts); } private static string[] ParseCSVLine(string line) { List result = new List(); bool inQuotes = false; string current = ""; foreach (char c in line) { if (c == '"') { inQuotes = !inQuotes; } else if (c == ',' && !inQuotes) { result.Add(current); current = ""; } else { current += c; } } result.Add(current); return result.ToArray(); } } }