구글 시트 로직 수정

This commit is contained in:
NTG_Lenovo 2025-08-08 19:44:34 +09:00
parent ddb8e499c3
commit 8940e8654b

View File

@ -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;
var raw = prop.Name;
if (raw.StartsWith("#")) continue;
// 🔽 보기용 컬럼 무시
if (rawName.StartsWith("#")) continue;
string field = raw.Contains(":") ? raw.Split(':')[0] : raw;
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];
}
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))
@ -785,12 +867,6 @@ 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