371 lines
14 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|
|
}
|