今さらだけどOverlaysを触ってみた

AvatarPosted by

この記事は GRIPHONE Advent Calendar 2022 18日目の記事になります。

こんにちは、Unityエンジニアの二間瀬です。

みなさんはUnity2021系でゲーム開発をしていますでしょうか。
執筆時現在において、 Unity2021 が LTS となってしばらく経ちますが、私はまだ本バージョンの新機能の一つであるOverlaysについて触れることが出来ていなかったので、この機会に触ってみたいと思います。

検証環境

Unity2021.3.10f1

Overlaysとは

Unity2020 以前ではエディタの左上に配置されていたりシーンビューの左右に散らばっていたボタン群を、全てシーンビュー内に配置できるようにしたものです。
また、Overlaysの名の通り、各ツールバーは上部のドックから切り離してシーンビュー内の好きな場所に展開することもでき、以前よりもアクセス性・操作性が向上しています。

これだけでも十分便利なのですが、Overlaysではここに独自のツールバーを追加することができます。
今回はこちらのカスタムOverlaysの機能を使い、2つのサンプルを作成して、使い勝手を検証してみようと思います。

なお、以降で想定する状況への対処として、今回の機能が適切かどうかについては疑問が残るところかとは思いますが、あくまで例として見ていただければ幸いです。

例1: 任意のシーンを開くボタン

ゲーム開発が進みシーン数が増えてくると、Projectで該当のシーンを探して移動するのが面倒になることがあるかもしれません。
ここでは、あるシーンを編集した後に、別の特定のシーン(例えばタイトルシーン)に移って作業したい状況を想定します。
その時にProjectウィンドウからではなく、ボタン一つでシーンを開けるようになると楽になりそうです。

ということで、以下の要件にしたがってカスタムのツールバーを作成してみます。

  • 開きたいシーンアセットを登録できる
  • ボタンを押すとそのシーンが開かれる

実装したコードは以下のようになります。
なお、見た目やエラーハンドリング等に関しては、今回の説明には冗長であると感じたため省略しています。予めご了承ください。

[EditorToolbarElement(id, typeof(SceneView))]
class GotoSceneButton : EditorToolbarButton
{
    public const string id = "ExampleToolbar/GotoScene";

    public GotoSceneButton()
    {
        text = "GotoScene";

        // SceneAssetをアタッチできるフィールドを追加
        var field = new ObjectField();
        field.objectType = typeof(Scene);
        Add(field);

        clicked += () =>
        {
            if (field.value == null)
                return;
            Debug.Log($"open scene: {field.value}");
            EditorSceneManager.OpenScene(AssetDatabase.GetAssetPath(field.value));
        };
    }
}

[Overlay(typeof(SceneView), "ExampleToolBar")]
public class EditorToolbarExample : ToolbarOverlay
{
    EditorToolbarExample() : base(
        GotoSceneButton.id
        )
    { }
}

このスクリプトをEditorフォルダに入れると、図1. のようにシーンビュー右上のメニューから「Overlays → ExampleToolbar」でツールバーを表示させて、任意のシーンを開くボタンが使用できるようになりました。

コードの説明をしていきます。
まず、ボタンを作るために、EditorToolbarButtonクラスを継承したGotoSceneButtonクラスを作成しています。
なお、EditorToolBarButtonはUI Toolkit(旧UI Elements)のVisualElementクラスを継承したものになっており、ツールバー全体もUI Toolkitの機能で構築されるものになっています。
そのため、Overlaysを活用するにはいくらかUI Toolkitも理解する必要がありますが、今回はUXMLやUSSを使用せずにC#のみで記述したので多少は理解しやすくなっているかと思います。

こちらのクラスのコンストラクタで、シーンをアタッチできるようにするためのフィールドと、ボタンをクリックしたときにそのシーンを開く処理を記述しています。
なお、今回使用したボタンの他にも様々な定義済みのコンポーネントが用意されていますので、公式のドキュメントの使用例を参考に適宜活用してください。

次に、ToolbarOverlayクラスを継承したクラスを作成しています。
こちらは先ほど作成したボタンの親となるツールバー本体を表すクラスになっており、コンストラクタで登録したいコンポーネントのidをベースクラスに渡しています。
引数はリストなので、複数のコンポーネントがある場合はその分だけidを渡しましょう。

