2024-09-12 07:36:24 +00:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
2024-06-03 18:26:03 +00:00
|
|
|
|
using System.IO;
|
2024-09-12 07:36:24 +00:00
|
|
|
|
using System.Reflection;
|
2024-06-03 18:26:03 +00:00
|
|
|
|
using BlueWater.Interfaces;
|
|
|
|
|
using BlueWater.Items;
|
2024-07-06 12:13:58 +00:00
|
|
|
|
using BlueWater.Npcs.Customers;
|
2024-06-03 18:26:03 +00:00
|
|
|
|
using BlueWater.Utility;
|
2024-12-06 13:20:10 +00:00
|
|
|
|
using Newtonsoft.Json;
|
2024-06-03 18:26:03 +00:00
|
|
|
|
using UnityEditor;
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
|
|
|
|
namespace BlueWater.Editors
|
|
|
|
|
{
|
|
|
|
|
public class JsonHelperEditor : EditorWindow
|
|
|
|
|
{
|
|
|
|
|
// Json파일이 추가될 때 마다, DataType을 추가하고,
|
|
|
|
|
// LoadData의 switch문에 LoadData<>를 추가합니다.
|
|
|
|
|
private enum DataType
|
|
|
|
|
{
|
|
|
|
|
None = 0,
|
2024-07-06 12:13:58 +00:00
|
|
|
|
ItemDataTable,
|
|
|
|
|
CustomerDataTable,
|
2024-08-27 12:23:41 +00:00
|
|
|
|
FoodDataTable,
|
|
|
|
|
CocktailDataTable,
|
2024-09-12 04:30:48 +00:00
|
|
|
|
LiquidDataTable,
|
2024-09-12 04:17:34 +00:00
|
|
|
|
LevelDataTable,
|
|
|
|
|
CardDataTable,
|
2024-11-28 23:07:50 +00:00
|
|
|
|
CardShopDataTable,
|
|
|
|
|
CardNormalDataTable,
|
|
|
|
|
CardRareDataTable
|
2024-06-03 18:26:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private string _jsonFilePath = "Assets/Resources/Json/FileName";
|
2024-08-27 12:23:41 +00:00
|
|
|
|
private string _assetFilePath = "Assets/02.Scripts/ScriptableObject/Item";
|
2024-06-03 18:26:03 +00:00
|
|
|
|
private DataType _dataType = DataType.None;
|
|
|
|
|
|
2024-09-12 07:36:24 +00:00
|
|
|
|
[MenuItem("Tools/02. json -> ScriptableObject")]
|
2024-06-03 18:26:03 +00:00
|
|
|
|
public static void ShowWindow()
|
|
|
|
|
{
|
2024-09-12 07:36:24 +00:00
|
|
|
|
GetWindow<JsonHelperEditor>("02. json -> ScriptableObject");
|
2024-06-03 18:26:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void OnGUI()
|
|
|
|
|
{
|
|
|
|
|
GUILayout.Label("Json파일 ScriptableObject로 자동 변환", EditorStyles.boldLabel);
|
|
|
|
|
GUILayout.Space(10);
|
|
|
|
|
|
|
|
|
|
EditorGUILayout.HelpBox("Assets 폴더를 시작으로 전체 Path가 필요하며, Json 파일이 있는 Path를 입력해야 합니다." +
|
|
|
|
|
"\n파일 확장자는 자동으로 .json이 들어갑니다." +
|
|
|
|
|
"\n예시: Assets/Resources/Json/최종 Json 파일 이름", MessageType.Info);
|
|
|
|
|
_jsonFilePath = EditorGUILayout.TextField("Json 파일 위치", _jsonFilePath);
|
|
|
|
|
|
|
|
|
|
EditorGUILayout.HelpBox("Assets 폴더를 시작으로 전체 Path가 필요하며, File을 저장할 폴더의 전체 Path가 필요합니다." +
|
|
|
|
|
"\n파일명은 Json파일의 이름과 동일하게 자동으로 적용됩니다." +
|
|
|
|
|
"\n예시1: Assets/02.Scripts/ScriptableObject" +
|
|
|
|
|
"\n예시2: Assets/02.Scripts/ScriptableObject/최종 폴더 이름", MessageType.Info);
|
|
|
|
|
_assetFilePath = EditorGUILayout.TextField("저장할 폴더 위치", _assetFilePath);
|
|
|
|
|
_dataType = (DataType)EditorGUILayout.EnumPopup("데이터 종류", _dataType);
|
|
|
|
|
|
|
|
|
|
if (GUILayout.Button("변환하기"))
|
|
|
|
|
{
|
|
|
|
|
LoadDataFromJson();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void LoadDataFromJson()
|
|
|
|
|
{
|
|
|
|
|
if (string.IsNullOrEmpty(_jsonFilePath) || string.IsNullOrEmpty(_assetFilePath))
|
|
|
|
|
{
|
|
|
|
|
EditorUtility.DisplayDialog("경고 메세지", "빈 목록을 채워주세요.", "확인");
|
|
|
|
|
Debug.LogError("빈 목록을 채워주세요.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (_dataType)
|
|
|
|
|
{
|
|
|
|
|
case DataType.None:
|
|
|
|
|
EditorUtility.DisplayDialog("경고 메세지", "데이터 타입이 None인지 확인해주세요.", "확인");
|
|
|
|
|
Debug.LogError("데이터 타입이 None인지 확인해주세요.");
|
|
|
|
|
return;
|
2024-06-21 22:11:53 +00:00
|
|
|
|
case DataType.ItemDataTable:
|
2024-06-03 18:26:03 +00:00
|
|
|
|
LoadData<ItemData, ItemDataSo>();
|
|
|
|
|
break;
|
2024-07-06 12:13:58 +00:00
|
|
|
|
case DataType.CustomerDataTable:
|
|
|
|
|
LoadData<CustomerData, CustomerDataSo>();
|
|
|
|
|
break;
|
|
|
|
|
case DataType.FoodDataTable:
|
|
|
|
|
LoadData<FoodData, FoodDataSo>();
|
|
|
|
|
break;
|
2024-08-27 12:23:41 +00:00
|
|
|
|
case DataType.CocktailDataTable:
|
|
|
|
|
LoadData<CocktailData, CocktailDataSo>();
|
|
|
|
|
break;
|
2024-09-12 04:30:48 +00:00
|
|
|
|
case DataType.LiquidDataTable:
|
|
|
|
|
LoadData<LiquidData, LiquidDataSo>();
|
2024-08-27 12:23:41 +00:00
|
|
|
|
break;
|
2024-09-09 09:50:37 +00:00
|
|
|
|
case DataType.LevelDataTable:
|
|
|
|
|
LoadData<LevelData, LevelDataSo>();
|
|
|
|
|
break;
|
2024-09-12 04:17:34 +00:00
|
|
|
|
case DataType.CardDataTable:
|
|
|
|
|
LoadData<CardData, CardDataSo>();
|
|
|
|
|
break;
|
2024-11-28 23:07:50 +00:00
|
|
|
|
case DataType.CardShopDataTable:
|
|
|
|
|
LoadData<CardShopData, CardShopDataSo>();
|
|
|
|
|
break;
|
|
|
|
|
case DataType.CardNormalDataTable:
|
|
|
|
|
LoadData<CardNormalData, CardNormalDataSo>();
|
|
|
|
|
break;
|
|
|
|
|
case DataType.CardRareDataTable:
|
|
|
|
|
LoadData<CardRareData, CardRareDataSo>();
|
|
|
|
|
break;
|
2024-06-03 18:26:03 +00:00
|
|
|
|
default:
|
|
|
|
|
EditorUtility.DisplayDialog("경고 메세지", "데이터 타입이 제대로 설정되어있는지 확인해주세요.", "OK");
|
|
|
|
|
Debug.LogError("데이터 타입이 제대로 설정되어있는지 확인해주세요.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void LoadData<T, U>()
|
|
|
|
|
where T : class, IIdx
|
|
|
|
|
where U : ScriptableObject, IDataContainer<T>, new()
|
|
|
|
|
{
|
|
|
|
|
var fullJsonFilePath = _jsonFilePath + ".json";
|
|
|
|
|
|
|
|
|
|
if (!File.Exists(fullJsonFilePath))
|
|
|
|
|
{
|
|
|
|
|
EditorUtility.DisplayDialog("경고 메세지", "Json 파일을 찾을 수 없습니다.\n" + fullJsonFilePath, "확인");
|
|
|
|
|
Debug.LogError("Json 파일을 찾을 수 없습니다.\n" + fullJsonFilePath);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-12 07:36:24 +00:00
|
|
|
|
var newDataList = JsonHelper.LoadJsonData<T>(fullJsonFilePath);
|
|
|
|
|
if (newDataList == null || newDataList.Count == 0)
|
2024-06-03 18:26:03 +00:00
|
|
|
|
{
|
|
|
|
|
EditorUtility.DisplayDialog("경고 메세지", "Json 파일 데이터가 비어있거나, 불러올 수 없습니다.\n" + fullJsonFilePath, "확인");
|
|
|
|
|
return;
|
|
|
|
|
}
|
2024-07-15 16:20:39 +00:00
|
|
|
|
|
2024-09-12 07:36:24 +00:00
|
|
|
|
var newDataKeyValueList = new List<SerializableKeyValuePair<T>>();
|
|
|
|
|
foreach (var data in newDataList)
|
|
|
|
|
{
|
|
|
|
|
if (data != null)
|
|
|
|
|
{
|
|
|
|
|
newDataKeyValueList.Add(new SerializableKeyValuePair<T> { Key = data.Idx, Value = data });
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-06-03 18:26:03 +00:00
|
|
|
|
|
|
|
|
|
var assetFileName = Path.GetFileNameWithoutExtension(_jsonFilePath) + ".asset";
|
|
|
|
|
var assetPath = Path.Combine(_assetFilePath, assetFileName);
|
|
|
|
|
|
|
|
|
|
var so = AssetDatabase.LoadAssetAtPath<U>(assetPath);
|
|
|
|
|
if (so == null)
|
|
|
|
|
{
|
|
|
|
|
so = CreateInstance<U>();
|
|
|
|
|
if (so is IDataContainer<T> container)
|
|
|
|
|
{
|
2024-09-12 07:36:24 +00:00
|
|
|
|
container.SetSerializedDataList(newDataKeyValueList);
|
2024-06-03 18:26:03 +00:00
|
|
|
|
}
|
|
|
|
|
AssetDatabase.CreateAsset(so, assetPath);
|
|
|
|
|
EditorUtility.DisplayDialog("알림 메세지", "새로운 ScriptableObject가 생성되었습니다.", "확인");
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (so is IDataContainer<T> container)
|
|
|
|
|
{
|
2024-09-12 07:36:24 +00:00
|
|
|
|
var existingData = container.GetSerializedDataList();
|
|
|
|
|
MergeData(existingData, newDataKeyValueList);
|
|
|
|
|
//existingData.Sort((x, y) => x.Idx.CompareTo(y.Idx));
|
|
|
|
|
container.SetSerializedDataList(existingData);
|
2024-06-03 18:26:03 +00:00
|
|
|
|
}
|
|
|
|
|
EditorUtility.DisplayDialog("알림 메세지", "기존 ScriptableObject가 업데이트되었습니다.", "확인");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
EditorUtility.SetDirty(so);
|
|
|
|
|
AssetDatabase.SaveAssets();
|
|
|
|
|
AssetDatabase.Refresh();
|
|
|
|
|
}
|
2024-09-12 07:36:24 +00:00
|
|
|
|
|
|
|
|
|
private void MergeData<T>(List<SerializableKeyValuePair<T>> existingData, List<SerializableKeyValuePair<T>> newData)
|
|
|
|
|
where T : class, IIdx
|
2024-06-03 18:26:03 +00:00
|
|
|
|
{
|
2024-09-12 07:36:24 +00:00
|
|
|
|
existingData ??= new List<SerializableKeyValuePair<T>>();
|
2024-07-15 16:20:39 +00:00
|
|
|
|
|
2024-08-22 10:39:15 +00:00
|
|
|
|
var newDataIdxSet = new HashSet<string>();
|
2024-07-15 16:20:39 +00:00
|
|
|
|
foreach (var newDataItem in newData)
|
|
|
|
|
{
|
2024-09-12 07:36:24 +00:00
|
|
|
|
if (newDataItem != null && newDataItem.Value != null)
|
2024-07-15 16:20:39 +00:00
|
|
|
|
{
|
2024-09-12 07:36:24 +00:00
|
|
|
|
newDataIdxSet.Add(newDataItem.Key); // Key는 Idx에 해당
|
2024-07-15 16:20:39 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-12 07:36:24 +00:00
|
|
|
|
// 기존 데이터에서 JSON에 없는 항목 제거
|
|
|
|
|
existingData.RemoveAll(item => item == null || !newDataIdxSet.Contains(item.Key));
|
2024-07-15 16:20:39 +00:00
|
|
|
|
|
2024-09-12 07:36:24 +00:00
|
|
|
|
// 기존 데이터를 Dictionary로 변환
|
|
|
|
|
var existingDataDict = new Dictionary<string, SerializableKeyValuePair<T>>();
|
2024-06-03 18:26:03 +00:00
|
|
|
|
foreach (var data in existingData)
|
|
|
|
|
{
|
2024-09-12 07:36:24 +00:00
|
|
|
|
if (data != null && data.Value != null)
|
2024-06-03 18:26:03 +00:00
|
|
|
|
{
|
2024-09-12 07:36:24 +00:00
|
|
|
|
existingDataDict[data.Key] = data;
|
2024-06-03 18:26:03 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-06 13:20:10 +00:00
|
|
|
|
// 새로운 데이터를 처리
|
2024-06-03 18:26:03 +00:00
|
|
|
|
foreach (var newDataItem in newData)
|
|
|
|
|
{
|
2024-09-12 07:36:24 +00:00
|
|
|
|
if (newDataItem == null || newDataItem.Value == null) continue;
|
2024-06-03 18:26:03 +00:00
|
|
|
|
|
2024-09-12 07:36:24 +00:00
|
|
|
|
if (existingDataDict.TryGetValue(newDataItem.Key, out var existingItem))
|
2024-06-03 18:26:03 +00:00
|
|
|
|
{
|
2024-12-06 13:20:10 +00:00
|
|
|
|
// 기존 데이터가 있으면 업데이트
|
2024-09-12 07:36:24 +00:00
|
|
|
|
var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
|
2024-06-03 18:26:03 +00:00
|
|
|
|
foreach (var property in properties)
|
|
|
|
|
{
|
2024-12-06 13:20:10 +00:00
|
|
|
|
// JSON 데이터에서 덮어쓸 프로퍼티만 처리
|
|
|
|
|
if (property.GetCustomAttribute<JsonPropertyAttribute>() != null)
|
2024-06-03 18:26:03 +00:00
|
|
|
|
{
|
2024-12-06 13:20:10 +00:00
|
|
|
|
var newValue = property.GetValue(newDataItem.Value);
|
|
|
|
|
var existingValue = property.GetValue(existingItem.Value);
|
|
|
|
|
|
|
|
|
|
// 덮어쓰기 조건 확인
|
|
|
|
|
if (ShouldOverwriteProperty(newValue, existingValue, property))
|
|
|
|
|
{
|
|
|
|
|
property.SetValue(existingItem.Value, newValue);
|
|
|
|
|
}
|
2024-06-03 18:26:03 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2024-09-12 07:36:24 +00:00
|
|
|
|
// 기존 데이터에 없으면 새로 추가
|
|
|
|
|
existingDataDict[newDataItem.Key] = newDataItem;
|
2024-06-03 18:26:03 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-06 13:20:10 +00:00
|
|
|
|
// 기존 데이터를 유지하고 새로운 데이터를 덮어쓰거나 추가한 결과로 갱신
|
2024-06-03 18:26:03 +00:00
|
|
|
|
existingData.Clear();
|
|
|
|
|
existingData.AddRange(existingDataDict.Values);
|
|
|
|
|
}
|
2024-09-12 07:36:24 +00:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 속성 덮어쓰기 여부를 결정하는 일반적인 로직
|
|
|
|
|
/// </summary>
|
|
|
|
|
private bool ShouldOverwriteProperty(object newValue, object existingValue, PropertyInfo property)
|
|
|
|
|
{
|
|
|
|
|
// 값이 없으면 덮어쓰지 않음
|
|
|
|
|
if (newValue == null) return false;
|
|
|
|
|
|
2024-11-05 12:27:46 +00:00
|
|
|
|
// Enum 타입의 경우, 값을 비교하여 덮어쓸지 결정
|
|
|
|
|
if (property.PropertyType.IsEnum)
|
|
|
|
|
{
|
|
|
|
|
return !newValue.Equals(existingValue); // 기존 enum 값과 다를 경우에만 덮어씀
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-12 07:36:24 +00:00
|
|
|
|
if (property.PropertyType.IsValueType)
|
|
|
|
|
{
|
2024-11-05 12:27:46 +00:00
|
|
|
|
return !newValue.Equals(existingValue); // 기존 값과 다를 경우 덮어씀
|
2024-09-12 07:36:24 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 참조 타입인 경우
|
|
|
|
|
return true; // 참조 타입은 항상 덮어씀 (null 체크는 상위에서 수행됨)
|
|
|
|
|
}
|
2024-06-03 18:26:03 +00:00
|
|
|
|
}
|
|
|
|
|
}
|