411 lines
14 KiB
C#
411 lines
14 KiB
C#
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<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() },
|
|
{ "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<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++;
|
|
}
|
|
}
|
|
|
|
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($"<color=cyan>[CSVToSOImporter] Tower import complete!</color>");
|
|
}
|
|
|
|
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<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}");
|
|
}
|
|
}
|
|
}
|
|
|
|
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<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;
|
|
}
|
|
|
|
string[] towerDataGuids = AssetDatabase.FindAssets("t:TowerData", new[] { "Assets/Data/ScriptableObjects" });
|
|
List<TowerData> allTowers = new List<TowerData>();
|
|
|
|
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;
|
|
}
|
|
|
|
// List<T> 타입 처리 (세미콜론 구분)
|
|
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<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();
|
|
}
|
|
}
|
|
}
|