SanAndreasUnity/Assets/Scripts/Editor/AssetExporter.cs
2022-01-21 00:17:06 +01:00

330 lines
11 KiB
C#

using SanAndreasUnity.Behaviours;
using SanAndreasUnity.Behaviours.World;
using SanAndreasUnity.Utilities;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
namespace SanAndreasUnity.Editor
{
public class AssetExporter : EditorWindowBase
{
private const string DefaultFolderName = "ExportedAssets";
private string m_selectedFolder = "Assets/" + DefaultFolderName;
string ModelsPath => m_selectedFolder + "/Models";
string CollisionModelsPath => m_selectedFolder + "/CollisionModels";
string MaterialsPath => m_selectedFolder + "/Materials";
string TexturesPath => m_selectedFolder + "/Textures";
string PrefabsPath => m_selectedFolder + "/Prefabs";
private CoroutineInfo m_coroutineInfo;
private int m_numNewlyExportedAssets = 0;
private int m_numAlreadyExportedAssets = 0;
private bool m_exportFromSelection = false;
private bool m_exportRenderMeshes = true;
private bool m_exportMaterials = true;
private bool m_exportTextures = true;
private bool m_exportCollisionMeshes = true;
private bool m_exportPrefabs = true;
[MenuItem(EditorCore.MenuName + "/" + "Asset exporter")]
static void Init()
{
var window = GetWindow<AssetExporter>();
window.Show();
}
public AssetExporter()
{
this.titleContent = new GUIContent("Asset exporter");
}
void OnGUI()
{
EditorGUILayout.HelpBox(
"This tool can export all currenty loaded world objects as assets and prefabs.\n" +
"It will store them in a separate folder, and will only export those objects that were not already exported.",
MessageType.Info,
true);
GUILayout.Space(30);
GUILayout.BeginHorizontal();
EditorGUILayout.LabelField("Folder: " + m_selectedFolder);
if (GUILayout.Button("Change"))
this.ChangeFolder();
GUILayout.FlexibleSpace();
GUILayout.EndHorizontal();
m_exportRenderMeshes = EditorGUILayout.Toggle("Export render meshes", m_exportRenderMeshes);
m_exportMaterials = EditorGUILayout.Toggle("Export materials", m_exportMaterials);
m_exportTextures = EditorGUILayout.Toggle("Export textures", m_exportTextures);
m_exportCollisionMeshes = EditorGUILayout.Toggle("Export collision meshes", m_exportCollisionMeshes);
m_exportPrefabs = EditorGUILayout.Toggle("Export prefabs", m_exportPrefabs);
GUILayout.Space(30);
if (GUILayout.Button("Export from world"))
this.Export(false);
if (GUILayout.Button("Export from selection"))
this.Export(true);
}
void ChangeFolder()
{
string newFolder = EditorUtility.SaveFolderPanel(
"Select folder where to export files",
m_selectedFolder,
"");
if (string.IsNullOrWhiteSpace(newFolder))
{
return;
}
newFolder = FileUtil.GetProjectRelativePath(newFolder);
if (string.IsNullOrWhiteSpace(newFolder))
{
EditorUtility.DisplayDialog("", "Folder must be inside project.", "Ok");
}
m_selectedFolder = newFolder;
}
void Export(bool fromSelection)
{
if (CoroutineManager.IsRunning(m_coroutineInfo))
return;
m_exportFromSelection = fromSelection;
m_coroutineInfo = CoroutineManager.Start(this.ExportCoroutine(), this.Cleanup, ex => this.Cleanup());
}
void Cleanup()
{
EditorUtility.ClearProgressBar();
}
IEnumerator ExportCoroutine()
{
yield return null;
m_numNewlyExportedAssets = 0;
m_numAlreadyExportedAssets = 0;
if (string.IsNullOrWhiteSpace(m_selectedFolder))
{
EditorUtility.DisplayDialog("", "Select a folder first.", "Ok");
yield break;
}
if (m_exportFromSelection)
{
if (Selection.transforms.Length == 0)
{
EditorUtility.DisplayDialog("", "No object selected.", "Ok");
yield break;
}
}
var cell = Cell.Instance;
if (null == cell && !m_exportFromSelection)
{
EditorUtility.DisplayDialog("", $"{nameof(Cell)} script not found in scene. Make sure that you started the game with the correct scene.", "Ok");
yield break;
}
EditorUtility.DisplayProgressBar("", "Gathering info...", 0f);
Transform[] objectsToExport = m_exportFromSelection
? Selection.transforms.Where(_ => _.GetComponent<MapObject>() != null).ToArray()
: cell.transform.GetFirstLevelChildren().ToArray();
int numObjectsActive = 0;
for (int i = 0; i < objectsToExport.Length; i++)
{
var child = objectsToExport[i];
if (child.gameObject.activeInHierarchy)
numObjectsActive++;
}
EditorUtility.ClearProgressBar();
if (0 == numObjectsActive)
{
EditorUtility.DisplayDialog("", "No suitable objects to export.", "Ok");
yield break;
}
if (!EditorUtility.DisplayDialog(
"",
$"There are {objectsToExport.Length} objects, with {numObjectsActive} active ones.\nProceed ?",
"Ok",
"Cancel"))
{
yield break;
}
if (EditorApplication.isPlaying)
EditorApplication.isPaused = true;
var stopwatch = Stopwatch.StartNew();
EditorUtility.DisplayProgressBar("", "Creating folders...", 0f);
this.CreateFolders();
EditorUtility.DisplayProgressBar("", "Creating assets...", 0f);
int numExported = 0;
for (int i = 0; i < objectsToExport.Length; i++)
{
var child = objectsToExport[i];
if (!child.gameObject.activeInHierarchy)
continue;
if (EditorUtility.DisplayCancelableProgressBar("", $"Creating assets ({numExported}/{numObjectsActive})... {child.name}", numExported / (float)numObjectsActive))
yield break;
this.ExportAssets(child.gameObject);
numExported++;
}
if (m_exportPrefabs)
{
EditorUtility.DisplayProgressBar("", "Creating prefabs...", 1f);
if (!m_exportFromSelection)
PrefabUtility.SaveAsPrefabAsset(cell.gameObject, $"{PrefabsPath}/ExportedWorld.prefab");
else
{
foreach (var obj in objectsToExport)
{
PrefabUtility.SaveAsPrefabAsset(obj.gameObject, $"{PrefabsPath}/{obj.gameObject.name}.prefab");
}
}
}
EditorUtility.DisplayProgressBar("", "Refreshing asset database...", 1f);
AssetDatabase.Refresh();
EditorUtility.ClearProgressBar();
string displayText = $"number of newly exported asssets {m_numNewlyExportedAssets}, number of already exported assets {m_numAlreadyExportedAssets}, time elapsed {stopwatch.Elapsed}";
UnityEngine.Debug.Log($"Exporting of assets finished, {displayText}");
EditorUtility.DisplayDialog("", $"Finished ! \r\n\r\n{displayText}", "Ok");
}
void CreateFolders()
{
if (!Directory.Exists(m_selectedFolder))
Directory.CreateDirectory(m_selectedFolder);
string[] folders = new string[]
{
ModelsPath,
MaterialsPath,
TexturesPath,
PrefabsPath,
CollisionModelsPath,
};
foreach (string folder in folders)
{
if (!Directory.Exists(folder))
Directory.CreateDirectory(folder);
}
}
public void ExportAssets(GameObject go)
{
string assetName = go.name;
if (m_exportRenderMeshes)
{
var meshFilters = go.GetComponentsInChildren<MeshFilter>();
for (int i = 0; i < meshFilters.Length; i++)
{
MeshFilter meshFilter = meshFilters[i];
string indexPath = meshFilters.Length == 1 ? "" : "-" + i;
meshFilter.sharedMesh = (Mesh)CreateAssetIfNotExists(meshFilter.sharedMesh, $"{ModelsPath}/{assetName}{indexPath}.asset");
}
}
var meshRenderers = go.GetComponentsInChildren<MeshRenderer>();
for (int i = 0; i < meshRenderers.Length; i++)
{
ExportMeshRenderer(go, meshRenderers[i], meshRenderers.Length == 1 ? (int?)null : i);
}
if (m_exportCollisionMeshes)
{
var meshColliders = go.GetComponentsInChildren<MeshCollider>();
for (int i = 0; i < meshColliders.Length; i++)
{
string indexPath = meshColliders.Length == 1 ? "" : "-" + i;
meshColliders[i].sharedMesh = (Mesh)CreateAssetIfNotExists(meshColliders[i].sharedMesh, $"{CollisionModelsPath}/{assetName}{indexPath}.asset");
}
}
}
public void ExportMeshRenderer(GameObject rootGo, MeshRenderer meshRenderer, int? index)
{
if (!m_exportTextures && !m_exportMaterials)
return;
string indexPath = index.HasValue ? "-" + index.Value : "";
string assetName = rootGo.name + indexPath;
var mats = meshRenderer.sharedMaterials.ToArray();
for (int i = 0; i < mats.Length; i++)
{
if (m_exportTextures)
{
var tex = mats[i].mainTexture;
if (tex != null && tex != Texture2D.whiteTexture) // sometimes materials will have white texture assigned, and Unity will crash if we attempt to create asset from it
mats[i].mainTexture = (Texture)CreateAssetIfNotExists(tex, $"{TexturesPath}/{assetName}-{i}.asset");
}
if (m_exportMaterials)
mats[i] = (Material)CreateAssetIfNotExists(mats[i], $"{MaterialsPath}/{assetName}-{i}.mat");
}
meshRenderer.sharedMaterials = mats;
}
private Object CreateAssetIfNotExists(Object asset, string path)
{
if (AssetDatabase.Contains(asset))
return asset;
if (File.Exists(Path.Combine(Directory.GetParent(Application.dataPath).FullName, path)))
{
m_numAlreadyExportedAssets++;
return AssetDatabase.LoadMainAssetAtPath(path);
}
AssetDatabase.CreateAsset(asset, path);
m_numNewlyExportedAssets++;
return asset;
}
}
}