diff --git a/Assets/_DDD/_Scripts/GenerateGoogleSheet/Core/GoogleSheetManager.cs b/Assets/_DDD/_Scripts/GenerateGoogleSheet/Core/GoogleSheetManager.cs index 68ce067af..cdfc514a0 100644 --- a/Assets/_DDD/_Scripts/GenerateGoogleSheet/Core/GoogleSheetManager.cs +++ b/Assets/_DDD/_Scripts/GenerateGoogleSheet/Core/GoogleSheetManager.cs @@ -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 private string _namespace = "DDD"; [BoxGroup("기본 설정")] - [SerializeField, Tooltip("적용시킬 시트의 이름들")] - private List _availSheets = new(){ "Sheet1", "Sheet2" }; + [SerializeField, Tooltip("코드/Enum/So 자동 생성 + 데이터 반영 시트")] + private List _autoCreateSheets = new(); + + [BoxGroup("기본 설정")] + [SerializeField, Tooltip("기존 Data/So 유지, SO 데이터만 동기화 시트")] + private List _soSyncSheets = new(); [BoxGroup("기본 설정")] [SerializeField, Tooltip("Class, Json, So 생성 위치 \"/GenerateGoogleSheet\"")] @@ -41,11 +47,9 @@ public class GoogleSheetManager : Singleton [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 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(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> GetVersionOptions() /// private async Task 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); - } - /// /// 유효한 구글 웹 앱 URL인지 확인 /// @@ -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> 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>(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(StringComparer.Ordinal); + enumCandidates.Add(enumType, set); + } + + set.Add(enumValue); } } } - } - // ✅ EnumTypes.cs 생성 - StringBuilder enumCode = new(); - enumCode.AppendLine("// "); - 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> enums) + { + var sb = new StringBuilder(); + sb.AppendLine("// "); + 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 CreateGoogleSheetSoAsync() // 🔁 카탈로그 업데이트 제거 (로컬 모드에서는 불필요) Debug.Log("[GoogleSheetManager] 로컬 모드 - 카탈로그 업데이트 스킵"); - result = await InternalCreateGoogleSheetSoAsync(); + result = InternalCreateGoogleSheetSoAsync(); } catch (Exception e) { @@ -574,201 +666,191 @@ private async Task 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 InternalCreateGoogleSheetSoAsync() + private static IEnumerable 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(soPath); + + if (so == null) { - Directory.CreateDirectory(soDirectory); - AssetDatabase.ImportAsset(soDirectory); - } - - string soPath = $"{soDirectory}/{sheetName}So.asset"; - ScriptableObject soInstance = AssetDatabase.LoadAssetAtPath(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(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(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 -} \ No newline at end of file +} +#endif \ No newline at end of file