696 lines
23 KiB (Stored with Git LFS)
C#
696 lines
23 KiB (Stored with Git LFS)
C#
using System;
|
|
using System.IO;
|
|
using System.Net.Http;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using System.Reflection;
|
|
using System.Collections.Generic;
|
|
using System.Collections;
|
|
using UnityEngine;
|
|
using Newtonsoft.Json.Linq;
|
|
using System.Linq;
|
|
using Sirenix.OdinInspector;
|
|
using UnityEditor;
|
|
using UnityEngine.AddressableAssets;
|
|
using UnityEngine.ResourceManagement.AsyncOperations;
|
|
|
|
public class GoogleSheetManager : Singleton<GoogleSheetManager>
|
|
{
|
|
[BoxGroup("기본 설정")] [Tooltip("true: google sheet, false: local json")] [SerializeField]
|
|
private bool _isAccessGoogleSheet = true;
|
|
|
|
[BoxGroup("기본 설정")]
|
|
[Tooltip("구글 시트 -> 확장 프로그램 -> Apps Script -> 새 배포(웹 앱) or 배포 관리 -> 웹 앱 URL(~~~/exec)")]
|
|
[SerializeField]
|
|
private string _googleSheetUrl;
|
|
|
|
[BoxGroup("기본 설정")]
|
|
[Tooltip("적용시킬 시트의 이름을 적고, 여러 개의 시트를 적는 경우 '/'로 구분지어 시트 나열\nex) \"Sheet1/Sheet2\"")]
|
|
[SerializeField]
|
|
private string _availSheets = "Sheet1/Sheet2";
|
|
|
|
[BoxGroup("기본 설정")] [Tooltip("Class, Json, So 생성 위치 \"/GenerateGoogleSheet\"")] [SerializeField]
|
|
private string _generateFolderPath = "/0.Datas/02.Scripts/GenerateGoogleSheet/AutoCreated";
|
|
|
|
[Title("버전 복구")] [BoxGroup("버전 복구")] [Tooltip("마지막 업데이트 시간")] [SerializeField, ReadOnly]
|
|
private string _lastUpdated;
|
|
|
|
#if UNITY_EDITOR
|
|
[BoxGroup("버전 복구")] [SerializeField, ValueDropdown(nameof(GetVersionOptions))]
|
|
private int _restoreIndex;
|
|
#endif
|
|
|
|
[Title("데이터 변경")]
|
|
[BoxGroup("데이터 변경")]
|
|
[LabelText("수정자 이름")]
|
|
[SerializeField, Required("반드시 수정자 이름을 입력해야 합니다\n이력을 남길 때 표시될 사용자 이름입니다.")]
|
|
private string _editorName;
|
|
|
|
private string JsonPath => $"{Application.dataPath}{_generateFolderPath}/GoogleSheetJson.json";
|
|
|
|
private const string ChangeLogPath = "Assets/0.Datas/02.Scripts/GenerateGoogleSheet/Logs/GoogleSheetChangeLog.asset";
|
|
|
|
private string[] _availSheetArray;
|
|
private string _json;
|
|
|
|
[SerializeField, ReadOnly]
|
|
private bool _refreshTrigger;
|
|
private bool _alreadyCreatedSo;
|
|
|
|
public static async Task<T> LoadSo<T>() where T : ScriptableObject
|
|
{
|
|
await Addressables.InitializeAsync().Task;
|
|
|
|
string key = typeof(T).Name;
|
|
var handle = Addressables.LoadAssetAsync<T>(key);
|
|
await handle.Task;
|
|
|
|
if (handle.Status == AsyncOperationStatus.Succeeded)
|
|
return handle.Result;
|
|
|
|
Debug.LogError($"[GoogleSheetManager] Addressable 로드 실패: {key}");
|
|
return null;
|
|
}
|
|
|
|
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
|
[BoxGroup("데이터 변경")]
|
|
[Button("데이터 최신화"), EnableIf(nameof(CanFetchData))]
|
|
private async void FetchGoogleSheet()
|
|
{
|
|
_availSheetArray = _availSheets.Split('/');
|
|
|
|
var prevLog = AssetDatabase.LoadAssetAtPath<GoogleSheetChangeLog>(ChangeLogPath);
|
|
string previousJson = prevLog?.Logs.LastOrDefault()?.JsonSnapshot ?? "";
|
|
|
|
if (_isAccessGoogleSheet)
|
|
{
|
|
if (!IsValidGoogleSheetUrl(_googleSheetUrl))
|
|
{
|
|
Debug.LogError("Google Sheet URL이 유효하지 않습니다.");
|
|
return;
|
|
}
|
|
|
|
Debug.Log("구글 시트 데이터 읽는 중...");
|
|
_json = await LoadDataGoogleSheet(_googleSheetUrl);
|
|
}
|
|
else
|
|
{
|
|
Debug.Log("Local Json 파일 읽는 중...");
|
|
_json = LoadDataLocalJson();
|
|
}
|
|
|
|
if (_json == null)
|
|
{
|
|
Debug.Log("Json is null. 최신화 실패");
|
|
return;
|
|
}
|
|
|
|
var diffs = GoogleSheetFetchHelper.CompareJsonDiff(previousJson, _json);
|
|
if (diffs.Count > 0)
|
|
GoogleSheetDiffViewer.ShowWindow(diffs);
|
|
|
|
bool isJsonSaved = SaveFileOrSkip(JsonPath, _json);
|
|
GenerateClassFilesPerSheet(_json);
|
|
|
|
_lastUpdated = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
|
|
|
if (isJsonSaved)
|
|
{
|
|
_refreshTrigger = true;
|
|
SaveChangeLog(_json);
|
|
EditorPrefs.SetBool("GoogleSheetManager_ShouldCreateSO", true);
|
|
AssetDatabase.Refresh();
|
|
}
|
|
}
|
|
|
|
private bool CanFetchData()
|
|
{
|
|
return !string.IsNullOrWhiteSpace(_editorName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Json 로그 저장
|
|
/// </summary>
|
|
private void SaveChangeLog(string json)
|
|
{
|
|
string logsDirectory = Path.GetDirectoryName(ChangeLogPath);
|
|
if (!Directory.Exists(logsDirectory))
|
|
{
|
|
Directory.CreateDirectory(logsDirectory);
|
|
AssetDatabase.ImportAsset(logsDirectory);
|
|
}
|
|
|
|
var log = AssetDatabase.LoadAssetAtPath<GoogleSheetChangeLog>(ChangeLogPath);
|
|
if (log == null)
|
|
{
|
|
log = ScriptableObject.CreateInstance<GoogleSheetChangeLog>();
|
|
AssetDatabase.CreateAsset(log, ChangeLogPath);
|
|
}
|
|
|
|
string previousJson = log.Logs.Count > 0 ? log.Logs[^1].JsonSnapshot : null;
|
|
|
|
// 차이 비교
|
|
if (!string.IsNullOrEmpty(previousJson))
|
|
{
|
|
string diffResult = GoogleSheetDiffHelper.GenerateDiff(previousJson, json);
|
|
Debug.Log(diffResult);
|
|
}
|
|
|
|
string saveTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
|
log.Logs.Add(new GoogleSheetChangeLog.LogEntry
|
|
{
|
|
Editor = _editorName,
|
|
Timestamp = saveTime,
|
|
JsonSnapshot = json
|
|
});
|
|
|
|
EditorUtility.SetDirty(log);
|
|
AssetDatabase.SaveAssets();
|
|
SaveJsonBackup(json, saveTime);
|
|
_editorName = null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Json 백업
|
|
/// </summary>
|
|
private void SaveJsonBackup(string json, string saveTime)
|
|
{
|
|
string safeSaveTime = saveTime.Replace(":", "-"); // 윈도우 파일 이름 안전 처리
|
|
string folderPath = Path.Combine(Application.dataPath, "0.Datas/02.Scripts/GenerateGoogleSheet/Backups");
|
|
|
|
if (!Directory.Exists(folderPath))
|
|
Directory.CreateDirectory(folderPath);
|
|
|
|
string fileName = $"{safeSaveTime} by {_editorName}.json";
|
|
string filePath = Path.Combine(folderPath, fileName);
|
|
|
|
File.WriteAllText(filePath, json);
|
|
}
|
|
|
|
[BoxGroup("버전 복구")]
|
|
[Button("선택한 버전과 현재 비교")]
|
|
private void CompareWithSelectedVersion()
|
|
{
|
|
var log = AssetDatabase.LoadAssetAtPath<GoogleSheetChangeLog>(ChangeLogPath);
|
|
if (log == null || _restoreIndex < 0 || _restoreIndex >= log.Logs.Count)
|
|
{
|
|
Debug.LogWarning("비교할 수 있는 로그가 없습니다.");
|
|
return;
|
|
}
|
|
|
|
string restoreJson = log.Logs[_restoreIndex].JsonSnapshot;
|
|
string currentJson = File.Exists(JsonPath) ? File.ReadAllText(JsonPath) : "";
|
|
|
|
List<GoogleSheetDiff> diffs = GoogleSheetFetchHelper.CompareJsonDiff(currentJson, restoreJson);
|
|
|
|
if (diffs.Count > 0)
|
|
{
|
|
GoogleSheetDiffViewer.ShowWindow(diffs, true); // 현재 → 선택 버전
|
|
Debug.Log("[GoogleSheetManager] 선택한 버전과 현재 버전 간의 변경점을 표시합니다.");
|
|
}
|
|
else
|
|
{
|
|
Debug.Log("[GoogleSheetManager] 변경점 없음.");
|
|
}
|
|
}
|
|
|
|
[BoxGroup("버전 복구")]
|
|
[Button("선택한 버전으로 복구")]
|
|
private void RestoreSelectedVersion()
|
|
{
|
|
var log = AssetDatabase.LoadAssetAtPath<GoogleSheetChangeLog>(ChangeLogPath);
|
|
if (log == null || _restoreIndex < 0 || _restoreIndex >= log.Logs.Count)
|
|
{
|
|
Debug.LogWarning("복원할 수 있는 로그가 없습니다.");
|
|
return;
|
|
}
|
|
|
|
string restoreJson = log.Logs[_restoreIndex].JsonSnapshot;
|
|
string currentJson = File.Exists(JsonPath) ? File.ReadAllText(JsonPath) : "";
|
|
|
|
List<GoogleSheetDiff> diffs = GoogleSheetFetchHelper.CompareJsonDiff(currentJson, restoreJson);
|
|
|
|
if (diffs.Count > 0)
|
|
GoogleSheetDiffViewer.ShowWindow(diffs); // 변경 전 → 변경 후
|
|
|
|
// 복원 처리
|
|
_json = restoreJson;
|
|
|
|
SaveFileOrSkip(JsonPath, _json);
|
|
CreateGoogleSheetSo();
|
|
_lastUpdated = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
|
|
|
Debug.Log($"[{log.Logs[_restoreIndex].Editor}]의 버전으로 복원 완료");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 버전 로그 드롭다운 함수
|
|
/// </summary>
|
|
private IEnumerable<ValueDropdownItem<int>> GetVersionOptions()
|
|
{
|
|
var log = AssetDatabase.LoadAssetAtPath<GoogleSheetChangeLog>(ChangeLogPath);
|
|
if (log == null)
|
|
yield break;
|
|
|
|
for (int i = 0; i < log.Logs.Count; i++)
|
|
{
|
|
yield return new ValueDropdownItem<int>(
|
|
$"{i} - {log.Logs[i].Timestamp} by {log.Logs[i].Editor}", i);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 구글 시트 데이터 읽어오기
|
|
/// </summary>
|
|
private async Task<string> LoadDataGoogleSheet(string url)
|
|
{
|
|
using (HttpClient client = new HttpClient())
|
|
{
|
|
try
|
|
{
|
|
byte[] dataBytes = await client.GetByteArrayAsync(url);
|
|
return Encoding.UTF8.GetString(dataBytes);
|
|
}
|
|
catch (HttpRequestException e)
|
|
{
|
|
Debug.LogError($"Request error: {e.Message}");
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// jSON 데이터 파일 읽어오기
|
|
/// </summary>
|
|
private string LoadDataLocalJson()
|
|
{
|
|
if (File.Exists(JsonPath))
|
|
{
|
|
return File.ReadAllText(JsonPath);
|
|
}
|
|
|
|
Debug.Log($"Json 파일이 존재하지 않습니다\n{JsonPath}");
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 파일 생성 및 비교
|
|
/// </summary>
|
|
private bool SaveFileOrSkip(string path, string contents)
|
|
{
|
|
string directoryPath = Path.GetDirectoryName(path);
|
|
if (!Directory.Exists(directoryPath))
|
|
{
|
|
Directory.CreateDirectory(directoryPath);
|
|
}
|
|
|
|
if (File.Exists(path) && File.ReadAllText(path).Equals(contents))
|
|
return false;
|
|
|
|
File.WriteAllText(path, contents);
|
|
return true;
|
|
}
|
|
|
|
private bool IsExistAvailSheets(string sheetName)
|
|
{
|
|
return Array.Exists(_availSheetArray, x => x == sheetName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 유효한 구글 웹 앱 URL인지 확인
|
|
/// </summary>
|
|
private bool IsValidGoogleSheetUrl(string url)
|
|
{
|
|
return !string.IsNullOrEmpty(url)
|
|
&& url.StartsWith("https://script.google.com/macros/")
|
|
&& url.EndsWith("/exec");
|
|
}
|
|
|
|
private void GenerateClassFilesPerSheet(string jsonInput)
|
|
{
|
|
JObject jsonObject = JObject.Parse(jsonInput);
|
|
string basePath = $"Assets{_generateFolderPath}";
|
|
|
|
// enum 후보 수집
|
|
Dictionary<string, HashSet<string>> enumCandidates = new();
|
|
foreach (var jObject in jsonObject)
|
|
{
|
|
string className = jObject.Key;
|
|
if (!IsExistAvailSheets(className)) continue;
|
|
var items = (JArray)jObject.Value;
|
|
if (items.Count < 2) continue;
|
|
|
|
for (int i = 1; i < items.Count; i++)
|
|
{
|
|
foreach (var property in ((JObject)items[i]).Properties())
|
|
{
|
|
string rawName = property.Name;
|
|
if (!rawName.Contains("_Enum")) continue;
|
|
|
|
string[] parts = rawName.Split(':');
|
|
string enumTypeName =
|
|
parts.Length > 1 ? parts[1].Replace("_Enum", "") : rawName.Replace("_Enum", "");
|
|
string enumValue = NormalizeEnumKey(property.Value.ToString());
|
|
|
|
if (!enumCandidates.ContainsKey(enumTypeName))
|
|
enumCandidates[enumTypeName] = new();
|
|
|
|
enumCandidates[enumTypeName].Add(enumValue);
|
|
}
|
|
}
|
|
}
|
|
|
|
// EnumTypes.cs 생성
|
|
StringBuilder enumCode = new();
|
|
enumCode.AppendLine("// <auto-generated>");
|
|
enumCode.AppendLine("using System;\n");
|
|
|
|
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)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(value) && value != "None")
|
|
enumCode.AppendLine($" {value} = {index++},");
|
|
}
|
|
|
|
enumCode.AppendLine("}\n");
|
|
}
|
|
|
|
File.WriteAllText($"{basePath}/EnumTypes.cs", enumCode.ToString());
|
|
AssetDatabase.ImportAsset($"{basePath}/EnumTypes.cs");
|
|
|
|
// 시트별 클래스 파일 생성
|
|
foreach (var jObject in jsonObject)
|
|
{
|
|
string className = jObject.Key;
|
|
if (!IsExistAvailSheets(className)) continue;
|
|
var items = (JArray)jObject.Value;
|
|
if (items.Count < 2) continue;
|
|
|
|
string dataCode = GenerateDataClassCode(className, items);
|
|
string soCode = GenerateSoClassCode(className);
|
|
|
|
string dataPath = $"{basePath}/{className}.cs";
|
|
string soPath = $"{basePath}/{className}So.cs";
|
|
|
|
File.WriteAllText(dataPath, dataCode);
|
|
File.WriteAllText(soPath, soCode);
|
|
|
|
AssetDatabase.ImportAsset(dataPath);
|
|
AssetDatabase.ImportAsset(soPath);
|
|
}
|
|
}
|
|
|
|
private string GenerateSoClassCode(string className)
|
|
{
|
|
return
|
|
$"// <auto-generated> File: {className}So.cs\n" +
|
|
"using System.Collections.Generic;\n" +
|
|
"using UnityEngine;\n\n" +
|
|
$"[CreateAssetMenu(fileName = \"{className}So\", menuName = \"GoogleSheet/{className}So\")]\n" +
|
|
$"public class {className}So : ScriptableObject \n" +
|
|
$"{{\n public List<{className}> {className}List;\n}}\n";
|
|
}
|
|
|
|
private string GenerateDataClassCode(string className, JArray items)
|
|
{
|
|
var commentRow = (JObject)items[0];
|
|
var sampleRow = (JObject)items[1];
|
|
|
|
StringBuilder sb = new();
|
|
sb.AppendLine("// <auto-generated>");
|
|
sb.AppendLine("using System;");
|
|
sb.AppendLine("using UnityEngine;");
|
|
|
|
sb.AppendLine("[Serializable]");
|
|
sb.AppendLine($"public class {className} \n{{");
|
|
|
|
int count = sampleRow.Properties().Count();
|
|
string[] types = new string[count];
|
|
string[] names = new string[count];
|
|
string[] tooltips = new string[count];
|
|
|
|
foreach (JToken item in items.Skip(1))
|
|
{
|
|
int i = 0;
|
|
foreach (var prop in ((JObject)item).Properties())
|
|
{
|
|
string rawName = prop.Name;
|
|
string propType = GetCSharpType(prop.Value.Type);
|
|
string fieldName, enumName;
|
|
|
|
if (rawName.Contains(":") && rawName.EndsWith("_Enum"))
|
|
{
|
|
string[] parts = rawName.Split(':');
|
|
fieldName = parts[0];
|
|
enumName = parts[1].Replace("_Enum", "");
|
|
types[i] = enumName;
|
|
names[i] = fieldName;
|
|
}
|
|
else if (rawName.EndsWith("_Enum"))
|
|
{
|
|
fieldName = rawName.Replace("_Enum", "");
|
|
enumName = fieldName;
|
|
types[i] = enumName;
|
|
names[i] = fieldName;
|
|
}
|
|
else
|
|
{
|
|
types[i] ??= propType;
|
|
names[i] ??= rawName;
|
|
}
|
|
|
|
tooltips[i] ??= commentRow.TryGetValue(rawName, out var tip) ? tip.ToString() : "";
|
|
i++;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(tooltips[i]))
|
|
{
|
|
sb.AppendLine($" /// <summary>{tooltips[i]}</summary>");
|
|
sb.AppendLine($" [Tooltip(\"{tooltips[i]}\")]");
|
|
}
|
|
|
|
sb.AppendLine($" public {types[i]} {names[i]};\n");
|
|
}
|
|
|
|
sb.AppendLine("}");
|
|
return sb.ToString();
|
|
}
|
|
|
|
private string GetCSharpType(JTokenType jsonType)
|
|
{
|
|
switch (jsonType)
|
|
{
|
|
case JTokenType.Integer:
|
|
return "int";
|
|
case JTokenType.Float:
|
|
return "float";
|
|
case JTokenType.Boolean:
|
|
return "bool";
|
|
default:
|
|
return "string";
|
|
}
|
|
}
|
|
|
|
private bool CreateGoogleSheetSo()
|
|
{
|
|
JObject jsonObject = JObject.Parse(_json);
|
|
bool allSuccess = true;
|
|
|
|
foreach (var sheetPair in jsonObject)
|
|
{
|
|
string sheetName = sheetPair.Key;
|
|
if (!IsExistAvailSheets(sheetName))
|
|
continue;
|
|
|
|
// 1. 데이터 클래스 및 SO 클래스 타입 찾기
|
|
Type dataType = FindTypeByName(sheetName);
|
|
Type soType = FindTypeByName($"{sheetName}So");
|
|
|
|
if (dataType == null || soType == null)
|
|
{
|
|
Debug.LogError($"[GoogleSheetManager] 타입을 찾을 수 없습니다: {sheetName} 또는 {sheetName}So");
|
|
allSuccess = false;
|
|
continue;
|
|
}
|
|
|
|
// 2. SO 경로 설정 및 불러오기 / 생성
|
|
string soDirectory = $"Assets{_generateFolderPath}/So";
|
|
if (!Directory.Exists(soDirectory))
|
|
{
|
|
Directory.CreateDirectory(soDirectory);
|
|
AssetDatabase.ImportAsset(soDirectory);
|
|
}
|
|
|
|
string soPath = $"{soDirectory}/{sheetName}So.asset";
|
|
|
|
ScriptableObject soInstance = AssetDatabase.LoadAssetAtPath<ScriptableObject>(soPath);
|
|
if (soInstance == null)
|
|
{
|
|
soInstance = ScriptableObject.CreateInstance(soType);
|
|
AssetDatabase.CreateAsset(soInstance, soPath);
|
|
}
|
|
GoogleSheetAddressableAutoSetup.AutoRegisterSo(soPath);
|
|
|
|
// 3. 데이터 파싱
|
|
IList list = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(dataType));
|
|
var dataArray = (JArray)sheetPair.Value;
|
|
|
|
for (int i = 1; i < dataArray.Count; i++) // 0번은 주석이므로 제외
|
|
{
|
|
JObject item = (JObject)dataArray[i];
|
|
object dataInstance = Activator.CreateInstance(dataType);
|
|
|
|
foreach (var prop in item.Properties())
|
|
{
|
|
string rawName = prop.Name;
|
|
string fieldName = rawName.Contains(":") ? rawName.Split(':')[0] : rawName;
|
|
|
|
FieldInfo field = dataType.GetField(fieldName,
|
|
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
|
|
|
if (field == null)
|
|
{
|
|
Debug.LogWarning($"[GoogleSheetManager] 필드 누락: {dataType.Name}.{fieldName}");
|
|
continue;
|
|
}
|
|
|
|
try
|
|
{
|
|
object value;
|
|
if (field.FieldType.IsEnum)
|
|
{
|
|
string enumRaw = prop.Value.ToString();
|
|
string formatted = NormalizeEnumKey(enumRaw);
|
|
value = Enum.TryParse(field.FieldType, formatted, out var parsed)
|
|
? parsed
|
|
: Activator.CreateInstance(field.FieldType);
|
|
}
|
|
else
|
|
{
|
|
value = Convert.ChangeType(prop.Value.ToString(), field.FieldType);
|
|
}
|
|
|
|
field.SetValue(dataInstance, value);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.LogWarning(
|
|
$"[GoogleSheetManager] 값 할당 실패: {fieldName} = {prop.Value} ({field.FieldType}) → {e.Message}");
|
|
}
|
|
}
|
|
|
|
list.Add(dataInstance);
|
|
}
|
|
|
|
// 4. SO의 필드에 리스트 할당
|
|
FieldInfo listField = soType.GetField($"{sheetName}List",
|
|
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
|
|
|
if (listField != null)
|
|
{
|
|
listField.SetValue(soInstance, list);
|
|
EditorUtility.SetDirty(soInstance);
|
|
}
|
|
else
|
|
{
|
|
Debug.LogError($"[GoogleSheetManager] {soType.Name}에 {sheetName}List 필드가 없습니다.");
|
|
allSuccess = false;
|
|
}
|
|
}
|
|
|
|
AssetDatabase.SaveAssets();
|
|
AssetDatabase.Refresh();
|
|
Debug.Log("✅ 시트별 ScriptableObject 생성 및 데이터 반영 완료");
|
|
|
|
return allSuccess;
|
|
}
|
|
|
|
private Type FindTypeByName(string name)
|
|
{
|
|
return AppDomain.CurrentDomain.GetAssemblies()
|
|
.SelectMany(a => a.GetTypes())
|
|
.FirstOrDefault(t => t.Name == name);
|
|
}
|
|
|
|
private string NormalizeEnumKey(string input)
|
|
{
|
|
string validName = System.Text.RegularExpressions.Regex.Replace(input, @"[^a-zA-Z0-9_]", "_");
|
|
if (char.IsDigit(validName[0]))
|
|
validName = "_" + validName;
|
|
return char.ToUpper(validName[0]) + validName.Substring(1);
|
|
}
|
|
|
|
private void OnValidate()
|
|
{
|
|
if (_refreshTrigger && !_alreadyCreatedSo)
|
|
{
|
|
if (string.IsNullOrEmpty(_json))
|
|
{
|
|
Debug.LogWarning("[GoogleSheetManager] _json이 null이므로 SO 생성을 건너뜁니다.");
|
|
return;
|
|
}
|
|
|
|
_refreshTrigger = false;
|
|
_alreadyCreatedSo = true;
|
|
DelayedSoCreation();
|
|
}
|
|
}
|
|
|
|
private void DelayedSoCreation()
|
|
{
|
|
if (CreateGoogleSheetSo())
|
|
{
|
|
Debug.Log("Fetch done. SO 업데이트 완료");
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning("[GoogleSheetManager] SO 생성 실패. 수동으로 Fetch를 다시 시도하세요.");
|
|
}
|
|
}
|
|
|
|
[BoxGroup("데이터 변경")]
|
|
[Button("런타임 중 데이터 재적용")]
|
|
public void ReloadRuntimeData()
|
|
{
|
|
StartCoroutine(ReloadRoutine());
|
|
}
|
|
|
|
private IEnumerator ReloadRoutine()
|
|
{
|
|
_availSheetArray = _availSheets.Split('/');
|
|
|
|
if (_isAccessGoogleSheet)
|
|
{
|
|
var task = LoadDataGoogleSheet(_googleSheetUrl);
|
|
yield return new WaitUntil(() => task.IsCompleted);
|
|
_json = task.Result;
|
|
}
|
|
else
|
|
{
|
|
_json = LoadDataLocalJson();
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(_json))
|
|
{
|
|
CreateGoogleSheetSo();
|
|
Debug.Log("런타임 데이터 재적용 완료");
|
|
}
|
|
}
|
|
|
|
public void CreateSoAfterScriptReload()
|
|
{
|
|
if (_json != null)
|
|
{
|
|
Debug.Log("[GoogleSheetManager] Script Reload 이후 SO 생성 실행");
|
|
CreateGoogleSheetSo();
|
|
}
|
|
}
|
|
#endif
|
|
} |