[Unity]ヒエラルキー上で隣に複製&プレハブを維持して複製

2021年5月19日(更新: 2021年6月23日)

Unity で同じ階層構造のオブジェクトを複数作りたいときには「複製」(ショートカットキー:Command+D)が便利です。

この標準の複製では、増やしたオブジェクトが元のオブジェクトの隣ではなくヒエラルキーの一番下に作られます

Unity標準機能の複製では複製オブジェクトが一番下に移動する

この挙動は、シーンのヒエラルキーにオブジェクトが増えてくると少し不便に感じることがあります。

ヒエラルキーにおいて、複製したオブジェクトがその元の直下に複製されるようにしたかったので、そのためのEditorスクリプトを作ることにしました。

また、複製するオブジェクトの子要素にプレハブ(Prefab)を含んでいても、それをインスタンス化させずにプレハブのまま維持したかったので UnityEditor のクラス PrefabUtility を利用しています。

選択したオブジェクトをそのすぐ下に複製するスクリプト

以下のC#スクリプトをプロジェクトの Assets > Editor ディレクトリに保存してください。スクリプトの動作確認は Unity 2021.1.10f1 で行っています。

メニュー項目に MyTools > Duplicate を追加します。ショートカットキーは「Command+Shift+D」としています。

using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

public static class CustomDuplicator
{
    [MenuItem("MyTools/Duplicate #%d")]
    private static void DuplicateSelection()
    {
        var newSelection = new List<Object>();

        if (Selection.gameObjects.Length > 0)
        {
            // 選択した全要素を対象にする
            for (int i = 0; i < Selection.gameObjects.Length; i++)
            {
                GameObject targetObj = Selection.gameObjects[i];

                // すぐ下に複製するためにヒエラルキー上のインデックス番号を保持
                int siblingIndex = targetObj.transform.GetSiblingIndex();

                // コピーを作成
                var cloneObj = MyInstantiation(targetObj);
                cloneObj.name = targetObj.name + "(Clone)";
                cloneObj.transform.parent = targetObj.transform.parent;
                cloneObj.transform.position = targetObj.transform.position;
                cloneObj.transform.SetSiblingIndex(siblingIndex + 1);

                if (PrefabUtility.GetPrefabAssetType(targetObj) == PrefabAssetType.NotAPrefab)
                {
                    // コピーの子要素を空に(インスタンス化されたプレハブが入っているので)
                    int numOfChild = cloneObj.transform.childCount - 1;
                    for (int k = numOfChild; k >= 0; k--)
                    {
                        Object.DestroyImmediate(cloneObj.transform.GetChild(k).gameObject);
                    }

                    // 子要素を検索
                    for (int j = 0; j < targetObj.transform.childCount; j++)
                    {
                        GameObject original = targetObj.transform.GetChild(j).gameObject;
                        var newChildObj = MyInstantiation(original);
                        newChildObj.transform.parent = cloneObj.transform;
                        newChildObj.transform.position = original.transform.position;
                    }
                }

                // 「元に戻す」ができるように
                Undo.RegisterCreatedObjectUndo(cloneObj, "Duplicate");

                newSelection.Add(cloneObj);
            }
        }

        // 複製したオブジェクトを選択状態にする
        Selection.objects = newSelection.ToArray();
    }

    private static GameObject MyInstantiation(GameObject targetObj)
    {
        // プレハブの場合
        if (PrefabUtility.GetPrefabInstanceStatus(targetObj) == PrefabInstanceStatus.Connected)
        {
            // プレハブを取得して複製
            var prefab = PrefabUtility.GetCorrespondingObjectFromSource(targetObj);            
            var prefabCopy = (GameObject)PrefabUtility.InstantiatePrefab(prefab);

            // プレハブに施された変更を取得
            var modifications = PrefabUtility.GetPropertyModifications(targetObj);
            PrefabUtility.SetPropertyModifications(prefabCopy, modifications);
            return prefabCopy;
        }

        // プレハブでなければ普通に複製
        return Object.Instantiate(targetObj);
    }
}

実際の複製例です。オリジナルのすぐ下にオブジェクトが複製されます。オブジェクト単体でもプレハブを含むオブジェクトでも動作します。

ヒエラルキー上の隣にオブジェクトを複製する例

オブジェクト数が膨大になってきたプロジェクトで役に立つかもしれません。

以上、オブジェクトをヒエラルキー上でオリジナルの隣(直下)にプレハブを維持して複製するスクリプトでした。

コメントを残す

メールアドレスが公開されることはありません。