MRが楽しい

MRやVRについて学習したことを書き残す

UnityでInspcetorからゲームオブジェクトを作成したり削除したりする

本日は Unity の小ネタ枠です。
UnityでInspcetorからゲームオブジェクトを作成したり削除したりする方法について記事にします。

Inspectorビューのカスタマイズ

Inspectorビューに処理を行うボタンを追加する場合は CustomEditor 属性でクラスを拡張し、OnInspectorGUI 関数をオーバーライドします。
docs.unity3d.com
docs.unity3d.com

今回は以下のような処理用のクラスと拡張クラスを作成しました。

using UnityEngine;

#if UNITY_EDITOR
using UnityEditor;
using CUSTOMTYPE = ChildObjectMaker;

// 拡張するクラスを指定する
[CustomEditor(typeof(CUSTOMTYPE))]
public class ChildObjectMakerEditor : Editor
{
    // GUIの表示関数をオーバーライドする
    public override void OnInspectorGUI()
    {
        // 元のインスペクター部分を表示
        base.OnInspectorGUI();

        // targetを変換して対象スクリプトの参照を取得する
        CUSTOMTYPE targetScript = target as CUSTOMTYPE;

        // Editorを実行していないときのみ追加UIを表示する
        if (EditorApplication.isPlaying == false)
        {
            // public関数を実行するボタンの作成
            if (GUILayout.Button("ボタンコメント"))
            {
                targetScript.呼び出し関数();
            }
        }
    }
}
#endif
using System.Collections.Generic;
using UnityEngine;

public class ChildObjectMaker : MonoBehaviour
{
    public void 呼び出し関数()
    {
        // 実行処理
    }
}

オブジェクトの生成と削除

オブジェクトの生成を行うには new GameObject で生成可能です。
シーンからオブジェクトの削除を行うには Destroy または DestroyImmediate を利用します。
docs.unity3d.com
docs.unity3d.com

エディターが実行中でないときもオブジェクトを削除するには Destory 関数ではなく DestroyImmediate を利用する必要があります。
Destory 関数は実行から1フレーム後に削除が行われるため、実行中でない場合はオブジェクトの削除が行われません。

動作確認

以下の自身のオブジェクト配下に対して、子オブジェクトの生成と削除を行うサンプルコンポーネントを作成しました。
Destory と DestroyImmediate の動作の違いを確認するため、2種類の削除関数を用意しています。
・ChildObjectMakerEditor.cs

using UnityEngine;

#if UNITY_EDITOR
using UnityEditor;
using CUSTOMTYPE = ChildObjectMaker;

// 拡張するクラスを指定する
[CustomEditor(typeof(CUSTOMTYPE))]
public class ChildObjectMakerEditor : Editor
{
    // GUIの表示関数をオーバーライドする
    public override void OnInspectorGUI()
    {
        // 元のインスペクター部分を表示
        base.OnInspectorGUI();

        // targetを変換して対象スクリプトの参照を取得する
        CUSTOMTYPE targetScript = target as CUSTOMTYPE;

        // Editorを実行していないときのみ追加UIを表示する
        if (EditorApplication.isPlaying == false)
        {
            // public関数を実行するボタンの作成
            if (GUILayout.Button("CreateChildObjectsの実行"))
            {
                targetScript.CreateChildObjects();
            }
            
            // public関数を実行するボタンの作成
            if (GUILayout.Button("DestroyChildObjectsの実行"))
            {
                targetScript.DestroyChildObjects();
            }
            
            // public関数を実行するボタンの作成
            if (GUILayout.Button("DestroyImmediateChildObjectsの実行"))
            {
                targetScript.DestroyImmediateChildObjects();
            }
        }
    }
}
#endif

・ChildObjectMaker.cs

using UnityEngine;

public class ChildObjectMaker : MonoBehaviour
{
    public void CreateChildObjects()
    {
        // オブジェクト直下にある5個の子オブジェクトを作成
        for (int i = 0; i < 5; i++)
        {
            var childObject = new GameObject($"ChildObject{i}");
            childObject.transform.SetParent(transform);
        }
    }
    
    public void DestroyChildObjects()
    {
        // オブジェクト直下にある子オブジェクトを全て削除
        foreach (Transform child in transform)
        {
            Destroy(child.gameObject);
        }
    }
    
    public void DestroyImmediateChildObjects()
    {
        // オブジェクト直下にある子オブジェクトを全て削除
        var childObjects = new List<GameObject>();
        foreach (Transform child in transform)
        {
            // 直接削除すると参照がズレるので一旦別リストに退避する
            childObjects.Add(child.gameObject);
        }

        childObjects.ForEach(child => DestroyImmediate(child));
    }
}

オブジェクトの生成処理を実行してみます。
以下の通り、コンポーネントを設定したオブジェクトの子オブジェクトが5つ生成されました。

次に2つの削除処理を実行してみます。
以下の通り、エディターが実行中でないときは Destory は失敗し、DestroyImmediate は削除に成功します。