【Unity】【エディタ拡張】FBXからのPrefab生成・更新を自動化する

FBXからPrefabを新規作成、更新するためのスクリプトです。
FBXを更新すると関連するPrefabのコンポーネントをいちいち貼りなおさないといけなかったりするので、それを簡単にするためのツールです。

Unity2018.2

ソースコード

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

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.IO;
using UnityEditorInternal;
using System.Linq;

public class FbxPrefabCreator 
{
    [MenuItem("Assets/Create/Prefab From Selected Fbx")]
    private static void CreatePrefabFromFbx()
    {
        var targetFbx   = Selection.activeGameObject;
        CreatePrefabFromFbx(targetFbx);
    }

    private static void CreatePrefabFromFbx(GameObject sourceFbx)
    {
        var sourcePath      = AssetDatabase.GetAssetPath(sourceFbx);
        var savePath        = Path.GetDirectoryName(sourcePath);
        var extension       = "prefab";
        
        // FBXじゃなかったら処理しない
        if (!Path.HasExtension(sourcePath) || Path.GetExtension(sourcePath) != ".fbx") {
            throw new System.Exception("Source is not fbx.");
        }

        // ファイル保存パネルを表示
        var defaultFileName = Path.GetFileNameWithoutExtension(sourcePath) + "_" + extension;
        savePath            = EditorUtility.SaveFilePanelInProject("Save Asset", defaultFileName, extension, "", savePath);

        // 保存パスが不正だったら処理しない
        if (string.IsNullOrEmpty(savePath)) {
            throw new System.Exception("Path to save is invalid.");
        }

        // Prefabのインスタンスを作成
        var tmpInstance     = GameObject.Instantiate(sourceFbx);

        // インスタンスを編集したければここで行う
        {
        }
        
        var targetPrefab    = AssetDatabase.LoadAssetAtPath<GameObject>(savePath);
        if (targetPrefab == null) {
            // Prefab未生成だったら新規作成
            PrefabUtility.CreatePrefab(savePath, tmpInstance);
        }
        else {
            // Prefab生成済みだった場合は生成済みのPrefabからコンポーネントをコピペして上書き
            foreach (var oldObject in targetPrefab.GetComponentsInChildren<Transform>(true)) {
                // 同じ名前のGameObjectを同一のものとみなす
                var newObject       = tmpInstance.transform.Find(oldObject.name);
                // ルートオブジェクトの場合だけは名前関係なく同一とみなす
                if (oldObject == targetPrefab.transform.root) {
                    newObject       = tmpInstance.transform.root;
                }

                if (newObject == null) {
                    continue;
                }

                // 既存のオブジェクトのコンポーネントのうち、新しいオブジェクトに無いものをコピペする
                var components      = oldObject.GetComponents<Component>();
                var componentTypes  = components.Select(x => x.GetType()).Distinct();
                foreach (var componentType in componentTypes) {
                    // 同じ種類のコンポーネントがアタッチされていなかったら全てアタッチする
                    if (newObject.GetComponents(componentType).Length == 0) {
                        foreach (var component in components.Where(y => y.GetType() == componentType)) {
                            ComponentUtility.CopyComponent(component);
                            ComponentUtility.PasteComponentAsNew(newObject.gameObject);
                        }
                    }
                }
            }

            PrefabUtility.ReplacePrefab(tmpInstance, targetPrefab, ReplacePrefabOptions.ConnectToPrefab);
        }
        
        GameObject.DestroyImmediate(tmpInstance);
    }
}

Prefabを更新する際には同じ名前のGameObjectを同一のものとみなし、貼られているコンポーネントをコピーします。
大体のケースはこんな感じの実装で問題ないのではないかと思います。

使い方

Fbxを右クリック > Create > Prefab From Selected Fbxを選択することで使用します。

f:id:halya_11:20181018103127p:plain

関連

light11.hatenadiary.com