7
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

NTTコムウェアAdvent Calendar 2022

Day 20

初めての自作Unityエディタ拡張

Last updated at Posted at 2022-12-19

この記事はNTTコムウェア Advent Calendar 2022 20日目の記事です。

本記事ではエディタ拡張を自作してみたいけど難しそうという人向けに、例題に沿って簡単なUnity拡張の作り方を説明します。

UI Builderというものを使いますので比較的新しいUnityが必要です。
2021以降からコア機能となっていて、それ以前(2019.4~)はpackage installが必要だそうです。

本記事作成の環境はUnity Versionは2022.1.5f1.3268です。

例題

あるモデルのMesh RendererのMaterialを、他3つのモデルに一括コピーする事を目的とします。
image.png

手順

  1. ウィンドウを作る
  2. ウィンドウの中身を作る
  3. 処理を書く
  4. 動作確認する

ウィンドウを作る

ウィンドウを作ってみましょう。
Assets直下に適当なフォルダを作り、そのフォルダの中にEditorというフォルダを作ります。
Editorフォルダ内で新しいC# Scriptを作成します。頭を大文字にしてまた適当な名前をつけましょう。
create_script.png
new_file.png

C# Scriptファイルを作ったらダブルクリックして開きましょう。エディタ関連付けを済ませてない人はこちらなどを参考にして関連付けしましょう。

UnityのC#エディタをVsCodeにして使う設定【Windows編】2022年8月

開いたら以下のような感じでコードを書きます。

Tomato.cs
using UnityEngine;
using UnityEditor;

public partial class Tomato : EditorWindow {

    [MenuItem("TOMATO/まとめてコピー")] // ヘッダメニュー名/ヘッダ以下のメニュー名
    private static void ShowWindow() {
        var window = GetWindow<Tomato>("UIElements");
        window.titleContent = new GUIContent("tomato"); // エディタ拡張ウィンドウのタイトル
        window.Show();
    }
}

エディタからUnityに戻るとメニューが増えています。クリックしてみましょう。
added_menu.png
empty_window.png

空の画面が表示されます。第一歩です!

ウィンドウの中身を作る

UI Builderで部品をぺたぺた貼る感じで画面を作れます。

ヘッダメニューのWindow -> UI Toolkit -> UI Builderを開きます。
UI_Builder_Menu.png

開いたらUI Builder画面内のLibrary右上メニューからEditor Extension Authoringにチェックを付けましょう。UI Builderはエディタ以外の画面を作るのがメインなのでエディタ用部品は最初非表示になっています。
Editor_Extension_Authoring.png

あとは文字を表示するためのラベルとモデルを受け取るためのObject Fieldをドラッグ&ドロップでぺたぺたしましょう。
window1.png

LabelのText、Object FieldのLabelを編集しましょう。
window2.png

多分出来たと思うので先程作ったEditor内に保存しましょう。UI Builder内のViewportのFileメニューからUxml(構造情報)を、StyleSheetsの+マークメニューからUss(見た目情報)を保存します。名前は適当につけてください。
save.png

utml_uss.png

次はUxmlとUssを先程のウィンドウに関連付けしましょう。先程のc# Scriptを開いて追記しましょう。using UnityEngine.UIElements;が追加されているのに気をつけて下さい。

Tomato.cs
using UnityEngine;
using UnityEditor;
using UnityEngine.UIElements; // 追加

public partial class Tomato : EditorWindow {

    [MenuItem("TOMATO/まとめてコピー")]
    private static void ShowWindow() {
        var window = GetWindow<Tomato>("UIElements");
        window.titleContent = new GUIContent("tomato");
        window.Show();
    }

    // 以下追加
    [SerializeField] private VisualTreeAsset _rootVisualTreeAsset;
    [SerializeField] private StyleSheet _rootStyleSheet;

    private void CreateGUI() {
        _rootVisualTreeAsset.CloneTree(rootVisualElement);
        rootVisualElement.styleSheets.Add(_rootStyleSheet);
    }
}

Unityで先程のC# ScriptをクリックするとRoot Visual Tree AssetとRoot Style Sheetが設定できるようになっているのでUxmlとUssをドラッグ&ドロップして設定しましょう。
set_uxml_uss.png

もう一度メニューを選ぶと作った画面が開かれます。
window3.png

処理を書く

次は処理を書きましょう。
各フィールドやボタンを指定するための識別子が欲しいのでまたUI Builderで設定します。(ついでにボタン忘れてたのでボタンも追加しましょう)
Uxmlファイルを選択してInspectorにあるOpenボタンをクリックしてUxmlをUI Builderで開きましょう。
open_uxml.png

それぞれの部品を選択してInspectorからNameを設定しましょう。
set_name.png

また、オブジェクトを設定できるように全てのObject FieldのTypeにGame Objectを設定します。
set_type.png

次はつけたNameを使って処理を書いていきます。using UnityEditor.UIElements;が追加されているのに気をつけて下さい。

Tomato.cs
using UnityEngine;
using UnityEditor;
using UnityEngine.UIElements;
using UnityEditor.UIElements; // 追加

public partial class Tomato : EditorWindow {

    [MenuItem("TOMATO/まとめてコピー")]
    private static void ShowWindow() {
        var window = GetWindow<Tomato>("UIElements");
        window.titleContent = new GUIContent("tomato");
        window.Show();
    }

    [SerializeField] private VisualTreeAsset _rootVisualTreeAsset;
    [SerializeField] private StyleSheet _rootStyleSheet;

    private void CreateGUI() {
        _rootVisualTreeAsset.CloneTree(rootVisualElement);
        rootVisualElement.styleSheets.Add(_rootStyleSheet);

        // 以下追加
        var copyButton = rootVisualElement.Q<Button>("copy_btn");
        copyButton.clicked += () => {
            var moto1 = (GameObject)rootVisualElement.Q<ObjectField>("moto1_obj").value;
            var saki1 = (GameObject)rootVisualElement.Q<ObjectField>("saki1_obj").value;
            var saki2 = (GameObject)rootVisualElement.Q<ObjectField>("saki2_obj").value;
            var saki3 = (GameObject)rootVisualElement.Q<ObjectField>("saki3_obj").value;
            saki1.GetComponent<Renderer>().sharedMaterial = moto1.GetComponent<Renderer>().sharedMaterial;
            saki2.GetComponent<Renderer>().sharedMaterial = moto1.GetComponent<Renderer>().sharedMaterial;
            saki3.GetComponent<Renderer>().sharedMaterial = moto1.GetComponent<Renderer>().sharedMaterial;
        };
    }
}

rootVisualElement.Q<ObjectField>("付けたName")で部品を指定できます。それぞれの部品がどんなプロパティを持つのかはリファレンスを見ましょう。

Materialをコピーする処理はただ上書きしてるだけです。

動作確認する

多分出来たと思うので動かしてみましょう。

HierarchyでCubeを作成してCtrl + Dで複製して適当に移動させます。
create_cube.png
cubes.png

コピー元となるCubeに設定するMaterialを作成してドラッグ&ドロップでコピー元にセットします。
create_material.png
red_material.png
set_material.png

作ったツールを開いてコピー元、コピー先のCubeをドラッグ&ドロップでセットします。
test1.png

ボタンを押した時にコピー先3つにMaterialが反映されれば成功です。
test2.png

おわりに

お疲れ様でした!
思ったよりも簡単に、また少ないコードでエディタ拡張が作れたのではないでしょうか。Unity作業は地道な繰り返し作業が多かったりするので少し頑張って自分のための拡張を作ってみてはいかがでしょうか。

7
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?