데이터파이프라인 타워 부분 개선

This commit is contained in:
2026-02-01 11:31:50 +09:00
parent 9d870625ce
commit fe046611b0
43 changed files with 2181 additions and 45 deletions

View File

@@ -0,0 +1,353 @@
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<string, IPrefabSetup> 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($"<color=green>[CSVToSOImporter] Import complete: {successCount} succeeded, {failCount} failed</color>");
if (towerImported)
{
AutoConfigureBuildingManager();
}
AssetDatabase.Refresh();
}
private static void InitializePrefabSetups()
{
prefabSetups = new Dictionary<string, IPrefabSetup>
{
{ "Monster", new MonsterPrefabSetup() },
{ "Tower", new TowerPrefabSetup() }
};
}
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];
string templateName = prefabSetup.GetTemplateName();
string templatePath = $"Assets/Data/Templates/{templateName}.prefab";
GameObject template = AssetDatabase.LoadAssetAtPath<GameObject>(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++;
}
}
Debug.Log($"[CSVToSOImporter] {typeName}: {successCount} prefabs created/updated");
// If towers were imported, auto-configure BuildingManager
// TowerData now extends BuildingData, so it can be used directly!
if (typeName == "Tower")
{
Debug.Log($"<color=cyan>[CSVToSOImporter] Tower import complete, TowerData extends BuildingData now!</color>");
}
return true;
}
private static bool CreatePrefabFromRow(string typeName, string[] headers, string[] values, GameObject template, IPrefabSetup prefabSetup)
{
string 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<ScriptableObject>(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}");
}
}
}
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);
GameObject prefabObj = PrefabUtility.SaveAsPrefabAsset(prefabInstance, prefabPath);
GameObject.DestroyImmediate(prefabInstance);
// Now set the prefab reference on data
if (data is BuildingData buildingData)
{
buildingData.prefab = prefabObj;
Debug.Log($"[CSVToSOImporter] Set prefab reference: {buildingData.name} -> {prefabObj.name}");
}
// Save data asset
if (isNew)
{
AssetDatabase.CreateAsset(data, soAssetPath);
}
else
{
EditorUtility.SetDirty(data);
}
// Force save assets to disk
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
return true;
}
private static void AutoConfigureBuildingManager()
{
BuildingManager buildingManager = GameObject.FindObjectOfType<BuildingManager>();
if (buildingManager == null)
{
Debug.LogError("<color=red>[CSVToSOImporter] BuildingManager not found in scene! Please add a BuildingManager component to a GameObject in your scene.</color>");
return;
}
// Load TowerData (which extends BuildingData)
string[] towerDataGuids = AssetDatabase.FindAssets("t:TowerData", new[] { "Assets/Data/ScriptableObjects" });
List<BuildingData> allTowers = new List<BuildingData>();
Debug.Log($"<color=cyan>[CSVToSOImporter] Found {towerDataGuids.Length} TowerData assets</color>");
foreach (string guid in towerDataGuids)
{
string assetPath = AssetDatabase.GUIDToAssetPath(guid);
TowerData towerData = AssetDatabase.LoadAssetAtPath<TowerData>(assetPath);
if (towerData == null)
{
Debug.LogWarning($"[CSVToSOImporter] Failed to load TowerData: {assetPath}");
continue;
}
if (towerData.prefab == null)
{
Debug.LogWarning($"<color=yellow>[CSVToSOImporter] TowerData {towerData.name} has no prefab reference - skipping</color>");
continue;
}
allTowers.Add(towerData);
Debug.Log($"<color=green>[CSVToSOImporter] Added tower: {towerData.buildingName}</color>");
}
if (allTowers.Count == 0)
{
Debug.LogWarning("<color=yellow>[CSVToSOImporter] No TowerData with valid prefabs found!</color>");
Debug.LogWarning("<color=yellow>Run 'Northbound > Diagnose Tower System' to see what's wrong</color>");
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($"<color=cyan>========================================</color>");
Debug.Log($"<color=cyan>🏗️ TOWER IMPORT COMPLETE!</color>");
Debug.Log($"<color=cyan>========================================</color>");
Debug.Log($"<color=green>✓ BuildingManager automatically configured!</color>");
Debug.Log($"<color=green>✓ Added {allTowers.Count} TowerData to availableBuildings list</color>");
Debug.Log($"<color=green>✓ Ready to play - all towers will appear in quickslot!</color>");
Debug.Log($"<color=cyan>========================================</color>");
foreach (var towerData in allTowers)
{
Debug.Log($"<color=green> - {towerData.buildingName}</color>");
}
Debug.Log($"<color=cyan>========================================</color>");
}
private static object ParseValue(string value, System.Type type)
{
if (string.IsNullOrEmpty(value))
{
return type.IsValueType ? System.Activator.CreateInstance(type) : null;
}
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<string> result = new List<string>();
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();
}
}
}