구글 시트 로직 수정
This commit is contained in:
parent
ddb8e499c3
commit
8940e8654b
@ -1,3 +1,4 @@
|
||||
#if UNITY_EDITOR
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
@ -9,6 +10,7 @@
|
||||
using UnityEngine;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using JetBrains.Annotations;
|
||||
using Sirenix.OdinInspector;
|
||||
using UnityEditor;
|
||||
@ -30,8 +32,12 @@ public class GoogleSheetManager : Singleton<GoogleSheetManager>
|
||||
private string _namespace = "DDD";
|
||||
|
||||
[BoxGroup("기본 설정")]
|
||||
[SerializeField, Tooltip("적용시킬 시트의 이름들")]
|
||||
private List<string> _availSheets = new(){ "Sheet1", "Sheet2" };
|
||||
[SerializeField, Tooltip("코드/Enum/So 자동 생성 + 데이터 반영 시트")]
|
||||
private List<string> _autoCreateSheets = new();
|
||||
|
||||
[BoxGroup("기본 설정")]
|
||||
[SerializeField, Tooltip("기존 Data/So 유지, SO 데이터만 동기화 시트")]
|
||||
private List<string> _soSyncSheets = new();
|
||||
|
||||
[BoxGroup("기본 설정")]
|
||||
[SerializeField, Tooltip("Class, Json, So 생성 위치 \"/GenerateGoogleSheet\"")]
|
||||
@ -41,11 +47,9 @@ public class GoogleSheetManager : Singleton<GoogleSheetManager>
|
||||
[SerializeField, Tooltip("현재 사용중인 버전"), ReadOnly, UsedImplicitly]
|
||||
private string _currentVersion;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[BoxGroup("버전 복구")]
|
||||
[SerializeField, ValueDropdown(nameof(GetVersionOptions))]
|
||||
private int _restoreIndex;
|
||||
#endif
|
||||
|
||||
[BoxGroup("데이터 변경"), LabelText("수정자 이름")]
|
||||
[SerializeField, Required("반드시 수정자 이름을 입력해야 합니다\n이력을 남길 때 표시될 사용자 이름입니다.")]
|
||||
@ -67,14 +71,19 @@ public class GoogleSheetManager : Singleton<GoogleSheetManager>
|
||||
|
||||
private bool _alreadyCreatedSo;
|
||||
|
||||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||
private bool IsAuto(string name) => _autoCreateSheets.Contains(name);
|
||||
private bool IsSync(string name) => _soSyncSheets.Contains(name);
|
||||
private bool IsSelected(string name) => IsAuto(name) || IsSync(name);
|
||||
|
||||
[BoxGroup("데이터 변경")]
|
||||
[Button("데이터 최신화"), EnableIf(nameof(CanFetchData))]
|
||||
private async Task FetchGoogleSheet()
|
||||
{
|
||||
// 0) 이전 원본 JSON
|
||||
var prevLog = AssetDatabase.LoadAssetAtPath<GoogleSheetChangeLog>(ChangeLogAssetPath);
|
||||
string previousJson = prevLog?.Logs.LastOrDefault()?.JsonSnapshot ?? "";
|
||||
|
||||
// 1) 최신 원본 JSON 로드 (전체 시트)
|
||||
if (_isAccessGoogleSheet)
|
||||
{
|
||||
if (!IsValidGoogleSheetUrl(_googleSheetUrl))
|
||||
@ -92,21 +101,25 @@ private async Task FetchGoogleSheet()
|
||||
_json = LoadDataLocalJson();
|
||||
}
|
||||
|
||||
if (_json == null)
|
||||
if (string.IsNullOrEmpty(_json))
|
||||
{
|
||||
Debug.Log("Json is null. 최신화 실패");
|
||||
Debug.LogWarning("Json is null/empty. 최신화 실패");
|
||||
return;
|
||||
}
|
||||
|
||||
// 2) Diff/로그는 '전체 JSON' 기준
|
||||
var diffs = GoogleSheetFetchHelper.CompareJsonDiff(previousJson, _json);
|
||||
if (diffs.Count > 0)
|
||||
GoogleSheetDiffViewer.ShowWindow(diffs);
|
||||
|
||||
bool isJsonSaved = SaveFileOrSkip(JsonFullPath, _json);
|
||||
// 3) 워크 파일로 전체 JSON 저장(변경시만)
|
||||
bool savedJson = SaveFileOrSkip(JsonFullPath, _json);
|
||||
|
||||
// 4) 클래스 생성 (auto만, 파일 없을 때만 생성)
|
||||
bool createdScripts = false;
|
||||
try
|
||||
{
|
||||
GenerateClassFilesPerSheet(_json);
|
||||
createdScripts = GenerateClassFilesPerSheet(_json);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@ -114,13 +127,41 @@ private async Task FetchGoogleSheet()
|
||||
return;
|
||||
}
|
||||
|
||||
if (diffs.Count > 0 || isJsonSaved)
|
||||
// 5) 로그/백업은 전체 JSON 기준으로 변경시에만 기록
|
||||
if (!string.IsNullOrEmpty(previousJson))
|
||||
{
|
||||
_refreshTrigger = true;
|
||||
SaveChangeLog(_json);
|
||||
EditorPrefs.SetBool("GoogleSheetManager_ShouldCreateSO", true);
|
||||
AssetDatabase.Refresh();
|
||||
if (!previousJson.Equals(_json))
|
||||
SaveChangeLog(_json);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 첫 기록
|
||||
SaveChangeLog(_json);
|
||||
}
|
||||
|
||||
// 6) 새 스크립트가 생겼다면 컴파일 완료 대기
|
||||
if (createdScripts)
|
||||
{
|
||||
await WaitUntilScriptsReady();
|
||||
await Task.Delay(100); // 도메인 리로드 직후 안정화 여유
|
||||
}
|
||||
|
||||
// 7) SO 동기화는 항상 수행 (변경 없어도)
|
||||
bool ok = await CreateGoogleSheetSoAsync();
|
||||
if (!ok) Debug.LogWarning("SO 동기화 중 일부 실패가 있었습니다.");
|
||||
|
||||
// 8) 필요시 마무리 리프레시
|
||||
if (savedJson || createdScripts || diffs.Count > 0)
|
||||
AssetDatabase.Refresh();
|
||||
}
|
||||
|
||||
private async Task WaitUntilScriptsReady()
|
||||
{
|
||||
// 스크립트 생성 직후 컴파일이 끝날 때까지 대기
|
||||
while (EditorApplication.isCompiling)
|
||||
await Task.Delay(150);
|
||||
|
||||
AssetDatabase.Refresh();
|
||||
}
|
||||
|
||||
private bool CanFetchData()
|
||||
@ -266,15 +307,43 @@ private IEnumerable<ValueDropdownItem<int>> GetVersionOptions()
|
||||
/// </summary>
|
||||
private async Task<string> LoadDataGoogleSheet(string url)
|
||||
{
|
||||
using HttpClient client = new HttpClient();
|
||||
// 네트워크가 느리거나 프록시/방화벽 영향 받는 환경에서 멈춤 방지
|
||||
var handler = new HttpClientHandler
|
||||
{
|
||||
// 사내 프록시/보안 툴 때문에 멈출 수 있으면 필요에 따라 끄기
|
||||
UseProxy = true, // 필요하면 false 로 바꿔 테스트
|
||||
};
|
||||
|
||||
using var client = new HttpClient(handler)
|
||||
{
|
||||
Timeout = TimeSpan.FromSeconds(20) // ← 핵심: 타임아웃
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
byte[] dataBytes = await client.GetByteArrayAsync(url);
|
||||
return Encoding.UTF8.GetString(dataBytes);
|
||||
Debug.Log("[GSM] HTTP GET start");
|
||||
var resp = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
|
||||
Debug.Log($"[GSM] HTTP status: {(int)resp.StatusCode} {resp.ReasonPhrase}");
|
||||
|
||||
resp.EnsureSuccessStatusCode(); // 2xx 아니면 예외
|
||||
|
||||
var text = await resp.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
Debug.Log($"[GSM] HTTP OK, length={text?.Length ?? 0}");
|
||||
return text;
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
Debug.LogError("[GSM] 요청이 타임아웃되었습니다. URL이 열리는지 브라우저에서 먼저 확인해보세요.");
|
||||
return null;
|
||||
}
|
||||
catch (HttpRequestException e)
|
||||
{
|
||||
Debug.LogError($"Request error: {e.Message}");
|
||||
Debug.LogError($"[GSM] HTTP 오류: {e.Message}");
|
||||
return null;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError($"[GSM] 예기치 못한 오류: {e}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -311,11 +380,6 @@ private bool SaveFileOrSkip(string path, string contents)
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool IsExistAvailSheets(string sheetName)
|
||||
{
|
||||
return _availSheets.Contains(sheetName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 유효한 구글 웹 앱 URL인지 확인
|
||||
/// </summary>
|
||||
@ -326,112 +390,140 @@ private bool IsValidGoogleSheetUrl(string url)
|
||||
&& url.EndsWith("/exec");
|
||||
}
|
||||
|
||||
private void GenerateClassFilesPerSheet(string jsonInput)
|
||||
private bool GenerateClassFilesPerSheet(string jsonInput)
|
||||
{
|
||||
JObject jsonObject = JObject.Parse(jsonInput);
|
||||
|
||||
Dictionary<string, HashSet<string>> enumCandidates = new();
|
||||
|
||||
foreach (var jObject in jsonObject)
|
||||
try
|
||||
{
|
||||
string className = jObject.Key;
|
||||
if (!IsExistAvailSheets(className)) continue;
|
||||
AssetDatabase.StartAssetEditing();
|
||||
EditorApplication.LockReloadAssemblies();
|
||||
AssetDatabase.DisallowAutoRefresh();
|
||||
bool createdAny = false;
|
||||
var root = JObject.Parse(jsonInput);
|
||||
|
||||
var items = (JArray)jObject.Value;
|
||||
if (items.Count < 2) continue;
|
||||
// 1) Enum 후보 수집 (auto 시트만)
|
||||
var enumCandidates = new Dictionary<string, HashSet<string>>(StringComparer.Ordinal);
|
||||
|
||||
for (int i = 1; i < items.Count; i++)
|
||||
foreach (var pair in root)
|
||||
{
|
||||
foreach (var property in ((JObject)items[i]).Properties())
|
||||
string className = pair.Key;
|
||||
if (!IsAuto(className)) continue;
|
||||
|
||||
var items = pair.Value as JArray;
|
||||
if (items == null || items.Count < 2) continue;
|
||||
|
||||
for (int i = 1; i < items.Count; i++)
|
||||
{
|
||||
string rawName = property.Name;
|
||||
string enumType = null;
|
||||
|
||||
// 🔽 #으로 시작하는 보기용 컬럼은 무시
|
||||
if (rawName.StartsWith("#")) continue;
|
||||
|
||||
// ✅ 단일 필드 Enum: Cookware:Enum
|
||||
if (rawName.Contains(":Enum"))
|
||||
foreach (var prop in ((JObject)items[i]).Properties())
|
||||
{
|
||||
enumType = rawName.Split(':')[0]; // 필드 이름이 곧 Enum 이름
|
||||
}
|
||||
// ✅ 공통 Enum: Taste1:Taste_Enum
|
||||
else if (rawName.Contains(":") && rawName.EndsWith("_Enum"))
|
||||
{
|
||||
enumType = rawName.Split(':')[1].Replace("_Enum", "");
|
||||
}
|
||||
else if (rawName.Contains(":NativeEnum"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
string raw = prop.Name;
|
||||
if (raw.StartsWith("#")) continue;
|
||||
|
||||
if (!string.IsNullOrEmpty(enumType))
|
||||
{
|
||||
string enumValue = NormalizeEnumKey(property.Value.ToString());
|
||||
string enumType = null;
|
||||
if (raw.Contains(":Enum"))
|
||||
{
|
||||
// 단일 필드 Enum: Cookware:Enum -> enumType = "Cookware"
|
||||
enumType = raw.Split(':')[0];
|
||||
}
|
||||
else if (raw.Contains(":") && raw.EndsWith("_Enum"))
|
||||
{
|
||||
// 공통 Enum: Taste1:Taste_Enum -> enumType = "Taste"
|
||||
enumType = raw.Split(':')[1].Replace("_Enum", "");
|
||||
}
|
||||
else if (raw.Contains(":NativeEnum"))
|
||||
{
|
||||
continue; // 네이티브 enum은 자동 생성 대상 아님
|
||||
}
|
||||
|
||||
if (!enumCandidates.ContainsKey(enumType))
|
||||
enumCandidates[enumType] = new();
|
||||
if (string.IsNullOrEmpty(enumType)) continue;
|
||||
|
||||
enumCandidates[enumType].Add(enumValue);
|
||||
string enumValue = NormalizeEnumKey(prop.Value?.ToString() ?? "");
|
||||
if (!enumCandidates.TryGetValue(enumType, out var set))
|
||||
{
|
||||
set = new HashSet<string>(StringComparer.Ordinal);
|
||||
enumCandidates.Add(enumType, set);
|
||||
}
|
||||
|
||||
set.Add(enumValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ EnumTypes.cs 생성
|
||||
StringBuilder enumCode = new();
|
||||
enumCode.AppendLine("// <auto-generated>");
|
||||
enumCode.AppendLine("using System;");
|
||||
enumCode.AppendLine();
|
||||
enumCode.AppendLine($"namespace {_namespace}");
|
||||
enumCode.AppendLine("{");
|
||||
// 2) EnumTypes.cs 생성/갱신 (자동 파일이므로 덮어써도 안전)
|
||||
var enumPath = $"{BaseAssetPath}/EnumTypes.cs";
|
||||
File.WriteAllText(enumPath, BuildEnumCode(enumCandidates));
|
||||
AssetDatabase.ImportAsset(enumPath);
|
||||
|
||||
foreach (var kvp in enumCandidates)
|
||||
{
|
||||
enumCode.AppendLine($" public enum {kvp.Key} \n {{");
|
||||
enumCode.AppendLine(" None = 0,");
|
||||
int index = 1;
|
||||
foreach (string value in kvp.Value)
|
||||
// 3) 클래스/So 생성 (auto만, 파일이 없을 때만)
|
||||
if (!Directory.Exists(ClassedFullPath))
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(value) && value != "None")
|
||||
enumCode.AppendLine($" {value} = {index++},");
|
||||
Directory.CreateDirectory(ClassedFullPath);
|
||||
AssetDatabase.ImportAsset(ClassedFullPath);
|
||||
}
|
||||
|
||||
enumCode.AppendLine(" }\n");
|
||||
foreach (var pair in root)
|
||||
{
|
||||
string className = pair.Key;
|
||||
if (!IsAuto(className)) continue;
|
||||
|
||||
var items = pair.Value as JArray;
|
||||
if (items == null || items.Count < 2) continue;
|
||||
|
||||
string dataPath = $"{ClassedFullPath}/{className}.cs";
|
||||
string soPath = $"{ClassedFullPath}/{className}So.cs";
|
||||
|
||||
if (!File.Exists(dataPath))
|
||||
{
|
||||
File.WriteAllText(dataPath, GenerateDataClassCode(className, items));
|
||||
AssetDatabase.ImportAsset(dataPath);
|
||||
createdAny = true;
|
||||
}
|
||||
|
||||
if (!File.Exists(soPath))
|
||||
{
|
||||
File.WriteAllText(soPath, GenerateSoClassCode(className));
|
||||
AssetDatabase.ImportAsset(soPath);
|
||||
createdAny = true;
|
||||
}
|
||||
}
|
||||
|
||||
return createdAny;
|
||||
}
|
||||
|
||||
enumCode.AppendLine("}");
|
||||
|
||||
File.WriteAllText($"{BaseAssetPath}/EnumTypes.cs", enumCode.ToString());
|
||||
AssetDatabase.ImportAsset($"{BaseAssetPath}/EnumTypes.cs");
|
||||
|
||||
if (!Directory.Exists(ClassedFullPath))
|
||||
finally
|
||||
{
|
||||
Directory.CreateDirectory(ClassedFullPath);
|
||||
AssetDatabase.ImportAsset(ClassedFullPath);
|
||||
AssetDatabase.StopAssetEditing();
|
||||
EditorApplication.UnlockReloadAssemblies();
|
||||
AssetDatabase.AllowAutoRefresh();
|
||||
AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport); // 마지막에 1회만
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ 시트별 클래스/So 생성
|
||||
foreach (var jObject in jsonObject)
|
||||
private string BuildEnumCode(Dictionary<string, HashSet<string>> enums)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("// <auto-generated>");
|
||||
sb.AppendLine("using System;");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine($"namespace {_namespace}");
|
||||
sb.AppendLine("{");
|
||||
|
||||
foreach (var kv in enums)
|
||||
{
|
||||
string className = jObject.Key;
|
||||
if (!IsExistAvailSheets(className)) continue;
|
||||
sb.AppendLine($" public enum {kv.Key}");
|
||||
sb.AppendLine(" {");
|
||||
sb.AppendLine(" None = 0,");
|
||||
int i = 1;
|
||||
foreach (var val in kv.Value)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(val) && val != "None")
|
||||
sb.AppendLine($" {val} = {i++},");
|
||||
}
|
||||
|
||||
var items = (JArray)jObject.Value;
|
||||
if (items.Count < 2) continue;
|
||||
|
||||
string dataCode = GenerateDataClassCode(className, items);
|
||||
string soCode = GenerateSoClassCode(className);
|
||||
|
||||
string dataPath = $"{ClassedFullPath}/{className}.cs";
|
||||
string soPath = $"{ClassedFullPath}/{className}So.cs";
|
||||
|
||||
File.WriteAllText(dataPath, dataCode);
|
||||
File.WriteAllText(soPath, soCode);
|
||||
|
||||
AssetDatabase.ImportAsset(dataPath);
|
||||
AssetDatabase.ImportAsset(soPath);
|
||||
sb.AppendLine(" }");
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
sb.AppendLine("}");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private string GenerateSoClassCode(string className)
|
||||
@ -563,7 +655,7 @@ private async Task<bool> CreateGoogleSheetSoAsync()
|
||||
// 🔁 카탈로그 업데이트 제거 (로컬 모드에서는 불필요)
|
||||
Debug.Log("[GoogleSheetManager] 로컬 모드 - 카탈로그 업데이트 스킵");
|
||||
|
||||
result = await InternalCreateGoogleSheetSoAsync();
|
||||
result = InternalCreateGoogleSheetSoAsync();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@ -574,201 +666,191 @@ private async Task<bool> CreateGoogleSheetSoAsync()
|
||||
_isCreatingSo = false;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (result) // 성공적으로 SO 생성된 경우에만 빌드 수행
|
||||
{
|
||||
Debug.Log("[GoogleSheetManager] Addressables BuildPlayerContent 실행");
|
||||
UnityEditor.AddressableAssets.Settings.AddressableAssetSettings.BuildPlayerContent();
|
||||
}
|
||||
#endif
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task<bool> InternalCreateGoogleSheetSoAsync()
|
||||
private static IEnumerable<Type> GetAllTypesSafe()
|
||||
{
|
||||
JObject jsonObject = JObject.Parse(_json);
|
||||
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
Type[] types;
|
||||
try
|
||||
{
|
||||
types = asm.GetTypes();
|
||||
}
|
||||
catch (ReflectionTypeLoadException ex)
|
||||
{
|
||||
types = ex.Types.Where(t => t != null).ToArray();
|
||||
}
|
||||
|
||||
foreach (var t in types)
|
||||
if (t != null)
|
||||
yield return t;
|
||||
}
|
||||
}
|
||||
|
||||
private bool InternalCreateGoogleSheetSoAsync()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_json))
|
||||
{
|
||||
Debug.LogError("[GoogleSheetManager] JSON 데이터가 비어있어 SO 생성을 중단합니다.");
|
||||
Debug.LogError("[GoogleSheetManager] JSON 비어있음");
|
||||
return false;
|
||||
}
|
||||
|
||||
var root = JObject.Parse(_json);
|
||||
bool allSuccess = true;
|
||||
|
||||
foreach (var sheetPair in jsonObject)
|
||||
{
|
||||
string sheetName = sheetPair.Key;
|
||||
if (!IsExistAvailSheets(sheetName))
|
||||
continue;
|
||||
// 타입 캐시
|
||||
var typeCache = GetAllTypesSafe()
|
||||
.Where(t =>
|
||||
t.IsClass &&
|
||||
t.Namespace == _namespace &&
|
||||
!t.Name.StartsWith("<") && // <>c 같은 컴파일러 생성 타입 제외
|
||||
!Attribute.IsDefined(t, typeof(CompilerGeneratedAttribute))
|
||||
)
|
||||
.GroupBy(t => t.Name) // 같은 이름 여러 개면 첫 것만
|
||||
.ToDictionary(g => g.Key, g => g.First());
|
||||
|
||||
Type dataType = FindTypeByName(sheetName);
|
||||
Type soType = FindTypeByName($"{sheetName}So");
|
||||
string soDir = $"Assets{_generateFolderPath}/So";
|
||||
if (!Directory.Exists(soDir))
|
||||
{
|
||||
Directory.CreateDirectory(soDir);
|
||||
AssetDatabase.ImportAsset(soDir);
|
||||
}
|
||||
|
||||
foreach (var pair in root)
|
||||
{
|
||||
string sheet = pair.Key;
|
||||
|
||||
if (!IsSelected(sheet))
|
||||
{
|
||||
Debug.Log($"[GSM] Skip (not selected): {sheet}");
|
||||
continue;
|
||||
}
|
||||
|
||||
typeCache.TryGetValue(sheet, out var dataType);
|
||||
typeCache.TryGetValue($"{sheet}So", out var soType);
|
||||
|
||||
if (dataType == null || soType == null)
|
||||
{
|
||||
Debug.LogError($"[GoogleSheetManager] 타입을 찾을 수 없습니다: {sheetName} 또는 {sheetName}So");
|
||||
Debug.LogWarning($"[GSM] Type missing for '{sheet}'. ns='{_namespace}' " +
|
||||
$"dataType={(dataType == null ? "null" : dataType.FullName)} " +
|
||||
$"soType={(soType == null ? "null" : soType.FullName)}");
|
||||
allSuccess = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
string soDirectory = $"Assets{_generateFolderPath}/So";
|
||||
if (!Directory.Exists(soDirectory))
|
||||
string soPath = $"{soDir}/{sheet}So.asset";
|
||||
var so = AssetDatabase.LoadAssetAtPath<ScriptableObject>(soPath);
|
||||
|
||||
if (so == null)
|
||||
{
|
||||
Directory.CreateDirectory(soDirectory);
|
||||
AssetDatabase.ImportAsset(soDirectory);
|
||||
}
|
||||
|
||||
string soPath = $"{soDirectory}/{sheetName}So.asset";
|
||||
ScriptableObject soInstance = AssetDatabase.LoadAssetAtPath<ScriptableObject>(soPath);
|
||||
|
||||
if (soInstance == null)
|
||||
{
|
||||
if (File.Exists(soPath))
|
||||
{
|
||||
Debug.LogWarning($"[GoogleSheetManager] 잘못된 SO 파일 제거: {soPath}");
|
||||
AssetDatabase.DeleteAsset(soPath);
|
||||
}
|
||||
|
||||
soInstance = ScriptableObject.CreateInstance(soType);
|
||||
AssetDatabase.CreateAsset(soInstance, soPath);
|
||||
Debug.Log($"[GSM] Creating SO asset: {soPath}");
|
||||
so = ScriptableObject.CreateInstance(soType);
|
||||
AssetDatabase.CreateAsset(so, soPath);
|
||||
AssetDatabase.SaveAssets();
|
||||
AssetDatabase.ImportAsset(soPath, ImportAssetOptions.ForceSynchronousImport);
|
||||
await Task.Delay(100);
|
||||
AssetDatabase.Refresh();
|
||||
|
||||
// 존재 확인
|
||||
var check = AssetDatabase.LoadAssetAtPath<ScriptableObject>(soPath);
|
||||
Debug.Log(check ? $"[GSM] SO created OK: {soPath}" : $"[GSM] SO create FAILED: {soPath}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log($"[GSM] SO asset exists: {soPath}");
|
||||
}
|
||||
|
||||
IList list = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(dataType));
|
||||
var dataArray = (JArray)sheetPair.Value;
|
||||
// 데이터 파싱 → 리스트
|
||||
var list = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(dataType));
|
||||
var rows = (JArray)pair.Value;
|
||||
|
||||
for (int i = 1; i < dataArray.Count; i++)
|
||||
for (int i = 1; i < rows.Count; i++)
|
||||
{
|
||||
JObject item = (JObject)dataArray[i];
|
||||
object dataInstance = Activator.CreateInstance(dataType);
|
||||
var row = (JObject)rows[i];
|
||||
var inst = Activator.CreateInstance(dataType);
|
||||
|
||||
foreach (var prop in item.Properties())
|
||||
foreach (var prop in row.Properties())
|
||||
{
|
||||
string rawName = prop.Name;
|
||||
string fieldName = rawName;
|
||||
string explicitType = null;
|
||||
|
||||
// 🔽 보기용 컬럼 무시
|
||||
if (rawName.StartsWith("#")) continue;
|
||||
var raw = prop.Name;
|
||||
if (raw.StartsWith("#")) continue;
|
||||
|
||||
if (rawName.Contains(":Enum"))
|
||||
{
|
||||
fieldName = rawName.Split(':')[0];
|
||||
explicitType = fieldName;
|
||||
}
|
||||
else if (rawName.Contains(":") && rawName.EndsWith("_Enum"))
|
||||
{
|
||||
var parts = rawName.Split(':');
|
||||
fieldName = parts[0];
|
||||
explicitType = parts[1].Replace("_Enum", "");
|
||||
}
|
||||
else if (rawName.Contains(":NativeEnum"))
|
||||
{
|
||||
fieldName = rawName.Split(':')[0];
|
||||
explicitType = fieldName;
|
||||
}
|
||||
else if (rawName.Contains(":"))
|
||||
{
|
||||
var parts = rawName.Split(':');
|
||||
fieldName = parts[0];
|
||||
explicitType = parts[1];
|
||||
}
|
||||
string field = raw.Contains(":") ? raw.Split(':')[0] : raw;
|
||||
|
||||
FieldInfo field = dataType.GetField(fieldName,
|
||||
var f = dataType.GetField(field,
|
||||
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
PropertyInfo property = dataType.GetProperty(fieldName,
|
||||
var p = dataType.GetProperty(field,
|
||||
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
|
||||
if (field == null && property == null)
|
||||
{
|
||||
Debug.LogWarning($"[GoogleSheetManager] 필드/프로퍼티 누락: {dataType.Name}.{fieldName}");
|
||||
continue;
|
||||
}
|
||||
if (f == null && p == null) continue;
|
||||
|
||||
try
|
||||
{
|
||||
var target = f?.FieldType ?? p?.PropertyType;
|
||||
object value;
|
||||
|
||||
if ((field?.FieldType.IsEnum ?? false) || (property?.PropertyType.IsEnum ?? false))
|
||||
if (target.IsEnum)
|
||||
{
|
||||
Type enumType = field?.FieldType ?? property?.PropertyType;
|
||||
string formatted = NormalizeEnumKey(prop.Value.ToString());
|
||||
value = Enum.TryParse(enumType, formatted, out var parsed)
|
||||
var k = NormalizeEnumKey(prop.Value.ToString());
|
||||
value = Enum.TryParse(target, k, out var parsed)
|
||||
? parsed
|
||||
: Activator.CreateInstance(enumType);
|
||||
: Activator.CreateInstance(target);
|
||||
}
|
||||
else if ((field?.FieldType == typeof(Color)) || (property?.PropertyType == typeof(Color)))
|
||||
else if (target == typeof(Color))
|
||||
{
|
||||
value = ColorUtility.TryParseHtmlString(prop.Value.ToString(), out var color)
|
||||
? color
|
||||
: Color.white;
|
||||
value = ColorUtility.TryParseHtmlString(prop.Value.ToString(), out var c) ? c : Color.white;
|
||||
}
|
||||
else if ((field?.FieldType == typeof(string)) || (property?.PropertyType == typeof(string)))
|
||||
else if (target == typeof(string))
|
||||
{
|
||||
value = prop.Value.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
Type targetType = field?.FieldType ?? property?.PropertyType;
|
||||
value = Convert.ChangeType(prop.Value.ToString(), targetType);
|
||||
value = Convert.ChangeType(prop.Value.ToString(), target);
|
||||
}
|
||||
|
||||
if (field != null)
|
||||
field.SetValue(dataInstance, value);
|
||||
else if (property != null && property.CanWrite)
|
||||
property.SetValue(dataInstance, value);
|
||||
if (f != null) f.SetValue(inst, value);
|
||||
else if (p is { CanWrite: true }) p.SetValue(inst, value);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogWarning($"[GoogleSheetManager] 값 할당 실패: {fieldName} = {prop.Value} → {e.Message}");
|
||||
Debug.LogWarning($"[{sheet}] 값 할당 실패 {raw} → {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
list.Add(dataInstance);
|
||||
list.Add(inst);
|
||||
}
|
||||
|
||||
MethodInfo setMethod = soType.GetMethod("SetDataList",
|
||||
// SetDataList 호출
|
||||
var setMethod = soType.GetMethod("SetDataList",
|
||||
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
if (setMethod != null)
|
||||
if (setMethod == null)
|
||||
{
|
||||
setMethod.Invoke(soInstance, new object[] { list });
|
||||
EditorUtility.SetDirty(soInstance);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError($"[GoogleSheetManager] {soType.Name}에 SetDataList 메서드가 없습니다.");
|
||||
Debug.LogError($"{soType.Name}에 SetDataList가 없습니다.");
|
||||
allSuccess = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
setMethod.Invoke(so, new object[] { list });
|
||||
EditorUtility.SetDirty(so);
|
||||
|
||||
AssetDatabase.SaveAssets();
|
||||
AssetDatabase.Refresh();
|
||||
|
||||
if (AssetDatabase.LoadAssetAtPath<ScriptableObject>(soPath) != null)
|
||||
{
|
||||
// Addressables 자동 등록은 auto만 (팀별 환경오염 방지)
|
||||
if (IsAuto(sheet))
|
||||
GoogleSheetAddressableAutoSetup.AutoRegisterSo(soPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"[GoogleSheetManager] Addressables 등록 생략: {soPath} 불완전");
|
||||
}
|
||||
}
|
||||
|
||||
Debug.Log("✅ [CreateGoogleSheetSoAsync] 시트별 ScriptableObject 생성 및 반영 완료");
|
||||
Debug.Log("✅ SO 동기화 완료(선택 시트만, 변경 없어도 항상 반영)");
|
||||
return allSuccess;
|
||||
}
|
||||
|
||||
private Type FindTypeByName(string sheetName)
|
||||
{
|
||||
return AppDomain.CurrentDomain.GetAssemblies()
|
||||
.SelectMany(a => a.GetTypes())
|
||||
.FirstOrDefault(t => t.Namespace == "DDD" && t.Name == sheetName);
|
||||
}
|
||||
|
||||
private string NormalizeEnumKey(string input)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input))
|
||||
@ -784,13 +866,7 @@ private string NormalizeEnumKey(string input)
|
||||
// 첫 글자 대문자화
|
||||
return char.ToUpper(validName[0]) + validName.Substring(1);
|
||||
}
|
||||
|
||||
private string NormalizeSpriteKey(string name)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name)) return "";
|
||||
return name.Replace("(Clone)", "").Trim();
|
||||
}
|
||||
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
if (_refreshTrigger && !_alreadyCreatedSo && EditorPrefs.GetBool("GoogleSheetManager_ShouldCreateSO"))
|
||||
@ -826,5 +902,5 @@ public async Task CreateSoAfterScriptReload()
|
||||
await CreateGoogleSheetSoAsync();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#endif
|
Loading…
Reference in New Issue
Block a user