2020-05-21
zelder
2020-05-25
21/05
2020

Сохранение мэша как отдельный ассет.


Ситуация
Необходимо часто сохранять мэш из MeshFilter в определенную папку, например, после запечки множества мешей в один.
Если не сохранить сам мэш, то его попросту не будет (в этот момент он только в памяти). И после перезапуска движка или при запуске игры он пропадет.

Решение
Добавим у MeshFilter в инспекторе кнопку для сохранения текущего контента в ассет по удобному пути.

Скрипт
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;
using UnityEditor.Experimental.SceneManagement; // убрать, если откажитесь от DetermineCurrentWorld()
public static class MeshSaverEditor
{
    // папка мира по-умолчанию
    public static string CurrentWorldPathName { get { return "Rattown"; } }
// формат пути
public static string CurrentWorldPathFormat { get { return "Assets/Resources/Contents/Worlds/{0}/Baked/"; } }

    private static Mesh SearchMesh(MeshFilter mf)
    {
        Mesh m = mf.sharedMesh;
        if (m != null) return m;
        var skr = mf.gameObject.GetComponent<SkinnedMeshRenderer>();
        if (skr == null) return null;
        return skr.sharedMesh;
    }
    [MenuItem("CONTEXT/MeshFilter/Save Mesh...")]
    public static void SaveMeshInPlace(MenuCommand menuCommand)
    {
        MeshFilter mf = menuCommand.context as MeshFilter;
        Mesh m = SearchMesh(mf);
        SaveMesh(m, m.name, false, true);
    }
    [MenuItem("CONTEXT/MeshFilter/Save Mesh As New Instance...")]
    public static void SaveMeshNewInstanceItem(MenuCommand menuCommand)
    {
        MeshFilter mf = menuCommand.context as MeshFilter;
        Mesh m = SearchMesh(mf);
        SaveMesh(m, m.name, true, true);
    }
    /// <summary>
    /// Сохранение меша в папку.
    /// </summary>
    /// <param name="mesh"></param>
    /// <param name="name"></param>
    /// <param name="makeNewInstance"></param>
    /// <param name="optimizeMesh"></param>
    /// <param name="startPath"></param>
    public static void SaveMesh(Mesh mesh, string name, bool makeNewInstance, bool optimizeMesh, string startPath = "Assets/")
    {
        string path = EditorUtility.SaveFilePanel("Save Separate Mesh Asset", startPath, name, "asset");
        if (string.IsNullOrEmpty(path)) return;
        path = FileUtil.GetProjectRelativePath(path);
        Mesh meshToSave = (makeNewInstance) ? Object.Instantiate(mesh) as Mesh : mesh;
        if (optimizeMesh) MeshUtility.Optimize(meshToSave);
        AssetDatabase.CreateAsset(meshToSave, path);
        AssetDatabase.SaveAssets();
    }
    //
    // пресеты для этой игры (помогатор)
    //
           
    [MenuItem("CONTEXT/MeshFilter/Save Mesh to the World...")]
    public static void SaveMeshInPlaceToWorld_FORTHISGAME(MenuCommand menuCommand)
    {
        MeshFilter mf = menuCommand.context as MeshFilter;
        API_SaveMeshToWorld(mf, CurrentWorldPathFormat, CurrentWorldPathName);
    }
    public static void API_SaveMeshToWorld(MeshFilter mf, string worldPathFormat, string worldPathName)
    {
        Mesh m = SearchMesh(mf);
        var pathDir = worldPathFormat + worldPathName;
        if (!string.IsNullOrWhiteSpace(worldPathFormat) && worldPathFormat.Contains("{0}")) pathDir = string.Format(worldPathFormat, worldPathName);
        var fileName = mf.gameObject.name;
        SaveMesh(m, fileName, false, true, pathDir);
    }
}
[CustomEditor(typeof(MeshFilter))]
[CanEditMultipleObjects]
public class MeshFilterEditor : Editor
{
    private string _pathFormat = MeshSaverEditor.CurrentWorldPathFormat;
    private string _worldPath = MeshSaverEditor.CurrentWorldPathName;
    private bool _isWorldPathDetermined = false;
    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();
        // расширяем MeshFilter в инспекторе
        EditorGUILayout.Separator();
        EditorGUILayout.HelpBox("Личное для этой игры (расширение). Мир по-умолчанию можно поменять в 'Scripts/Common/Libs/MeshSaverEditor'", UnityEditor.MessageType.Info);
        // формат пути
        _pathFormat = EditorGUILayout.TextField(_pathFormat);
        // текущий Мир (директория)
        this.DetermineCurrentWorld();
        EditorGUILayout.BeginHorizontal();
        _worldPath = EditorGUILayout.TextField(_worldPath);
        // пресеты значений для этой игры // TODO: подсветка кнопок, на основе текущего значения
        if (GUILayout.Button("RT")) { _worldPath = MeshSaverEditor.CurrentWorldPathName; }
        if (GUILayout.Button("SN")) { _worldPath = "Snowland"; }
        if (GUILayout.Button("SA")) { _worldPath = "Sandland"; }
        if (GUILayout.Button("DK")) { _worldPath = "Dark"; }
        EditorGUILayout.EndHorizontal();
        // save
        if (GUILayout.Button("Сохранить Mesh в папку мира"))
        {
            MeshSaverEditor.API_SaveMeshToWorld((MeshFilter)target, _pathFormat, _worldPath);
        }
    }
    // устанавливает текущее название Мира (директории)
    // специфичное для этой игры: на основе текущего префаба в редакторе - берем его название, это и есть название папки
    private void DetermineCurrentWorld()
    {
        if (_isWorldPathDetermined) return;
        _isWorldPathDetermined = true;
        // NOTE: реализация через PrefabStageUtility - экспериментальная функция
        // NOTE: есть альтернатива: StageUtility.GetStage (не экспериментальная), но для Unity 2020.1+
        var prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
        if (prefabStage != null && prefabStage.scene != null) _worldPath = prefabStage.scene.name;
    }
}
#endif