1. サイトトップ
  2. ブログ
  3. Unity
  4. 【Unity】エディタ拡張を使って効率的に開発を行おう!

【Unity】エディタ拡張を使って効率的に開発を行おう!

こんにちは、 情熱開発部プログラム2課の樋宮です
暑い日が続いていますね、屋内との寒暖差で夏バテにならないよう体調には気をつかっていきたいですね

さて、今回のブログではUnityのエディタ拡張機能について紹介します
様々な機能が実装可能ですので本ブログが参考になれば幸いです
Unityは2022.3.22f1を使用しています

エディタ拡張は何がうれしい?

まずエディタ拡張とは既存のエディタの機能だけではなく、C#スクリプトを書くことで独自の機能を追加していくことを指します
ではなぜエディタ拡張を行うのでしょうか

様々な機能の作成が可能ですが基本的には「開発効率が上がる」が最大の理由かと思います
例えば、「大量のAssetに対して一括で操作を行いたい」 「敵やアイテムのデータ作成を楽に行いたい」などなど
特に大量の操作が必要な場合はひとつひとつ設定していくとなると時間がかかりすぎてしまいます

そこで拡張機能として用意することでミスを防いだり、より短時間で実装できたりするようになります

準備

拡張機能の実装自体はUnityEditor側でAPIが用意されていますのでそちらを利用して進めていきます
しかし、エディタの拡張機能をビルドに含める必要はありませんのでAssets以下に「Editor」という名前でフォルダを作成します
その中にC#スクリプトを追加していくことでビルドに含めずに実装を進めることが可能です
特殊なフォルダー名について

あるいはエディタ拡張スクリプト内の処理を#if UNITY_EDITOR#endif で囲うことでもビルドから除外が可能です

実装可能な拡張機能について

エディタ拡張で行えることはたくさんありますがいくつか簡単に紹介します

▶EditorWindowの作成
拡張機能専用のウィンドウ表示してそこに値を出したり、操作対象を指定したりなどが可能です
複雑な機能はこちらを利用することになるかと思います

生成にはEditowWindowを継承したクラスを作成する必要があります
以下のコードはログに文字を出力する簡単なものになります
エディタタブへは[MenuItem]というAttributeを指定することで追加が可能になります

using UnityEditor;
using UnityEngine;

public class SampleEditorWindow : EditorWindow
{
    [MenuItem("Sample/Window")]
    // 静的にするのを忘れない
    private static void ShowWindow()
    {
        SampleEditorWindow window = GetWindow<SampleEditorWindow>();
    }

    private void OnGUI()
    {
        // 実際の機能はここに書く
    }
}

▶右クリックメニューへの処理の追加
エディタ上で右クリックすることで表示されるメニューの一覧に処理を追加することが可能です
選択中のAssetに対しての操作が主になると思います
基本的には単純な関数を登録することが多いかと思います

先程EditorWindowでも紹介した[MenuItem]で指定していたパスの先頭をGameObjectにすることで追加可能です

using UnityEditor;
using UnityEngine;

public class SampleRightClickMenu
{
    [MenuItem("GameObject/ShowName")]
    // 静的にするのを忘れない
    private static void ShowName()
    {
        // 名前をログに表示
        // Selection.activeObjectで選択中のGameObjectを取得できる
        Debug.Log(Selection.activeObject.name);
    }
}

上記以外にも以下のような指定が可能です
[MenuItem("Assets/Sample")]:ProjectViewのAssets以下で開くメニューに追加
[MenuItem("Component/Sample")]:AddComponentから開くメニューに追加

▶InspectorViewに表示する内容の追加
InspecterViewの表示内容を自由に変更可能です
特定の値を新規で表示したり、あるいは非表示にすることも可能です
Inspecterの利便性を高めることができます

using UnityEditor;
using UnityEngine;

// Transformを指定してカスタムする
[CustomEditor(typeof(Transform))]

public class SampleCustomInspector : Editor
{
    public override void OnInspectorGUI()
    {
        // 元々表示していたものを消さないようにbaseクラスの関数を呼んでおく
        base.OnInspectorGUI();

        // インスペクターに適当な文字を表示
        EditorGUILayout.LabelField("これはサンプルのメッセージです!");
    }
}