また、これら2つのクラスにはそれぞれ属性が付いていますが、こちらに関しては本記事では詳しく解説はしません。ほとんどの場合同じように記述することが多いと思いますので、詳しく知りたい場合はこちらも 公式のドキュメント を参考にしていただければと思います。

Icon属性でアイコンを設定することなどもできるので、ドックに格納された際などに分かりやすくするために利用すると良いかもしれません。

例2: シーン内のオブジェクトをピン留めする

例1では、ToolbarOverlayクラスとEditorToolbarButtonクラスを利用して、比較的簡単にカスタムのツールバーを作成できることを確認できました。

しかし、もう少し複雑なことをしようとすると、これらのクラスでは実現が難しい場合がありそうです。

そこで次の例として、オブジェクトが深く階層化されたシーン内で、特定のオブジェクトをピン留めしてアクセスしやすくしたい、という状況を想定します。

要件としては次のようなものが考えられます。

  • 現在選択中のオブジェクトの情報を保存できる
  • 各オブジェクトに紐づけられたボタンを押すと、任意のタイミングでそれを選択状態にできる
  • ピン留めされたオブジェクトはリスト状に可視化されて、複数個登録・解除できる

これらを実現するために、今度はOverlayクラスを継承する形を取ってみようと思います。

なお、ToolbarOverlayクラスもOverlayクラスを継承しているため、こちらを使用しても問題ない場合もあります。

ToolbarOverlayクラスは ICreateHorizontalToolbarとICreateVerticalToolbar を実装しているため、図2. のようにツールバーを右クリックして呼び出せるメニュー上で、ツールバーの見た目をHorizontalとVerticalに切り替えることができるようになっています。

そのため、上部のドックに格納したい場合などはこちらを利用した方が楽だと思いますが、見た目が崩れるため切り替えしたくないといった場合は、Overlayクラスを継承してこれらの機能を切った方が良いと思います。

図2. ツールバーの右クリックメニュー

早速ですが、実装したコードは以下のようになります。
今回も冗長と思われるレイアウトや一部の要件等は実装していませんがご了承ください。

[Overlay(typeof(SceneView), "Pin Objects", true)]
public class PinObjectsOverlay : Overlay
{
    private readonly List<GameObject> _list = new();
    public override VisualElement CreatePanelContent()
    {
        var root = new VisualElement() { name = "PinObjects" };

        var button = new Button() { text = "Pin selected objects" };
        root.Add(button);
        
        root.Add(new Label() { text = "Pinned objects" });

        var scrollView = new ScrollView()
        {
            name = "Pinned Objects", 
            mode = ScrollViewMode.Vertical,
        };
        root.Add(scrollView);

        button.clicked += () =>
        {
            // 選択中のオブジェクトをリストに追加して、ボタンビューをスクロールビューに追加する
            foreach (var go in Selection.gameObjects)
            {
                var b = new Button
                {
                    name = go.name,
                    text = go.name,
                };
                b.clicked += () =>
                {
                    Debug.Log("select " + go.name);
                    Selection.objects = new Object[] { go };
                };
                scrollView.Add(b);
            }
        };
        
        return root;
    }
}

こちらを実行すると図3. のようになります。
なお、複雑なシーンのデモとして、UnityのUI Samplesというアセットをお借りしています。

図3. 作成したツールバーの操作(2)

Overlayクラスを継承したクラスでは、CreateCreatePanelContentメソッドをオーバーライドして、コンポーネントに関する記述を行います。
戻り値はVisualElementなので、rootオブジェクトとしてVisualElementを作成し、それを親にして各種コンポーネントを登録しています。
今回はScrollViewを使用して実装しました。

例1と比較してツールバークラスが肥大化していますが、かなり自由度高くレイアウトを構築できることが分かりました。
今回は完全にスクリプトのみで作成しましたが、こちらの記事で紹介されているように、UI Builderを使用するなどしてUI Toolkitの機能を活用し見た目をよりリッチにすることもできます。

おわりに

今回はUnity2021で追加された新機能であるOverlaysで、独自の機能を持ったツールバーを作成して使い勝手を調べてみました。

簡単な機能であれば短いコード量で実装することもでき、複雑な機能に関してはUIElementsによってエディタ拡張の要領で自由度高く構築ができるということで、非常に柔軟に作られているなと感じました。

今回紹介できなかった機能もまだありますので、公式のドキュメントなどを参考にぜひ試していただければと思います。