Files
test/Assets/PerfectWorld/Scripts/Utils/Editor/JsonScriptableObjectImporterWindow.cs
2026-03-09 19:28:45 +07:00

371 lines
14 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using UnityEditor;
using UnityEngine;
namespace BrewMonster.Utils
{
public class JsonScriptableObjectImporterWindow : EditorWindow
{
private const string DefaultFieldTemplate =
@"public string m_strName;
public int m_id;
public string m_strPath;
public int m_iRowNum;
public int m_iColNum;
public int m_bLimitJump;
public string[] m_routeFiles;
public string m_content;";
private TextAsset jsonFile;
private DefaultAsset outputFolder;
private string dataTypeName = "JsonData";
private string assetTypeName = "JsonDataAsset";
private string assetFileName = "JsonDataAsset";
private string fieldDefinitions = DefaultFieldTemplate;
private bool generateStruct;
private Vector2 scrollPosition;
[MenuItem("Tools/JSON ScriptableObject Importer")]
public static void OpenWindow()
{
GetWindow<JsonScriptableObjectImporterWindow>("JSON -> ScriptableObject");
}
private void OnGUI()
{
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
EditorGUILayout.Space();
EditorGUILayout.HelpBox(
"1. Keo file JSON vao o ben duoi.\n" +
"2. Dien ten type va field cho struct/class.\n" +
"3. Bam Generate Runtime Types de tao file C# trong project Unity.\n" +
"4. Cho Unity compile xong, bam Create Asset From JSON.",
MessageType.Info);
DrawJsonDropZone();
EditorGUILayout.Space();
jsonFile = (TextAsset)EditorGUILayout.ObjectField("JSON Input", jsonFile, typeof(TextAsset), false);
outputFolder = (DefaultAsset)EditorGUILayout.ObjectField("Output Folder", outputFolder, typeof(DefaultAsset), false);
EditorGUILayout.Space();
EditorGUILayout.LabelField("Type Settings", EditorStyles.boldLabel);
dataTypeName = EditorGUILayout.TextField("Data Type Name", dataTypeName);
assetTypeName = EditorGUILayout.TextField("Asset Type Name", assetTypeName);
assetFileName = EditorGUILayout.TextField("Asset File Name", assetFileName);
generateStruct = EditorGUILayout.Toggle("Generate As Struct", generateStruct);
EditorGUILayout.Space();
EditorGUILayout.LabelField("Struct/Class Fields", EditorStyles.boldLabel);
EditorGUILayout.HelpBox(
"Dien cac field theo cu phap C#. Vi du: public string name; public int id; public string[] tags;",
MessageType.None);
fieldDefinitions = EditorGUILayout.TextArea(fieldDefinitions, GUILayout.MinHeight(220));
EditorGUILayout.Space();
using (new EditorGUILayout.HorizontalScope())
{
if (GUILayout.Button("Generate Runtime Types", GUILayout.Height(32)))
{
GenerateRuntimeTypes();
}
if (GUILayout.Button("Create Asset From JSON", GUILayout.Height(32)))
{
CreateAssetFromJson();
}
}
EditorGUILayout.EndScrollView();
}
private void DrawJsonDropZone()
{
Rect dropArea = GUILayoutUtility.GetRect(0f, 70f, GUILayout.ExpandWidth(true));
GUI.Box(dropArea, "Drop file JSON vao day", EditorStyles.helpBox);
Event currentEvent = Event.current;
if (!dropArea.Contains(currentEvent.mousePosition))
{
return;
}
if (currentEvent.type == EventType.DragUpdated || currentEvent.type == EventType.DragPerform)
{
DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
if (currentEvent.type == EventType.DragPerform)
{
DragAndDrop.AcceptDrag();
foreach (UnityEngine.Object draggedObject in DragAndDrop.objectReferences)
{
if (draggedObject is TextAsset textAsset)
{
jsonFile = textAsset;
GUI.FocusControl(null);
break;
}
}
}
currentEvent.Use();
}
}
private void GenerateRuntimeTypes()
{
if (!ValidateGenerateInputs())
{
return;
}
string folderPath = AssetDatabase.GetAssetPath(outputFolder);
string sanitizedDataTypeName = SanitizeIdentifier(dataTypeName, "JsonData");
string sanitizedAssetTypeName = SanitizeIdentifier(assetTypeName, sanitizedDataTypeName + "Asset");
string outputPath = Path.Combine(folderPath, sanitizedAssetTypeName + ".cs").Replace("\\", "/");
string source = BuildRuntimeSource(
sanitizedDataTypeName,
sanitizedAssetTypeName,
generateStruct,
fieldDefinitions);
File.WriteAllText(outputPath, source, new UTF8Encoding(false));
AssetDatabase.Refresh();
Debug.Log("Generated runtime types at: " + outputPath);
EditorUtility.DisplayDialog(
"Generate Complete",
"Da tao file C# runtime. Hay doi Unity compile xong roi bam 'Create Asset From JSON'.",
"OK");
}
private void CreateAssetFromJson()
{
if (!ValidateImportInputs())
{
return;
}
string sanitizedDataTypeName = SanitizeIdentifier(dataTypeName, "JsonData");
string sanitizedAssetTypeName = SanitizeIdentifier(assetTypeName, sanitizedDataTypeName + "Asset");
string jsonText = jsonFile.text;
Type dataType = FindType(sanitizedDataTypeName);
Type assetType = FindType(sanitizedAssetTypeName);
Type wrapperType = FindType(sanitizedDataTypeName + "ArrayWrapper");
if (dataType == null || assetType == null || wrapperType == null)
{
EditorUtility.DisplayDialog(
"Types Not Ready",
"Khong tim thay cac type vua generate. Hay chac chan Unity da compile xong sau khi bam Generate.",
"OK");
return;
}
ScriptableObject asset = ScriptableObject.CreateInstance(assetType);
SetFieldIfExists(assetType, asset, "rawJson", jsonText);
string trimmedJson = jsonText.TrimStart();
if (trimmedJson.StartsWith("[", StringComparison.Ordinal))
{
object wrapper = JsonUtility.FromJson("{\"items\":" + jsonText + "}", wrapperType);
object items = wrapperType.GetField("items", BindingFlags.Public | BindingFlags.Instance)?.GetValue(wrapper);
SetFieldIfExists(assetType, asset, "items", items);
}
else
{
object data = JsonUtility.FromJson(jsonText, dataType);
SetFieldIfExists(assetType, asset, "data", data);
}
string folderPath = AssetDatabase.GetAssetPath(outputFolder);
string requestedName = string.IsNullOrWhiteSpace(assetFileName) ? sanitizedAssetTypeName : assetFileName.Trim();
string assetPath = AssetDatabase.GenerateUniqueAssetPath(
Path.Combine(folderPath, requestedName + ".asset").Replace("\\", "/"));
AssetDatabase.CreateAsset(asset, assetPath);
EditorUtility.SetDirty(asset);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
Selection.activeObject = asset;
Debug.Log("Created ScriptableObject asset at: " + assetPath);
EditorUtility.DisplayDialog("Import Complete", "Da tao asset tu JSON thanh cong.", "OK");
}
private bool ValidateGenerateInputs()
{
if (outputFolder == null)
{
EditorUtility.DisplayDialog("Missing Output Folder", "Hay chon folder de tao file C# runtime.", "OK");
return false;
}
string folderPath = AssetDatabase.GetAssetPath(outputFolder);
if (!AssetDatabase.IsValidFolder(folderPath))
{
EditorUtility.DisplayDialog("Invalid Folder", "Output Folder phai la mot folder trong Assets.", "OK");
return false;
}
if (string.IsNullOrWhiteSpace(dataTypeName) || string.IsNullOrWhiteSpace(assetTypeName))
{
EditorUtility.DisplayDialog("Missing Type Names", "Hay nhap Data Type Name va Asset Type Name.", "OK");
return false;
}
if (string.IsNullOrWhiteSpace(fieldDefinitions))
{
EditorUtility.DisplayDialog("Missing Fields", "Hay nhap field cho struct/class.", "OK");
return false;
}
return true;
}
private bool ValidateImportInputs()
{
if (!ValidateGenerateInputs())
{
return false;
}
if (jsonFile == null)
{
EditorUtility.DisplayDialog("Missing JSON", "Hay keo hoac chon file JSON dau vao.", "OK");
return false;
}
return true;
}
private static string BuildRuntimeSource(
string dataType,
string assetType,
bool asStruct,
string fields)
{
string keyword = asStruct ? "struct" : "class";
string createMenuName = "Tools/Generated/" + assetType;
StringBuilder builder = new StringBuilder();
builder.AppendLine("using System;");
builder.AppendLine("using UnityEngine;");
builder.AppendLine();
builder.AppendLine("[Serializable]");
builder.AppendLine("public " + keyword + " " + dataType);
builder.AppendLine("{");
foreach (string line in NormalizeFieldLines(fields))
{
builder.AppendLine(" " + line);
}
builder.AppendLine("}");
builder.AppendLine();
builder.AppendLine("[CreateAssetMenu(fileName = \"" + assetType + "\", menuName = \"" + createMenuName + "\")]");
builder.AppendLine("public class " + assetType + " : ScriptableObject");
builder.AppendLine("{");
builder.AppendLine(" public " + dataType + " data;");
builder.AppendLine(" public " + dataType + "[] items;");
builder.AppendLine(" [TextArea(5, 20)]");
builder.AppendLine(" public string rawJson;");
builder.AppendLine("}");
builder.AppendLine();
builder.AppendLine("[Serializable]");
builder.AppendLine("public class " + dataType + "ArrayWrapper");
builder.AppendLine("{");
builder.AppendLine(" public " + dataType + "[] items;");
builder.AppendLine("}");
return builder.ToString();
}
private static IEnumerable<string> NormalizeFieldLines(string fields)
{
return fields
.Replace("\r\n", "\n")
.Split('\n')
.Select(line => line.Trim())
.Where(line => !string.IsNullOrWhiteSpace(line));
}
private static void SetFieldIfExists(Type targetType, object target, string fieldName, object value)
{
FieldInfo field = targetType.GetField(fieldName, BindingFlags.Public | BindingFlags.Instance);
if (field != null)
{
field.SetValue(target, value);
}
}
private static string SanitizeIdentifier(string value, string fallback)
{
if (string.IsNullOrWhiteSpace(value))
{
return fallback;
}
List<char> chars = new List<char>();
foreach (char character in value.Trim())
{
if (char.IsLetterOrDigit(character) || character == '_')
{
chars.Add(character);
}
}
if (chars.Count == 0)
{
return fallback;
}
if (!char.IsLetter(chars[0]) && chars[0] != '_')
{
chars.Insert(0, '_');
}
return new string(chars.ToArray());
}
private static Type FindType(string typeName)
{
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
{
Type directMatch = assembly.GetType(typeName);
if (directMatch != null)
{
return directMatch;
}
Type fallbackMatch = GetTypesSafely(assembly).FirstOrDefault(type => type.Name == typeName);
if (fallbackMatch != null)
{
return fallbackMatch;
}
}
return null;
}
private static IEnumerable<Type> GetTypesSafely(Assembly assembly)
{
try
{
return assembly.GetTypes();
}
catch (ReflectionTypeLoadException exception)
{
return exception.Types.Where(type => type != null);
}
}
}
}