2022-01-16 02:52:59 +00:00
|
|
|
|
using SanAndreasUnity.Behaviours.World;
|
2022-01-16 04:00:29 +00:00
|
|
|
|
using SanAndreasUnity.Utilities;
|
2022-01-16 02:52:59 +00:00
|
|
|
|
using System.Collections;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.IO;
|
2022-01-16 04:00:29 +00:00
|
|
|
|
using System.Linq;
|
2022-01-16 02:52:59 +00:00
|
|
|
|
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;
|
|
|
|
|
|
2022-01-16 04:00:29 +00:00
|
|
|
|
private bool m_exportFromSelection = false;
|
|
|
|
|
|
2022-01-16 16:31:57 +00:00
|
|
|
|
private bool m_exportCollisionMeshes = true;
|
|
|
|
|
|
2022-01-16 02:52:59 +00:00
|
|
|
|
|
|
|
|
|
[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);
|
|
|
|
|
|
2022-01-16 17:06:25 +00:00
|
|
|
|
GUILayout.Space(30);
|
2022-01-16 02:52:59 +00:00
|
|
|
|
|
2022-01-16 16:22:53 +00:00
|
|
|
|
GUILayout.BeginHorizontal();
|
|
|
|
|
EditorGUILayout.LabelField("Folder: " + m_selectedFolder);
|
|
|
|
|
if (GUILayout.Button("Change"))
|
|
|
|
|
this.ChangeFolder();
|
|
|
|
|
GUILayout.FlexibleSpace();
|
|
|
|
|
GUILayout.EndHorizontal();
|
|
|
|
|
|
2022-01-16 16:31:57 +00:00
|
|
|
|
m_exportCollisionMeshes = EditorGUILayout.Toggle("Export collision meshes", m_exportCollisionMeshes);
|
|
|
|
|
|
2022-01-16 17:06:25 +00:00
|
|
|
|
GUILayout.Space(30);
|
2022-01-16 16:22:53 +00:00
|
|
|
|
|
2022-01-16 04:00:29 +00:00
|
|
|
|
if (GUILayout.Button("Export from world"))
|
|
|
|
|
this.Export(false);
|
|
|
|
|
|
|
|
|
|
if (GUILayout.Button("Export from selection"))
|
|
|
|
|
this.Export(true);
|
2022-01-16 02:52:59 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-01-16 16:22:53 +00:00
|
|
|
|
void ChangeFolder()
|
|
|
|
|
{
|
2022-01-16 17:06:42 +00:00
|
|
|
|
string newFolder = EditorUtility.SaveFolderPanel(
|
2022-01-16 16:22:53 +00:00
|
|
|
|
"Select folder where to export files",
|
|
|
|
|
m_selectedFolder,
|
|
|
|
|
"");
|
2022-01-16 17:06:42 +00:00
|
|
|
|
if (string.IsNullOrWhiteSpace(newFolder))
|
2022-01-16 16:22:53 +00:00
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-16 17:06:42 +00:00
|
|
|
|
newFolder = FileUtil.GetProjectRelativePath(newFolder);
|
|
|
|
|
if (string.IsNullOrWhiteSpace(newFolder))
|
2022-01-16 16:22:53 +00:00
|
|
|
|
{
|
|
|
|
|
EditorUtility.DisplayDialog("", "Folder must be inside project.", "Ok");
|
|
|
|
|
}
|
2022-01-16 17:06:42 +00:00
|
|
|
|
|
|
|
|
|
m_selectedFolder = newFolder;
|
2022-01-16 16:22:53 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-01-16 04:00:29 +00:00
|
|
|
|
void Export(bool fromSelection)
|
2022-01-16 02:52:59 +00:00
|
|
|
|
{
|
|
|
|
|
if (this.IsCoroutineRunning(m_coroutineInfo))
|
|
|
|
|
return;
|
|
|
|
|
|
2022-01-16 04:00:29 +00:00
|
|
|
|
m_exportFromSelection = fromSelection;
|
|
|
|
|
|
2022-01-16 02:52:59 +00:00
|
|
|
|
m_coroutineInfo = this.StartCoroutine(this.ExportCoroutine(), this.Cleanup, ex => this.Cleanup());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Cleanup()
|
|
|
|
|
{
|
|
|
|
|
EditorUtility.ClearProgressBar();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
IEnumerator ExportCoroutine()
|
|
|
|
|
{
|
|
|
|
|
yield return null;
|
|
|
|
|
|
2022-01-16 16:22:53 +00:00
|
|
|
|
if (string.IsNullOrWhiteSpace(m_selectedFolder))
|
|
|
|
|
{
|
|
|
|
|
EditorUtility.DisplayDialog("", "Select a folder first.", "Ok");
|
|
|
|
|
yield break;
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-16 04:00:29 +00:00
|
|
|
|
if (m_exportFromSelection)
|
|
|
|
|
{
|
|
|
|
|
if (Selection.transforms.Length == 0)
|
|
|
|
|
{
|
|
|
|
|
EditorUtility.DisplayDialog("", "No object selected.", "Ok");
|
|
|
|
|
yield break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-16 02:52:59 +00:00
|
|
|
|
var cell = Cell.Instance;
|
2022-01-16 04:00:29 +00:00
|
|
|
|
if (null == cell && !m_exportFromSelection)
|
2022-01-16 02:52:59 +00:00
|
|
|
|
{
|
|
|
|
|
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);
|
|
|
|
|
|
2022-01-16 04:00:29 +00:00
|
|
|
|
Transform[] objectsToExport = m_exportFromSelection
|
|
|
|
|
? Selection.transforms
|
|
|
|
|
: cell.transform.GetFirstLevelChildren().ToArray();
|
|
|
|
|
|
2022-01-16 02:52:59 +00:00
|
|
|
|
int numObjectsActive = 0;
|
2022-01-16 04:00:29 +00:00
|
|
|
|
for (int i = 0; i < objectsToExport.Length; i++)
|
2022-01-16 02:52:59 +00:00
|
|
|
|
{
|
2022-01-16 04:00:29 +00:00
|
|
|
|
var child = objectsToExport[i];
|
2022-01-16 02:52:59 +00:00
|
|
|
|
|
|
|
|
|
if (child.gameObject.activeInHierarchy)
|
|
|
|
|
numObjectsActive++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
EditorUtility.ClearProgressBar();
|
|
|
|
|
|
|
|
|
|
if (!EditorUtility.DisplayDialog(
|
|
|
|
|
"",
|
2022-01-16 04:00:29 +00:00
|
|
|
|
$"There are {objectsToExport.Length} objects, with {numObjectsActive} active ones.\nProceed ?",
|
2022-01-16 02:52:59 +00:00
|
|
|
|
"Ok",
|
|
|
|
|
"Cancel"))
|
|
|
|
|
{
|
|
|
|
|
yield break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (EditorApplication.isPlaying)
|
|
|
|
|
EditorApplication.isPaused = true;
|
|
|
|
|
|
|
|
|
|
EditorUtility.DisplayProgressBar("", "Creating folders...", 0f);
|
|
|
|
|
|
|
|
|
|
this.CreateFolders();
|
|
|
|
|
|
|
|
|
|
EditorUtility.DisplayProgressBar("", "Creating assets...", 0f);
|
|
|
|
|
|
|
|
|
|
int numExported = 0;
|
2022-01-16 04:00:29 +00:00
|
|
|
|
for (int i = 0; i < objectsToExport.Length; i++)
|
2022-01-16 02:52:59 +00:00
|
|
|
|
{
|
2022-01-16 04:00:29 +00:00
|
|
|
|
var child = objectsToExport[i];
|
2022-01-16 02:52:59 +00:00
|
|
|
|
|
|
|
|
|
if (!child.gameObject.activeInHierarchy)
|
|
|
|
|
continue;
|
|
|
|
|
|
2022-01-16 04:13:55 +00:00
|
|
|
|
if (EditorUtility.DisplayCancelableProgressBar("", $"Creating assets ({numExported}/{numObjectsActive})... {child.name}", numExported / (float)numObjectsActive))
|
2022-01-16 02:52:59 +00:00
|
|
|
|
yield break;
|
|
|
|
|
|
|
|
|
|
this.ExportAssets(child.gameObject);
|
|
|
|
|
|
|
|
|
|
numExported++;
|
2022-01-16 04:00:29 +00:00
|
|
|
|
|
|
|
|
|
yield return null;
|
2022-01-16 02:52:59 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
EditorUtility.DisplayProgressBar("", "Creating prefab...", 1f);
|
2022-01-16 04:00:29 +00:00
|
|
|
|
if (!m_exportFromSelection)
|
|
|
|
|
PrefabUtility.SaveAsPrefabAsset(cell.gameObject, $"{PrefabsPath}/{cell.gameObject.name}.prefab");
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
foreach (var obj in objectsToExport)
|
|
|
|
|
{
|
|
|
|
|
PrefabUtility.SaveAsPrefabAsset(obj.gameObject, $"{PrefabsPath}/{obj.gameObject.name}.prefab");
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-01-16 02:52:59 +00:00
|
|
|
|
|
|
|
|
|
EditorUtility.DisplayProgressBar("", "Refreshing asset database...", 1f);
|
|
|
|
|
AssetDatabase.Refresh();
|
|
|
|
|
|
|
|
|
|
EditorUtility.ClearProgressBar();
|
|
|
|
|
EditorUtility.DisplayDialog("", "Finished !", "Ok");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CreateFolders()
|
|
|
|
|
{
|
|
|
|
|
string[] folders = new string[]
|
|
|
|
|
{
|
|
|
|
|
"Models",
|
|
|
|
|
"Materials",
|
|
|
|
|
"Textures",
|
|
|
|
|
"Prefabs",
|
|
|
|
|
"CollisionModels",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
foreach (string folder in folders)
|
|
|
|
|
{
|
|
|
|
|
if (!AssetDatabase.IsValidFolder(Path.Combine(m_selectedFolder, folder)))
|
|
|
|
|
AssetDatabase.CreateFolder(m_selectedFolder, folder);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void ExportAssets(GameObject go)
|
|
|
|
|
{
|
|
|
|
|
string assetName = go.name;
|
|
|
|
|
|
|
|
|
|
var meshFilters = go.GetComponentsInChildren<MeshFilter>();
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < meshFilters.Length; i++)
|
|
|
|
|
{
|
|
|
|
|
MeshFilter meshFilter = meshFilters[i];
|
|
|
|
|
string indexPath = meshFilters.Length == 1 ? "" : "-" + i;
|
2022-01-16 16:14:28 +00:00
|
|
|
|
meshFilter.sharedMesh = (Mesh)CreateAssetIfNotExists(meshFilter.sharedMesh, $"{ModelsPath}/{assetName}{indexPath}.asset");
|
2022-01-16 02:52:59 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var meshRenderers = go.GetComponentsInChildren<MeshRenderer>();
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < meshRenderers.Length; i++)
|
|
|
|
|
{
|
|
|
|
|
ExportMeshRenderer(go, meshRenderers[i], meshRenderers.Length == 1 ? (int?)null : i);
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-16 16:31:57 +00:00
|
|
|
|
if (m_exportCollisionMeshes)
|
2022-01-16 02:52:59 +00:00
|
|
|
|
{
|
2022-01-16 16:31:57 +00:00
|
|
|
|
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");
|
|
|
|
|
}
|
2022-01-16 02:52:59 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//PrefabUtility.SaveAsPrefabAsset(go, $"{PrefabsPath}/{assetName}.prefab");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void ExportMeshRenderer(GameObject rootGo, MeshRenderer meshRenderer, int? index)
|
|
|
|
|
{
|
|
|
|
|
string indexPath = index.HasValue ? "-" + index.Value : "";
|
|
|
|
|
string assetName = rootGo.name + indexPath;
|
|
|
|
|
|
2022-01-16 16:14:28 +00:00
|
|
|
|
var mats = meshRenderer.sharedMaterials.ToArray();
|
2022-01-16 02:52:59 +00:00
|
|
|
|
|
|
|
|
|
for (int i = 0; i < mats.Length; i++)
|
|
|
|
|
{
|
2022-01-16 04:01:46 +00:00
|
|
|
|
var tex = mats[i].mainTexture;
|
|
|
|
|
if (tex != null)
|
2022-01-16 16:14:28 +00:00
|
|
|
|
mats[i].mainTexture = (Texture)CreateAssetIfNotExists(tex, $"{TexturesPath}/{assetName}-{i}.asset");
|
|
|
|
|
mats[i] = (Material)CreateAssetIfNotExists(mats[i], $"{MaterialsPath}/{assetName}-{i}.mat");
|
2022-01-16 02:52:59 +00:00
|
|
|
|
}
|
2022-01-16 16:14:28 +00:00
|
|
|
|
|
|
|
|
|
meshRenderer.sharedMaterials = mats;
|
2022-01-16 02:52:59 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-01-16 16:14:28 +00:00
|
|
|
|
private static Object CreateAssetIfNotExists(Object asset, string path)
|
2022-01-16 02:52:59 +00:00
|
|
|
|
{
|
|
|
|
|
if (AssetDatabase.Contains(asset))
|
2022-01-16 16:14:28 +00:00
|
|
|
|
return asset;
|
2022-01-16 02:52:59 +00:00
|
|
|
|
|
2022-01-16 03:26:03 +00:00
|
|
|
|
if (File.Exists(Path.Combine(Application.dataPath + "/../", path)))
|
2022-01-16 16:14:28 +00:00
|
|
|
|
return AssetDatabase.LoadMainAssetAtPath(path);
|
2022-01-16 02:52:59 +00:00
|
|
|
|
|
2022-01-16 03:26:03 +00:00
|
|
|
|
AssetDatabase.CreateAsset(asset, path);
|
2022-01-16 16:14:28 +00:00
|
|
|
|
|
|
|
|
|
return AssetDatabase.LoadMainAssetAtPath(path);
|
2022-01-16 02:52:59 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|