mirror of
https://github.com/shinyflvre/Mate-Engine.git
synced 2026-08-29 18:31:58 +00:00
304 lines
13 KiB
C#
304 lines
13 KiB
C#
using UnityEditor;
|
|
using UnityEngine;
|
|
using System;
|
|
using System.IO;
|
|
using System.IO.Compression;
|
|
using System.Collections.Generic;
|
|
|
|
public class ModExporterWindow : EditorWindow
|
|
{
|
|
enum ExportMode { StandardMod, DanceMod }
|
|
enum StandardModType { Mod, Sound, Particle, Animation, Misc }
|
|
StandardModType standardModType = StandardModType.Mod;
|
|
GameObject exportObject;
|
|
string modName = "MyMod";
|
|
string author = "";
|
|
string description = "";
|
|
string weblink = "";
|
|
BuildTarget buildTarget = BuildTarget.StandaloneWindows64;
|
|
bool showMoreInfo = false;
|
|
|
|
ExportMode mode = ExportMode.StandardMod;
|
|
MEDanceModCreator danceCreator;
|
|
string danceModName = "MyDance";
|
|
string danceAuthor = "";
|
|
BuildTarget danceBuildTarget = BuildTarget.StandaloneWindows64;
|
|
|
|
[MenuItem("MateEngine/ME Mod Exporter")]
|
|
public static void ShowWindow() => GetWindow<ModExporterWindow>("ME Mod Exporter");
|
|
|
|
[MenuItem("MateEngine/Export Scene Registry")]
|
|
public static void ExportSceneRegistry()
|
|
{
|
|
var allRoots = UnityEngine.SceneManagement.SceneManager.GetActiveScene().GetRootGameObjects();
|
|
List<SceneObjectInfo> all = new List<SceneObjectInfo>();
|
|
foreach (var root in allRoots) CollectSceneObjects(root.transform, "", all);
|
|
var wrapper = new SceneRegistry { SceneObjects = all };
|
|
var json = JsonUtility.ToJson(wrapper, true);
|
|
File.WriteAllText(Path.Combine(Application.dataPath, "scene_registry.json"), json);
|
|
Debug.Log("[ModExporter] scene_registry.json written.");
|
|
}
|
|
|
|
static void CollectSceneObjects(Transform tr, string parentPath, List<SceneObjectInfo> list)
|
|
{
|
|
string path = string.IsNullOrEmpty(parentPath) ? tr.name : parentPath + "/" + tr.name;
|
|
var comps = new List<string>();
|
|
foreach (var c in tr.GetComponents<Component>()) if (c != null) comps.Add(c.GetType().Name);
|
|
list.Add(new SceneObjectInfo { name = tr.name, path = path, components = comps });
|
|
foreach (Transform child in tr) CollectSceneObjects(child, path, list);
|
|
}
|
|
|
|
void OnGUI()
|
|
{
|
|
mode = (ExportMode)GUILayout.Toolbar((int)mode, new[] { "Standard Mod", "Dance Mod" });
|
|
GUILayout.Space(8);
|
|
if (mode == ExportMode.StandardMod) DrawStandardModGUI();
|
|
else DrawDanceModGUI();
|
|
}
|
|
|
|
void DrawStandardModGUI()
|
|
{
|
|
GUILayout.Label("Export Mod", EditorStyles.boldLabel);
|
|
GUILayout.Space(8);
|
|
EditorGUILayout.HelpBox("Modding Limitations\nThe MateEngine SDK is limited and only supports a few modding aspects. Creating your own C# assemblies is not allowed, as we aim to prevent any potential malware distribution.", MessageType.Info);
|
|
GUILayout.Space(12);
|
|
showMoreInfo = EditorGUILayout.Foldout(showMoreInfo, "Display the mod limitations", true);
|
|
if (showMoreInfo)
|
|
{
|
|
EditorGUILayout.HelpBox("- You can create GameObjects that hold existing components\n- You can use SDK scripts to remove things at runtime\n- You can use prefabs with Audio Sources, Particle Systems, Meshes", MessageType.None);
|
|
}
|
|
exportObject = (GameObject)EditorGUILayout.ObjectField("Root GameObject", exportObject, typeof(GameObject), true);
|
|
modName = EditorGUILayout.TextField("Mod Name", modName);
|
|
author = EditorGUILayout.TextField("Author", author);
|
|
GUILayout.Label("Description");
|
|
description = EditorGUILayout.TextArea(description, GUILayout.Height(60));
|
|
weblink = EditorGUILayout.TextField("Weblink", weblink);
|
|
buildTarget = (BuildTarget)EditorGUILayout.EnumPopup("Build Target", buildTarget);
|
|
standardModType = (StandardModType)EditorGUILayout.EnumPopup("Mode Type", standardModType);
|
|
GUI.enabled = exportObject != null && !string.IsNullOrEmpty(modName);
|
|
if (GUILayout.Button("Export Mod", GUILayout.Height(40))) ExportMod();
|
|
GUI.enabled = true;
|
|
}
|
|
|
|
void DrawDanceModGUI()
|
|
{
|
|
GUILayout.Label("Export Dance Mod", EditorStyles.boldLabel);
|
|
GUILayout.Space(8);
|
|
danceCreator = (MEDanceModCreator)EditorGUILayout.ObjectField("Dance Creator", danceCreator, typeof(MEDanceModCreator), true);
|
|
danceModName = EditorGUILayout.TextField("Dance Name", danceModName);
|
|
danceAuthor = EditorGUILayout.TextField("Author", danceAuthor);
|
|
danceBuildTarget = (BuildTarget)EditorGUILayout.EnumPopup("Build Target", danceBuildTarget);
|
|
GUI.enabled = danceCreator != null && !string.IsNullOrEmpty(danceModName);
|
|
if (GUILayout.Button("Export Dance Mod", GUILayout.Height(40))) ExportDanceMod();
|
|
GUI.enabled = true;
|
|
}
|
|
|
|
void ExportMod()
|
|
{
|
|
string tempDir = Path.Combine("Assets/__ModExportTemp__");
|
|
if (Directory.Exists(tempDir)) Directory.Delete(tempDir, true);
|
|
Directory.CreateDirectory(tempDir);
|
|
|
|
string prefabPath = Path.Combine(tempDir, modName + ".prefab").Replace("\\", "/");
|
|
var prefab = PrefabUtility.SaveAsPrefabAssetAndConnect(exportObject, prefabPath, InteractionMode.UserAction);
|
|
if (prefab == null) { Debug.LogError("[ModExporter] Failed to create prefab."); return; }
|
|
|
|
var sceneRefs = new Dictionary<string, string>();
|
|
ExtractSceneLinks(exportObject, sceneRefs);
|
|
|
|
string buildDir = Path.Combine("TempModBuild", modName);
|
|
if (Directory.Exists(buildDir)) Directory.Delete(buildDir, true);
|
|
Directory.CreateDirectory(buildDir);
|
|
|
|
AssetImporter.GetAtPath(prefabPath).assetBundleName = modName.ToLower() + ".bundle";
|
|
BuildPipeline.BuildAssetBundles(buildDir, BuildAssetBundleOptions.None, buildTarget);
|
|
|
|
File.WriteAllText(Path.Combine(buildDir, "modinfo.json"),
|
|
"{\n" +
|
|
$" \"name\": \"{modName}\",\n" +
|
|
$" \"author\": \"{author}\",\n" +
|
|
$" \"description\": \"{description}\",\n" +
|
|
$" \"weblink\": \"{weblink}\",\n" +
|
|
$" \"buildTarget\": \"{buildTarget}\",\n" +
|
|
$" \"timestamp\": \"{DateTime.UtcNow:O}\"\n" +
|
|
"}");
|
|
|
|
if (sceneRefs.Count > 0)
|
|
{
|
|
File.WriteAllText(Path.Combine(buildDir, "scene_links.json"),
|
|
JsonUtility.ToJson(new SerializableRefMap(sceneRefs), true));
|
|
}
|
|
|
|
string finalDir = Path.Combine("ExportedMods");
|
|
Directory.CreateDirectory(finalDir);
|
|
string mePath = Path.Combine(finalDir, modName + ".me");
|
|
if (File.Exists(mePath)) File.Delete(mePath);
|
|
File.WriteAllText(Path.Combine(buildDir, "mod_type.json"), "{\"type\":\"" + standardModType.ToString() + "\"}");
|
|
ZipFile.CreateFromDirectory(buildDir, mePath);
|
|
|
|
|
|
AssetDatabase.RemoveAssetBundleName(modName.ToLower() + ".bundle", true);
|
|
AssetDatabase.DeleteAsset(prefabPath);
|
|
if (Directory.Exists(tempDir)) Directory.Delete(tempDir, true);
|
|
if (Directory.Exists(buildDir)) Directory.Delete(buildDir, true);
|
|
|
|
AssetDatabase.Refresh();
|
|
EditorUtility.RevealInFinder(mePath);
|
|
Debug.Log("[ModExporter] Export finished: " + mePath);
|
|
}
|
|
|
|
void ExportDanceMod()
|
|
{
|
|
var meta = danceCreator.GetMetadata();
|
|
var clip = danceCreator.GetDanceClip();
|
|
var audio = danceCreator.GetAudioClip();
|
|
|
|
string tempRoot = "Assets/__DanceExportTemp__";
|
|
if (Directory.Exists(tempRoot)) Directory.Delete(tempRoot, true);
|
|
Directory.CreateDirectory(tempRoot);
|
|
|
|
string buildDir = Path.Combine("TempDanceBuild", danceModName);
|
|
if (Directory.Exists(buildDir)) Directory.Delete(buildDir, true);
|
|
Directory.CreateDirectory(buildDir);
|
|
|
|
string clipAssetPath = null;
|
|
if (clip != null)
|
|
{
|
|
var copy = new AnimationClip();
|
|
EditorUtility.CopySerialized(clip, copy);
|
|
clipAssetPath = Path.Combine(tempRoot, danceModName + "_dance.anim").Replace("\\", "/");
|
|
AssetDatabase.CreateAsset(copy, clipAssetPath);
|
|
AssetImporter.GetAtPath(clipAssetPath).assetBundleName = "dance.bundle";
|
|
}
|
|
|
|
string audioAssetPath = null;
|
|
if (audio != null)
|
|
{
|
|
string src = AssetDatabase.GetAssetPath(audio);
|
|
if (!string.IsNullOrEmpty(src))
|
|
{
|
|
audioAssetPath = Path.Combine(tempRoot, Path.GetFileName(src)).Replace("\\", "/");
|
|
AssetDatabase.CopyAsset(src, audioAssetPath);
|
|
AssetImporter.GetAtPath(audioAssetPath).assetBundleName = "dance.bundle";
|
|
}
|
|
}
|
|
|
|
BuildPipeline.BuildAssetBundles(buildDir, BuildAssetBundleOptions.None, danceBuildTarget);
|
|
|
|
var danceInfo = new DanceExportMeta
|
|
{
|
|
songName = meta.songName,
|
|
songAuthor = meta.songAuthor,
|
|
mmdAuthor = meta.mmdAuthor,
|
|
songLength = meta.songLength,
|
|
placeholderClipName = meta.placeholderClipName
|
|
};
|
|
File.WriteAllText(Path.Combine(buildDir, "dance_meta.json"), JsonUtility.ToJson(danceInfo, true));
|
|
|
|
string finalDir = Path.Combine("ExportedMods");
|
|
Directory.CreateDirectory(finalDir);
|
|
string mePath = Path.Combine(finalDir, danceModName + ".me");
|
|
if (File.Exists(mePath)) File.Delete(mePath);
|
|
ZipFile.CreateFromDirectory(buildDir, mePath);
|
|
|
|
if (!string.IsNullOrEmpty(clipAssetPath)) AssetDatabase.DeleteAsset(clipAssetPath);
|
|
if (!string.IsNullOrEmpty(audioAssetPath)) AssetDatabase.DeleteAsset(audioAssetPath);
|
|
if (Directory.Exists(tempRoot)) Directory.Delete(tempRoot, true);
|
|
if (Directory.Exists(buildDir)) Directory.Delete(buildDir, true);
|
|
|
|
AssetDatabase.Refresh();
|
|
EditorUtility.RevealInFinder(mePath);
|
|
Debug.Log("[ModExporter] Dance export finished: " + mePath);
|
|
}
|
|
|
|
void ExtractSceneLinks(GameObject root, Dictionary<string, string> output)
|
|
{
|
|
var prefabObjs = new HashSet<GameObject>();
|
|
foreach (var t in root.GetComponentsInChildren<Transform>(true)) prefabObjs.Add(t.gameObject);
|
|
|
|
foreach (var mb in root.GetComponentsInChildren<MonoBehaviour>(true))
|
|
{
|
|
if (mb == null) continue;
|
|
Type mbType = mb.GetType();
|
|
var fields = mbType.GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
|
foreach (var field in fields)
|
|
{
|
|
string basePath = mbType.Name + "." + field.Name;
|
|
object value = field.GetValue(mb);
|
|
if (value == null) continue;
|
|
|
|
Type fieldType = field.FieldType;
|
|
|
|
if (fieldType == typeof(GameObject) || fieldType.IsSubclassOf(typeof(Component)))
|
|
{
|
|
TryRecordReference(value, basePath, prefabObjs, output);
|
|
}
|
|
else if (typeof(System.Collections.IEnumerable).IsAssignableFrom(fieldType) || fieldType.IsArray)
|
|
{
|
|
int index = 0;
|
|
foreach (var item in (System.Collections.IEnumerable)value)
|
|
{
|
|
if (item == null) continue;
|
|
string indexedPath = basePath + "[" + index + "]";
|
|
ScanObjectFields(item, indexedPath, prefabObjs, output);
|
|
index++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ScanObjectFields(value, basePath, prefabObjs, output);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ScanObjectFields(object obj, string parentPath, HashSet<GameObject> prefabObjs, Dictionary<string, string> output)
|
|
{
|
|
var subFields = obj.GetType().GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
|
foreach (var subField in subFields)
|
|
{
|
|
object val = subField.GetValue(obj);
|
|
if (val == null) continue;
|
|
|
|
string path = parentPath + "." + subField.Name;
|
|
if (val is GameObject || val is Component) TryRecordReference(val, path, prefabObjs, output);
|
|
}
|
|
}
|
|
|
|
void TryRecordReference(object val, string path, HashSet<GameObject> prefabObjs, Dictionary<string, string> output)
|
|
{
|
|
GameObject go = val as GameObject;
|
|
if (val is Component comp) go = comp.gameObject;
|
|
if (go != null && !prefabObjs.Contains(go)) output[path] = GetHierarchyPath(go.transform);
|
|
}
|
|
|
|
string GetHierarchyPath(Transform t)
|
|
{
|
|
var path = t.name;
|
|
while (t.parent != null)
|
|
{
|
|
t = t.parent;
|
|
path = t.name + "/" + path;
|
|
}
|
|
return path;
|
|
}
|
|
|
|
[Serializable] public class SceneRegistry { public List<SceneObjectInfo> SceneObjects; }
|
|
[Serializable] public class SceneObjectInfo { public string name; public string path; public List<string> components; }
|
|
[Serializable]
|
|
public class SerializableRefMap
|
|
{
|
|
public List<string> keys = new List<string>();
|
|
public List<string> values = new List<string>();
|
|
public SerializableRefMap(Dictionary<string, string> dict) { foreach (var kv in dict) { keys.Add(kv.Key); values.Add(kv.Value); } }
|
|
}
|
|
[Serializable]
|
|
class DanceExportMeta
|
|
{
|
|
public string songName;
|
|
public string songAuthor;
|
|
public string mmdAuthor;
|
|
public float songLength;
|
|
public string placeholderClipName;
|
|
}
|
|
}
|