【Unity】エディタ拡張でオブジェクトの生成/破壊をUndoする

投稿者: | 2023-05-13

エディタウィンドウでオブジェクトのインスタンス化や破壊をした後に元に戻せるようにしてみました。

Undo(Ctrl + Z)を実装するには、Undo.RecordObjectメソッドの第一引数に変更予定のオブジェクトを渡します。すると、フレームの最後で変更が記録されます。

オブジェクトの生成や破壊を元に戻すには別のメソッドが使われます。

エディタウィンドウを作る

ボタンを押すとゲームオブジェクトを作成/破壊するエディタウィンドウを作りました。

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

public class UndoTest : EditorWindow
{
    // 作成したオブジェクトのリスト
    [SerializeField] List<GameObject> testObjs = new List<GameObject>();

    // 取り消し回数
    int undoCount;

    [MenuItem("Window/Test/UndoTest")]
    static void CreateWindow()
    {
        EditorWindow.GetWindow<UndoTest>();
    }

    private void OnEnable()
    {
        // Undo か Redo をしたときのデリゲートに登録
        Undo.undoRedoPerformed += OnUndo;
    }

    private void OnDisable()
    {
        // デリゲートを解除
        Undo.undoRedoPerformed -= OnUndo;
    }

    private void OnGUI()
    {
        // リストの要素数を表示
        EditorGUILayout.LabelField("作成 : " + testObjs.Count);

        // リストのすべてのゲームオブジェクトの名前を表示
        for(int i = 0; i < testObjs.Count; i++)
        {
            EditorGUILayout.LabelField(testObjs[i].name);
        }

        // 作成ボタンを押す
        if (GUILayout.Button("作成"))
        {
            // 新しいゲームオブジェクトを作成
            var go = new GameObject();

            // オブジェクト生成をUndo可能にする
            Undo.RegisterCreatedObjectUndo(go, "create " + go.name);

            // リストの状態を記録
            Undo.RecordObject(this, go.name);
            
            // リストに追加
            testObjs.Add(go);
        }

        // すべて削除ボタンを押す
        if(GUILayout.Button("すべて削除"))
        {
            if (testObjs.Count == 0) return;

            // リストの全要素に対して処理
            for(int i = 0; i < testObjs.Count; i++)
            {
                // オブジェクトを破棄して、復元可能にする
                Undo.DestroyObjectImmediate(testObjs[i]);
            }

            // リストの状態を記録
            Undo.RecordObject(this, "clear" + testObjs);

            // リストをクリア
            testObjs.Clear();
        }

        EditorGUILayout.LabelField("取り消し : " + undoCount);

    }

    // Undoをした時
    void OnUndo()
    {
        undoCount++;

        // ウィンドウを再描画する
        Repaint();
    }
}   

このエディタウィンドウを作るための静的メソッドにMenuItem属性を付けて、メインメニューから実行できるようにしています。

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

public class UndoTest : EditorWindow
{
    // 作成したオブジェクトのリスト
    [SerializeField] List<GameObject> testObjs = new List<GameObject>();

    // 取り消し回数
    int undoCount;

    [MenuItem("Window/Test/UndoTest")]
    static void CreateWindow()
    {
        EditorWindow.GetWindow<UndoTest>();
    }

フィールドの変更を元に戻せるようにするには、そのフィールドにpublicキーワードかSerializeField属性を付けます。

Undoをしたときに何かの処理をしたいときは、Undo.undoRedoPerformedにメソッドを登録します。

    private void OnEnable()
    {
        // Undo か Redo をしたときのデリゲートに登録
        Undo.undoRedoPerformed += OnUndo;
    }

    private void OnDisable()
    {
        // デリゲートを解除
        Undo.undoRedoPerformed -= OnUndo;
    }

ボタンなどをOnGUIメソッドに実装します。まず、作成したオブジェクトの数や名前と「作成」ボタンを表示します。ボタンを押すと、新しいゲームオブジェクトを作成して、それをUndo.RegisterCreatedObjectUndoメソッドの引数に渡しUndo操作を登録します。

    private void OnGUI()
    {
        // リストの要素数を表示
        EditorGUILayout.LabelField("作成 : " + testObjs.Count);

        // リストのすべてのゲームオブジェクトの名前を表示
        for(int i = 0; i < testObjs.Count; i++)
        {
            EditorGUILayout.LabelField(testObjs[i].name);
        }

        // 作成ボタンを押す
        if (GUILayout.Button("作成"))
        {
            // 新しいゲームオブジェクトを作成
            var go = new GameObject();

            // オブジェクト生成をUndo可能にする
            Undo.RegisterCreatedObjectUndo(go, "create " + go.name);

            // リストの状態を記録
            Undo.RecordObject(this, go.name);
            
            // リストに追加
            testObjs.Add(go);
        }

その後、作成したゲームオブジェクトをリストに追加する前にUndo.RecordObjectメソッドの引数に現在のインスタンスを渡しています。

次にリストの要素をすべて削除するためのボタンを表示します。

        if(GUILayout.Button("すべて削除"))
        {
            if (testObjs.Count == 0) return;

            // リストの全要素に対して処理
            for(int i = 0; i < testObjs.Count; i++)
            {
                // オブジェクトを破棄して、復元可能にする
                Undo.DestroyObjectImmediate(testObjs[i]);
            }

            // リストの状態を記録
            Undo.RecordObject(this, "clear" + testObjs);

            // リストをクリア
            testObjs.Clear();
        }

Undo.DestroyObjectImmediateメソッドで、引数に渡したオブジェクトを破壊しUndo操作で復元できるようにします。その後リストをクリアする前にUndo.RecordObjectメソッドを呼んでいます。

Undoをした時にカウントして、回数を最後に表示しています。

        EditorGUILayout.LabelField("取り消し : " + undoCount);

    }

    // Undoをした時
    void OnUndo()
    {
        undoCount++;

        // ウィンドウを再描画する
        Repaint();
    }
}   

これでオブジェクトの生成と破壊を元に戻せるようになりました。

コメントを残す

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