구글 시트 매니저 로직 수정

This commit is contained in:
NTG_Lenovo 2025-07-15 12:59:53 +09:00
parent c04291fdd4
commit df3341158a
2 changed files with 94 additions and 67 deletions

View File

@ -1,7 +1,9 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
#if UNITY_EDITOR
using UnityEditor.AddressableAssets; using UnityEditor.AddressableAssets;
#endif
using UnityEngine; using UnityEngine;
using UnityEngine.AddressableAssets; using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations; using UnityEngine.ResourceManagement.AsyncOperations;

View File

@ -89,8 +89,6 @@ public static async Task<T> LoadSo<T>() where T : ScriptableObject
[Button("데이터 최신화"), EnableIf(nameof(CanFetchData))] [Button("데이터 최신화"), EnableIf(nameof(CanFetchData))]
private async Task FetchGoogleSheet() private async Task FetchGoogleSheet()
{ {
_availSheetArray = _availSheets.Split('/'); _availSheetArray = _availSheets.Split('/');
var prevLog = AssetDatabase.LoadAssetAtPath<GoogleSheetChangeLog>(ChangeLogAssetPath); var prevLog = AssetDatabase.LoadAssetAtPath<GoogleSheetChangeLog>(ChangeLogAssetPath);
@ -366,14 +364,17 @@ private void GenerateClassFilesPerSheet(string jsonInput)
foreach (var property in ((JObject)items[i]).Properties()) foreach (var property in ((JObject)items[i]).Properties())
{ {
string rawName = property.Name; string rawName = property.Name;
// ✅ Enum 타입 여부 판단
string enumType = null; string enumType = null;
if (rawName.EndsWith("_Enum"))
// ✅ 단일 필드 Enum: Cookware:Enum
if (rawName.Contains(":Enum"))
{ {
enumType = rawName.Contains(":") enumType = rawName.Split(':')[0]; // 필드 이름이 곧 Enum 이름
? rawName.Split(':')[1].Replace("_Enum", "") }
: rawName.Replace("_Enum", ""); // ✅ 공통 Enum: Taste1:Taste_Enum
else if (rawName.Contains(":") && rawName.EndsWith("_Enum"))
{
enumType = rawName.Split(':')[1].Replace("_Enum", "");
} }
if (!string.IsNullOrEmpty(enumType)) if (!string.IsNullOrEmpty(enumType))
@ -389,10 +390,10 @@ private void GenerateClassFilesPerSheet(string jsonInput)
} }
} }
// EnumTypes.cs 생성 // EnumTypes.cs 생성
StringBuilder enumCode = new(); StringBuilder enumCode = new();
enumCode.AppendLine("// <auto-generated>"); enumCode.AppendLine("// <auto-generated>");
enumCode.AppendLine("using System;\n"); enumCode.AppendLine("using System;");
enumCode.AppendLine(); enumCode.AppendLine();
enumCode.AppendLine($"namespace {_namespace}"); enumCode.AppendLine($"namespace {_namespace}");
enumCode.AppendLine("{"); enumCode.AppendLine("{");
@ -410,6 +411,7 @@ private void GenerateClassFilesPerSheet(string jsonInput)
enumCode.AppendLine(" }\n"); enumCode.AppendLine(" }\n");
} }
enumCode.AppendLine("}"); enumCode.AppendLine("}");
File.WriteAllText($"{BaseAssetPath}/EnumTypes.cs", enumCode.ToString()); File.WriteAllText($"{BaseAssetPath}/EnumTypes.cs", enumCode.ToString());
@ -421,7 +423,7 @@ private void GenerateClassFilesPerSheet(string jsonInput)
AssetDatabase.ImportAsset(ClassedFullPath); AssetDatabase.ImportAsset(ClassedFullPath);
} }
// 시트별 클래스 생성 // 시트별 클래스/So 생성
foreach (var jObject in jsonObject) foreach (var jObject in jsonObject)
{ {
string className = jObject.Key; string className = jObject.Key;
@ -451,10 +453,10 @@ private string GenerateSoClassCode(string className)
"using System.Collections.Generic;\n" + "using System.Collections.Generic;\n" +
"using UnityEngine;\n\n" + "using UnityEngine;\n\n" +
$"namespace {_namespace}\n" + $"namespace {_namespace}\n" +
"{\n" + $"{{\n" +
$" [CreateAssetMenu(fileName = \"{className}So\", menuName = \"GoogleSheet/{className}So\")]\n" + $" [CreateAssetMenu(fileName = \"{className}So\", menuName = \"GoogleSheet/{className}So\")]\n" +
$" public class {className}So : ScriptableObject \n" + $" public class {className}So : DataSo<{className}> {{ }}\n" +
$" {{\n public List<{className}> {className}List;\n }}\n}}\n"; $"}}";
} }
private string GenerateDataClassCode(string className, JArray items) private string GenerateDataClassCode(string className, JArray items)
@ -470,7 +472,7 @@ private string GenerateDataClassCode(string className, JArray items)
sb.AppendLine($"namespace {_namespace}"); sb.AppendLine($"namespace {_namespace}");
sb.AppendLine("{"); sb.AppendLine("{");
sb.AppendLine(" [Serializable]"); sb.AppendLine(" [Serializable]");
sb.AppendLine($" public class {className}"); sb.AppendLine($" public class {className} : IId");
sb.AppendLine(" {"); sb.AppendLine(" {");
int count = sampleRow.Properties().Count(); int count = sampleRow.Properties().Count();
@ -487,22 +489,27 @@ private string GenerateDataClassCode(string className, JArray items)
string fieldName = rawName; string fieldName = rawName;
string explicitType = null; string explicitType = null;
if (rawName.Contains(":")) if (rawName.Contains(":Enum"))
{
fieldName = rawName.Split(':')[0];
explicitType = fieldName;
}
else if (rawName.Contains(":") && rawName.EndsWith("_Enum"))
{ {
var parts = rawName.Split(':'); var parts = rawName.Split(':');
fieldName = parts[0]; fieldName = parts[0];
explicitType = parts[1].Replace("_Enum", ""); explicitType = parts[1].Replace("_Enum", "");
} }
else if (rawName.EndsWith("_Enum")) else if (rawName.Contains(":"))
{ {
fieldName = rawName.Replace("_Enum", ""); var parts = rawName.Split(':');
explicitType = fieldName; fieldName = parts[0];
explicitType = parts[1];
} }
types[i] = explicitType ?? GetCSharpType(prop.Value.Type); types[i] = explicitType ?? GetCSharpType(prop.Value.Type);
names[i] = fieldName; names[i] = fieldName;
tooltips[i] ??= commentRow.TryGetValue(rawName, out var tip) ? tip.ToString() : ""; tooltips[i] ??= commentRow.TryGetValue(rawName, out var tip) ? tip.ToString() : "";
i++; i++;
} }
} }
@ -515,11 +522,20 @@ private string GenerateDataClassCode(string className, JArray items)
sb.AppendLine($" [Tooltip(\"{tooltips[i]}\")]"); sb.AppendLine($" [Tooltip(\"{tooltips[i]}\")]");
} }
if (names[i] == "Id" && types[i] == "string")
{
sb.AppendLine(" [field: SerializeField]");
sb.AppendLine(" public string Id { get; set; }\n");
}
else
{
sb.AppendLine($" public {types[i]} {names[i]};\n"); sb.AppendLine($" public {types[i]} {names[i]};\n");
} }
}
sb.AppendLine(" }"); sb.AppendLine(" }");
sb.AppendLine("}"); sb.AppendLine("}");
return sb.ToString(); return sb.ToString();
} }
@ -616,7 +632,6 @@ private async Task<bool> InternalCreateGoogleSheetSoAsync()
string soPath = $"{soDirectory}/{sheetName}So.asset"; string soPath = $"{soDirectory}/{sheetName}So.asset";
ScriptableObject soInstance = AssetDatabase.LoadAssetAtPath<ScriptableObject>(soPath); ScriptableObject soInstance = AssetDatabase.LoadAssetAtPath<ScriptableObject>(soPath);
// 💡 잘못된 asset이면 삭제 후 새로 생성
if (soInstance == null) if (soInstance == null)
{ {
if (File.Exists(soPath)) if (File.Exists(soPath))
@ -629,11 +644,10 @@ private async Task<bool> InternalCreateGoogleSheetSoAsync()
AssetDatabase.CreateAsset(soInstance, soPath); AssetDatabase.CreateAsset(soInstance, soPath);
AssetDatabase.SaveAssets(); AssetDatabase.SaveAssets();
AssetDatabase.ImportAsset(soPath, ImportAssetOptions.ForceSynchronousImport); AssetDatabase.ImportAsset(soPath, ImportAssetOptions.ForceSynchronousImport);
await Task.Delay(100); // meta 확정 시간 확보 await Task.Delay(100);
AssetDatabase.Refresh(); AssetDatabase.Refresh();
} }
// 🧠 Sprite나 Enum 값을 반영한 List<T> 생성
IList list = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(dataType)); IList list = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(dataType));
var dataArray = (JArray)sheetPair.Value; var dataArray = (JArray)sheetPair.Value;
@ -648,24 +662,33 @@ private async Task<bool> InternalCreateGoogleSheetSoAsync()
string fieldName = rawName; string fieldName = rawName;
string explicitType = null; string explicitType = null;
if (rawName.Contains(":")) if (rawName.Contains(":Enum"))
{ {
var split = rawName.Split(':'); fieldName = rawName.Split(':')[0];
fieldName = split[0];
explicitType = split[1].Replace("_Enum", "");
}
else if (rawName.EndsWith("_Enum"))
{
fieldName = rawName.Replace("_Enum", "");
explicitType = fieldName; 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(":"))
{
var parts = rawName.Split(':');
fieldName = parts[0];
explicitType = parts[1];
}
FieldInfo field = dataType.GetField(fieldName, FieldInfo field = dataType.GetField(fieldName,
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (field == null) PropertyInfo property = dataType.GetProperty(fieldName,
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (field == null && property == null)
{ {
Debug.LogWarning($"[GoogleSheetManager] 필드 누락: {dataType.Name}.{fieldName}"); Debug.LogWarning($"[GoogleSheetManager] 필드/프로퍼티 누락: {dataType.Name}.{fieldName}");
continue; continue;
} }
@ -673,12 +696,12 @@ private async Task<bool> InternalCreateGoogleSheetSoAsync()
{ {
object value; object value;
// ✅ Sprite 처리 (주소로부터 비동기 로드) // Sprite, Color 등 기존 로직 유지
if (explicitType == "Sprite" && field.FieldType == typeof(Sprite)) if (explicitType == "Sprite" && (field?.FieldType == typeof(Sprite) ||
property?.PropertyType == typeof(Sprite)))
{ {
string spriteKey = prop.Value.ToString().Trim(); string spriteKey = prop.Value.ToString().Trim();
if (string.IsNullOrEmpty(spriteKey)) if (string.IsNullOrEmpty(spriteKey)) continue;
continue;
if (!AssetManager.HasLabel(spriteKey, "Sprite")) if (!AssetManager.HasLabel(spriteKey, "Sprite"))
{ {
@ -696,68 +719,62 @@ private async Task<bool> InternalCreateGoogleSheetSoAsync()
} }
value = handle.Result; value = handle.Result;
field.SetValue(dataInstance, value);
continue; // 이후 변환 스킵
} }
else if ((field?.FieldType.IsEnum ?? false) || (property?.PropertyType.IsEnum ?? false))
// ✅ Enum 처리
if (field.FieldType.IsEnum)
{ {
Type enumType = field?.FieldType ?? property?.PropertyType;
string formatted = NormalizeEnumKey(prop.Value.ToString()); string formatted = NormalizeEnumKey(prop.Value.ToString());
value = Enum.TryParse(field.FieldType, formatted, out var parsed) value = Enum.TryParse(enumType, formatted, out var parsed)
? parsed ? parsed
: Activator.CreateInstance(field.FieldType); : Activator.CreateInstance(enumType);
} }
// ✅ Color 처리 else if ((field?.FieldType == typeof(Color)) || (property?.PropertyType == typeof(Color)))
else if (field.FieldType == typeof(Color))
{ {
value = ColorUtility.TryParseHtmlString(prop.Value.ToString(), out var color) value = ColorUtility.TryParseHtmlString(prop.Value.ToString(), out var color)
? color ? color
: Color.white; : Color.white;
} }
// ✅ 기본 타입 처리 else if ((field?.FieldType == typeof(string)) || (property?.PropertyType == typeof(string)))
{
value = prop.Value.ToString();
}
else else
{ {
value = Convert.ChangeType(prop.Value.ToString(), field.FieldType); Type targetType = field?.FieldType ?? property?.PropertyType;
value = Convert.ChangeType(prop.Value.ToString(), targetType);
} }
if (field != null)
field.SetValue(dataInstance, value); field.SetValue(dataInstance, value);
else if (property != null && property.CanWrite)
property.SetValue(dataInstance, value);
} }
catch (Exception e) catch (Exception e)
{ {
Debug.LogWarning( Debug.LogWarning($"[GoogleSheetManager] 값 할당 실패: {fieldName} = {prop.Value} → {e.Message}");
$"[GoogleSheetManager] 값 할당 실패: {fieldName} = {prop.Value} ({field.FieldType}) → {e.Message}");
} }
} }
list.Add(dataInstance); list.Add(dataInstance);
} }
// ✅ 리스트 필드에 값 설정 // ✅ SetDataList() 호출
FieldInfo listField = soType.GetField($"{sheetName}List", MethodInfo setMethod = soType.GetMethod("SetDataList",
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (setMethod != null)
if (listField != null)
{ {
object existingListObj = listField.GetValue(soInstance); setMethod.Invoke(soInstance, new object[] { list });
if (existingListObj is IList existingList)
{
existingList.Clear(); // ✅ 기존 리스트를 완전히 비움
}
listField.SetValue(soInstance, list);
EditorUtility.SetDirty(soInstance); EditorUtility.SetDirty(soInstance);
} }
else else
{ {
Debug.LogError($"[GoogleSheetManager] {soType.Name}에 {sheetName}List 필드가 없습니다."); Debug.LogError($"[GoogleSheetManager] {soType.Name}에 SetDataList 메서드가 없습니다.");
allSuccess = false; allSuccess = false;
} }
AssetDatabase.SaveAssets(); AssetDatabase.SaveAssets();
AssetDatabase.Refresh(); AssetDatabase.Refresh();
// ✅ Addressables 등록은 마지막에, 유효한 경우만
if (AssetDatabase.LoadAssetAtPath<ScriptableObject>(soPath) != null) if (AssetDatabase.LoadAssetAtPath<ScriptableObject>(soPath) != null)
{ {
GoogleSheetAddressableAutoSetup.AutoRegisterSo(soPath); GoogleSheetAddressableAutoSetup.AutoRegisterSo(soPath);
@ -781,9 +798,17 @@ private Type FindTypeByName(string sheetName)
private string NormalizeEnumKey(string input) private string NormalizeEnumKey(string input)
{ {
string validName = System.Text.RegularExpressions.Regex.Replace(input, @"[^a-zA-Z0-9_]", "_"); if (string.IsNullOrEmpty(input))
return "None";
// 특수문자 및 공백을 밑줄(_)로 치환
string validName = System.Text.RegularExpressions.Regex.Replace(input, @"[^a-zA-Z0-9_]+", "_");
// 숫자로 시작하는 경우 밑줄 추가
if (char.IsDigit(validName[0])) if (char.IsDigit(validName[0]))
validName = "_" + validName; validName = "_" + validName;
// 첫 글자 대문자화
return char.ToUpper(validName[0]) + validName.Substring(1); return char.ToUpper(validName[0]) + validName.Substring(1);
} }