今回は上記の内、EditorWindowの機能を利用して簡単な拡張を行っていきたいと思います

実際に作ってみる

今回作るのはScene上にあるGameObjectの置き換えになります
実際だと敵キャラクターやアイテムの置き換えに利用できるかと思います
指定した文字列が含まれているGameObjectを置き換えるように実装してみようと思います
実際のコードが以下になります

using UnityEditor;
using UnityEngine;

public class GameObjectReplacer : EditorWindow
{
    private string searchString = "";
    private GameObject replacementObject;

    [MenuItem("Tools/GameObject Replacer")]
    public static void ShowWindow()
    {
        GetWindow<GameObjectReplacer>("GameObject Replacer");
    }

    private void OnGUI()
    {
        // 検索する名前を入力するUIの追加
        searchString = EditorGUILayout.TextField("Name", searchString);
        // ゲームオブジェクトを指定するUIの追加
        replacementObject = (GameObject)EditorGUILayout.ObjectField("Replacement Object", replacementObject, typeof(GameObject), true);

        // ボタンをしたタイミングで置き換え実行
        if (GUILayout.Button("Replace"))
        {
            ReplaceGameObjects();
        }
    }

    private void ReplaceGameObjects()
    {
        // 名前か置き換え先がnullならログに出して終了
        if (string.IsNullOrEmpty(searchString) || replacementObject == null)
        {
            Debug.LogWarning("検索名か置き換え先の指定が不適切です");
            return;
        }

        // アクティブなゲームオブジェクトをすべて取得
        GameObject[] allObjects = FindObjectsOfType<GameObject>();
        int replacedCount = 0;

        foreach (GameObject obj in allObjects)
        {
            // 完全一致ではなく含んでいるかで検索
            if (obj.name.Contains(searchString))
            {
                // Prefabとの関連を残すためにPrefabUtilityからInstantiateを呼ぶ
                GameObject newObject = (GameObject)PrefabUtility.InstantiatePrefab(replacementObject);

                // 位置や向き、親子関係をコピー
                newObject.transform.position = obj.transform.position;
                newObject.transform.rotation = obj.transform.rotation;
                newObject.transform.parent = obj.transform.parent;
                 
                // 元の不要なGameObjectの削除
                DestroyImmediate(obj);
                replacedCount++;
            }
        }

        Debug.Log($"Replaced {replacedCount} GameObjects.");
    }
}

EditorGUILayout.TextField()EditorGUILayout.ObjectField()
上記コードそれぞれでテキスト入力欄とオブジェクト指定欄を追加することができます
他にも様々なUIを追加することができます
公式リファレンスに一覧があるのでこちらを参考に機能の拡張もできるかと思います

動作確認

今回はEnemyCubeという名前のPrefabが大量に配置されているSceneを用意しました
このPrefabすべてをEnemySphereというPrefabに置換していこうと思います


まずは画面上部からEditorWindowを呼び出します
そこからCubeで名前指定を行い、置き換え先にEnemySphereを指定します

この状態でボタンを押して実行すると….

GameObjectが置き換わったのがわかるかと思います
これで急なアイテムや敵の配置変更にも対応がすぐできるようになりましたね!

まとめ

今回は名前指定で置き換えるような実装を行いましたが実際には選択したもののみを置き換えたい場合などもあると思います
処理やUIを追加することでさらに便利なツールを作成することが可能です
また、開発だけでなくデバッグ機能としても利用することができるかと思います
しかし、便利な反面データの書き換えが簡単に行えてしまえたりInspectorViewを利用する際には本来見えていたものを非表示にできたりなど管理に気をつけなくてはいけない場面も数多くあります
そういった面と上手に付き合って便利な機能を作成していきましょう!

参考

Unity – エディタ拡張

Unity – EditorGUILayout

エディター拡張チートシート


【免責事項】

本サイトでの情報を利用することによる損害等に対し、
株式会社ロジカルビートは一切の責任を負いません。