【Unity】【エディタ拡張】FBX内のMeshを複製して独立したAssetとして保存する

FBX内のMeshを複製して独立したAssetとして保存する方法です。

Unity2018.1.4

ソースコード

早速ですがソースコードです。

using UnityEngine;
using UnityEditor;
using System.Linq;

public class Example
{
    /// <summary>
    /// FBX内のメッシュを複製する
    /// </summary>
    private static void DuplicateFbxMesh(GameObject fbx)
    {
        var fbxPath     = AssetDatabase.GetAssetPath(fbx);
        if (!fbxPath.EndsWith(".fbx")) {
            throw new System.Exception("unsupported extension.");
        }
        var meshes      = AssetDatabase.LoadAllAssetsAtPath(fbxPath).OfType<Mesh>().ToArray();

        // 保存先のフォルダを作る
        var folderPath  = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(fbxPath), "Mesh");
        CreateFolder(folderPath);

        for (int i = 0; i < meshes.Count(); i++) {
            var mesh                = meshes[i];
            // FBX内のメッシュはアセット化できないので複製する
            var name                = mesh.name;
            mesh                    = GameObject.Instantiate(mesh);
            mesh.name               = name;
            var meshAssetPath       = System.IO.Path.Combine(folderPath, mesh.name + ".asset");
            CreateOrUpdate(mesh, meshAssetPath);
        }
    }

    /// <summary>
    /// 指定されたパスのフォルダを生成する
    /// </summary>
    private static void CreateFolder(string path)
    {
        var target          = "";
        var splitChars      = new char[]{ System.IO.Path.DirectorySeparatorChar, System.IO.Path.AltDirectorySeparatorChar };
        foreach (var dir in path.Split(splitChars)) {
            var parent      = target;
            target          = System.IO.Path.Combine(target, dir);
            if (!AssetDatabase.IsValidFolder(target)) {
                AssetDatabase.CreateFolder(parent, dir);
            }
        }
    }

    /// <summary>
    /// アセットがなければ新規作成し、すでにあれば更新する
    /// </summary>
    private static void CreateOrUpdate(Object newAsset, string assetPath)
    {
        var oldAsset = AssetDatabase.LoadAssetAtPath<Mesh>(assetPath);
        if (oldAsset == null) {
            AssetDatabase.CreateAsset(newAsset, assetPath);
        }
        else {
            EditorUtility.CopySerializedIfDifferent(newAsset, oldAsset);
            AssetDatabase.SaveAssets();
        }
    }

}

AssetImporterと組み合わせる

ついでにAssetImporterと組み合わせて、FBXをインポートした時に自動的にMeshが複製されるようにしてみます。
書いてるうちにあまりAssetImporterでやるべき処理じゃない気もしてきましたが一応・・

using UnityEngine;
using UnityEditor;
using System.Linq;
using System.IO;

public class ExampleProcessor : AssetPostprocessor
{
    private const string MESH_FOLDER_NAME        = "Mesh";
    
    private void OnPreprocessModel()
    {
        var modelImporter           = assetImporter as ModelImporter;
        // ModelImporterの設定はここで行う
    }

    private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
    {
        System.Func<string, bool> isTarget          = path => path.EndsWith(".fbx");
        var targetImportedAssetPaths                = importedAssets.Where(x => isTarget(x)).ToArray();
        var targetDeletedAssetPaths                 = deletedAssets.Where(x => isTarget(x)).ToArray();
        var targetMovedAssetPaths                   = movedAssets.Where(x => isTarget(x)).ToArray();
        var targetMovedFromAssetPaths               = movedFromAssetPaths.Where(x => isTarget(x)).ToArray();
        
        // インポートされたアセットのメッシュを作成する
        foreach (var assetPath in targetImportedAssetPaths) {
            DuplicateFbxMesh(AssetDatabase.LoadAssetAtPath<GameObject>(assetPath));
        }

        // 削除されたアセットのメッシュを削除する
        foreach (var assetPath in targetDeletedAssetPaths) {
            var directory           = Path.GetDirectoryName(assetPath);
            directory               = Path.Combine(directory, MESH_FOLDER_NAME);
            AssetDatabase.DeleteAsset(directory);
        }

        // 移動されたアセットのメッシュを移動する
        for (int i = 0; i < targetMovedAssetPaths.Length; i++) {
            var movedPath           = targetMovedAssetPaths[i];
            movedPath               = Path.GetDirectoryName(movedPath);
            movedPath               = Path.Combine(movedPath, MESH_FOLDER_NAME);
            var movedFromPath       = targetMovedFromAssetPaths[i];
            movedFromPath           = Path.GetDirectoryName(movedFromPath);
            movedFromPath           = Path.Combine(movedFromPath, MESH_FOLDER_NAME);
            AssetDatabase.MoveAsset(movedFromPath, movedPath);
        }
    }
    
    /// <summary>
    /// FBX内のメッシュを複製する
    /// </summary>
    private static void DuplicateFbxMesh(GameObject fbx)
    {
        var fbxPath     = AssetDatabase.GetAssetPath(fbx);
        if (!fbxPath.EndsWith(".fbx")) {
            throw new System.Exception("unsupported extension.");
        }
        var meshes      = AssetDatabase.LoadAllAssetsAtPath(fbxPath).OfType<Mesh>().ToArray();

        // 保存先のフォルダを作る
        var folderPath  = Path.Combine(Path.GetDirectoryName(fbxPath), MESH_FOLDER_NAME);
        CreateFolder(folderPath);

        for (int i = 0; i < meshes.Count(); i++) {
            var mesh                = meshes[i];
            EditorUtility.DisplayProgressBar("Duplicating Meshes", mesh.name + " of " + fbxPath, (float)i / meshes.Length);
            // FBX内のメッシュはアセット化できないので複製する
            var name                = mesh.name;
            mesh                    = GameObject.Instantiate(mesh);
            mesh.name               = name;
            var meshAssetPath       = Path.Combine(folderPath, mesh.name + ".asset");
            CreateOrUpdate(mesh, meshAssetPath);
        }
        EditorUtility.ClearProgressBar();
    }

    /// <summary>
    /// 指定されたパスのフォルダを生成する
    /// </summary>
    private static void CreateFolder(string path)
    {
        var target          = "";
        var splitChars      = new char[]{ Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar };
        foreach (var dir in path.Split(splitChars)) {
            var parent      = target;
            target          = Path.Combine(target, dir);
            if (!AssetDatabase.IsValidFolder(target)) {
                AssetDatabase.CreateFolder(parent, dir);
            }
        }
    }

    /// <summary>
    /// アセットがなければ新規作成し、すでにあれば更新する
    /// </summary>
    private static void CreateOrUpdate(Object newAsset, string assetPath)
    {
        var oldAsset = AssetDatabase.LoadAssetAtPath<Mesh>(assetPath);
        if (oldAsset == null) {
            AssetDatabase.CreateAsset(newAsset, assetPath);
        }
        else {
            EditorUtility.CopySerializedIfDifferent(newAsset, oldAsset);
            AssetDatabase.SaveAssets();
        }
    }

}