ObjectFieldの作成(Editor拡張)

ObjectFieldの作成(Editor拡張)

 EditorGUILayout.ObjectFieldやEditorGUI.ObjectFieldはGUIStyleを設定できないため、これらのコントロールは見た目の変更ができません。そこで、GUIStyleを自由に変更できるObjectFieldを作成しました。

Script

 作成したscriptは以下の通りです。

ボタンなし

 ボタンがない場合のオブジェクトフィールドのScriptです。

public static T ObjectField<T>(string label, Object obj, GUIStyle style_label, GUIStyle style_obj, bool allow_scene_object)
    where T : Object
{
    Rect rect = GUILayoutUtility.GetRect(GUIContent.none, style_obj);
    T t_obj = obj as T;
    t_obj = CustomEditorGUI.ObjectField<T>(rect, label, t_obj, style_label, style_obj, allow_scene_object);

    return t_obj;
}

public static T ObjectField<T>(Rect position, string label, Object obj, GUIStyle style_label, GUIStyle style_obj, bool allow_scene_object)
    where T : Object
{
    Event evt = Event.current;
    EventType evt_type = Event.current.type;

    int margin = style_obj.margin.left;
    int label_width = 150;
    int obj_width = (int)position.width - label_width - margin;

    Rect rect_label = new Rect(position) { width = label_width };
    Rect rect_obj = new Rect(position) { x = position.width - obj_width, width = obj_width };
    int button_width = 16;
    Rect rect_button = new Rect(position) { x = position.width - button_width, width = button_width };

    GUIContent obj_content = EditorGUIUtility.ObjectContent(obj, typeof(T));

    int id_obj = GUIUtility.GetControlID(FocusType.Keyboard);
    int id_picker = GUIUtility.GetControlID(FocusType.Passive);

    if (evt.commandName == "ObjectSelectorUpdated" && id_picker == EditorGUIUtility.GetObjectPickerControlID())
    {
        obj = EditorGUIUtility.GetObjectPickerObject();
        HandleUtility.Repaint();
    }


    bool flg_focused = false;
    bool flg_on = false;
    if(GUIUtility.keyboardControl == id_obj)
    {
        flg_focused = true;
    }

    if(GUIUtility.hotControl == id_obj)
    {
        flg_on = true;
    }

    Vector2 mouse_pos = evt.mousePosition;
    switch (evt_type)
    {
        case EventType.DragUpdated:
        case EventType.DragPerform:
            if (rect_obj.Contains(mouse_pos))
            {
                if (DragAndDrop.objectReferences.Length == 1) DragAndDrop.AcceptDrag();
                if (DragAndDrop.objectReferences[0] is T) DragAndDrop.visualMode = DragAndDropVisualMode.Generic;
                DragAndDrop.activeControlID = id_obj;
                GUIUtility.hotControl = id_obj;
            }
            else if (GUIUtility.hotControl == id_obj)
            {
                GUIUtility.hotControl = 0;
            }
            break;

        case EventType.DragExited:
            if (rect_obj.Contains(mouse_pos))
            {
                T drag_obj = DragAndDrop.objectReferences[0] as T;
                if (drag_obj != null)
                {
                    obj = drag_obj;
                    HandleUtility.Repaint();
                }
            }
            GUIUtility.hotControl = 0;
            break;

        case EventType.MouseDown:
            if (evt.button != 0) break;
            if(rect_label.Contains(mouse_pos) || rect_obj.Contains(mouse_pos))
            {
                GUIUtility.keyboardControl = id_obj;
                HandleUtility.Repaint();
            }

            if (rect_button.Contains(mouse_pos))
            {
                EditorGUIUtility.ShowObjectPicker<T>(obj, allow_scene_object, "", id_picker);
            }
            else if(rect_obj.Contains(mouse_pos))
            {
                EditorGUIUtility.PingObject(obj);
            }
            break;

        case EventType.Repaint:
            style_label.Draw(rect_label, new GUIContent(label), false, false, false, flg_focused);
            style_obj.Draw(rect_obj, obj_content, false, false, flg_on, flg_focused);
            break;

    }

    return obj as T;
}

ボタンあり

 ボタンがある場合のオブジェクトフィールドのScriptです。

public static T ObjectField<T>(string label, Object obj, GUIStyle style_label, GUIStyle style_obj, GUIStyle style_obj_button, bool allow_scene_object)
    where T : Object
{
    Rect rect = GUILayoutUtility.GetRect(GUIContent.none, style_obj);
    T t_obj = obj as T;
    t_obj = CustomEditorGUI.ObjectField<T>(rect, label, t_obj, style_label, style_obj, style_obj_button, allow_scene_object);

    return t_obj;
}

public static T ObjectField<T>(Rect position, string label, Object obj, GUIStyle style_label, GUIStyle style_obj, GUIStyle style_obj_button, bool allow_scene_object)
    where T : Object
{
    if(style_obj_button == null)
    {
        obj = ObjectField<T>(position, label, obj, style_label, style_obj, allow_scene_object);
        return obj as T;
    }

    Event evt = Event.current;
    EventType evt_type = Event.current.type;

    int margin = style_obj.margin.left;
    int label_width = 150;
    int obj_width = (int)position.width - label_width - margin;

    Rect rect_label = new Rect(position) { width = label_width };
    Rect rect_obj = new Rect(position) { x = position.width - obj_width, width = obj_width };
    int obj_field_outline = 1;
    int button_width = (int)position.height;
    int button_height = (int)position.height - obj_field_outline * 2;
    Rect rect_button = new Rect(position) { x = position.width - button_width - obj_field_outline, y = position.y + obj_field_outline,  width = button_width, height = button_height };

    GUIContent obj_content = EditorGUIUtility.ObjectContent(obj, typeof(T));

    int id_obj = GUIUtility.GetControlID(FocusType.Keyboard);
    int id_picker = GUIUtility.GetControlID(FocusType.Passive);

    if (evt.commandName == "ObjectSelectorUpdated" && id_picker == EditorGUIUtility.GetObjectPickerControlID())
    {
        obj = EditorGUIUtility.GetObjectPickerObject();
        HandleUtility.Repaint();
    }

    Vector2 mouse_pos = evt.mousePosition;
    bool flg_focused = false;
    bool flg_on = false;
    bool flg_hover = false;
    bool flg_button_hover = false;
    if (GUIUtility.keyboardControl == id_obj) flg_focused = true;
    if (GUIUtility.hotControl == id_obj) flg_on = true;
    if (rect_obj.Contains(mouse_pos)) flg_hover = true;
    if (rect_button.Contains(mouse_pos)) flg_button_hover = true;

    switch (evt_type)
    {
        case EventType.DragUpdated:
        case EventType.DragPerform:
            if (rect_obj.Contains(mouse_pos))
            {
                if (DragAndDrop.objectReferences.Length == 1) DragAndDrop.AcceptDrag();
                if (DragAndDrop.objectReferences[0] is T) DragAndDrop.visualMode = DragAndDropVisualMode.Generic;
                DragAndDrop.activeControlID = id_obj;
                GUIUtility.hotControl = id_obj;
            }
            else if (GUIUtility.hotControl == id_obj)
            {
                GUIUtility.hotControl = 0;
            }
            break;

        case EventType.DragExited:
            if (rect_obj.Contains(mouse_pos))
            {
                T drag_obj = DragAndDrop.objectReferences[0] as T;
                if (drag_obj != null)
                {
                    obj = drag_obj;
                    HandleUtility.Repaint();
                }
                GUIUtility.hotControl = 0;
            }
            break;

        case EventType.MouseDown:
            if (evt.button != 0) break;
            if (rect_label.Contains(mouse_pos) || rect_obj.Contains(mouse_pos))
            {
                GUIUtility.keyboardControl = id_obj;
                HandleUtility.Repaint();
            }

            if (rect_button.Contains(mouse_pos))
            {
                EditorGUIUtility.ShowObjectPicker<T>(obj, allow_scene_object, "", id_picker);
            }
            else if(rect_obj.Contains(mouse_pos))
            {
                EditorGUIUtility.PingObject(obj);
            }
            break;


        case EventType.Repaint:
            style_label.Draw(rect_label, new GUIContent(label), false, false, false, flg_focused);
            style_obj.Draw(rect_obj, obj_content, flg_hover, false, flg_on, flg_focused);
            style_obj_button.Draw(rect_button, GUIContent.none, flg_button_hover, false, false, false);
            break;
    }

    return obj as T;
}

EditorGUIUtility.ObjectContentにより、オブジェクトの名前とそのオブジェクトの型に応じたアイコンを有するGUIContentを取得することができます。取得したGUIContentはObjectFIeldを表示する際に使用します。

GUIContent obj_content = EditorGUIUtility.ObjectContent(obj, typeof(T));

コントロール用のIDとオブジェクトピッカー用のIDを取得します。

int id_obj = GUIUtility.GetControlID(FocusType.Keyboard);
int id_picker = GUIUtility.GetControlID(FocusType.Passive);

ObjectSelectorUpdatedはオブジェクトピッカーで選択されたオブジェクトが変更された際に呼び出されます。また、EditorGUIUtility.GetObjectPickerControlIDにより開かれているオブジェクトピッカーのIDが取得できます。これらを用いてこのコントロールで開かれたオブジェクトピッカーで選択されたオブジェクトに変更があったかを判定することができます。これらの条件が満たされた場合にEditorGUIUtility.GetObjectPickerObjectによってオブジェクトピッカーで選択されたオブジェクトを取得します。

if (evt.commandName == "ObjectSelectorUpdated" && id_picker == EditorGUIUtility.GetObjectPickerControlID())
{
    obj = EditorGUIUtility.GetObjectPickerObject();
    HandleUtility.Repaint();
}

ドラック&ドロップ

ドラック&ドロップが操作されているときの処理です。

ドラック

 EventType.DragUpdatedはドラック&ドロップで操作が更新された時、EventType.DragPerformはドラック&ドロップの操作を実行したときに呼ばれます。ドラックに関する処理はこれらのイベントが発生しているときに行います。

case EventType.DragUpdated:
case EventType.DragPerform:
    if (rect_obj.Contains(mouse_pos))
    {
        //ドラックされているオブジェクトが一つの場合にドラックの操作を受け付ける
        if (DragAndDrop.objectReferences.Length == 1) DragAndDrop.AcceptDrag();

        //ドラックされているオブジェクトがT型へ変換できる場合
        //マウスカーソルの見た目を変更する。
        if (DragAndDrop.objectReferences[0] is T) DragAndDrop.visualMode = DragAndDropVisualMode.Generic;

        //ドラックされているオブジェクトとコントロールを紐づける
        DragAndDrop.activeControlID = id_obj;

        //コントロールの見た目を変更する際に使用
        GUIUtility.hotControl = id_obj;
    }
    else if (GUIUtility.hotControl == id_obj)
    {
        //コントロールからマウスカーソルが外れたら初期化
        GUIUtility.hotControl = 0;
    }
    break;

ドロップ

 ドロップが実行されるとEventType.DragExitedが呼ばれます。コントロール内へドロップが実行された時、コントロールへオブジェクトを格納します。

case EventType.DragExited:
    if (rect_obj.Contains(mouse_pos))
    {
        //ドラックされているオブジェクトを受け取る
        T drag_obj = DragAndDrop.objectReferences[0] as T;
        if (drag_obj != null)
        {
            //T型にキャストできる場合のみコントロールへオブジェクトを渡す
            obj = drag_obj;

            //GUIの再描画を行う
            HandleUtility.Repaint();
        }
        GUIUtility.hotControl = 0;
    }
    break;

マウスクリック

 左クリックされた際の処理です。ラベルやオブジェクトフィールドの見た目を変更、ボタンクリック時にオブジェクトピッカーを開く及びオブジェクトをProjectビュー内でハイライト表示を行います。

case EventType.MouseDown:
    //左クリックの場合のみ処理を実行
    if (evt.button != 0) break;
    if (rect_label.Contains(mouse_pos) || rect_obj.Contains(mouse_pos))
    {
        //コントロールの見た目変更用
        GUIUtility.keyboardControl = id_obj;
        HandleUtility.Repaint();
    }

    if (rect_button.Contains(mouse_pos))
    {
        //ボタンがクリックされた時、オブジェクトピッカーを開く
        EditorGUIUtility.ShowObjectPicker<T>(obj, allow_scene_object, "", id_picker);
    }
    else if(rect_obj.Contains(mouse_pos))
    {
        //コントロールがクリックされた際、
        //格納されているオブジェクトをProjectビュー内でハイライト表示
        EditorGUIUtility.PingObject(obj);
    }
    break;

コントロールの描画

 コントロールの描画はGUIStyle.Drawを用いて行います。GUIStyle.DrawはRepaint時にのみ実行します。

case EventType.Repaint:
    //ラベル
    style_label.Draw(rect_label, new GUIContent(label), false, false, false, flg_focused);

    //オブジェクト
    style_obj.Draw(rect_obj, obj_content, flg_hover, false, flg_on, flg_focused);

    //ボタン
    style_obj_button.Draw(rect_button, GUIContent.none, flg_button_hover, false, false, false);
    break;

実行結果

 作成したObjectFIeldを実行するScriptは以下の通りです。

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

public class EditorTest : EditorWindow
{
    private Material[] mat = new Material[3];

    [MenuItem("Tools/EditorTest")]
    public static void OpenWindow()
    {
        EditorWindow ed = EditorWindow.GetWindow(typeof(EditorTest), false, "EditorTest");
        ed.minSize = new Vector2(256, 60);
    }


    private void OnGUI()
    {
        GUIStyle style_label = new GUIStyle(GUI.skin.label);
        style_label.focused.textColor = Color.blue;
        GUISkin light_skin = (GUISkin)EditorGUIUtility.Load("LightSkin.guiskin");
        GUISkin dark_skin = (GUISkin)EditorGUIUtility.Load("DarkSkin.guiskin");

        GUIStyle style_light_obj = new GUIStyle(light_skin.FindStyle("ObjectField"));
        style_light_obj.onNormal.textColor = Color.black;
        style_light_obj.fixedHeight = 16;
        style_light_obj.normal.background = (Texture2D)EditorGUIUtility.Load("builtin skins/lightskin/images/dropwell nothumb.png");
        style_light_obj.focused.background = (Texture2D)EditorGUIUtility.Load("builtin skins/lightskin/images/dropwell nothumb focus.png");
        mat[0] = CustomEditorGUI.ObjectField<Material>("light skin object field", mat[0], style_label, style_light_obj, false);

        GUIStyle style_dark_obj = new GUIStyle(dark_skin.FindStyle("ObjectField"));
        style_dark_obj.onNormal.textColor = Color.black;
        style_dark_obj.fixedHeight = 16;
        style_dark_obj.normal.background = (Texture2D)EditorGUIUtility.Load("builtin skins/darkskin/images/dropwell nothumb.png");
        style_dark_obj.focused.background = (Texture2D)EditorGUIUtility.Load("builtin skins/darkskin/images/dropwell nothumb focus.png");
        mat[1] = CustomEditorGUI.ObjectField<Material>("dark skin object field", mat[1], style_label, style_dark_obj, null, false);

        //editor style
        GUIStyle style_editor_obj = new GUIStyle(EditorStyles.objectField);
        GUIStyle style_obj_button = new GUIStyle(dark_skin.FindStyle("ObjectFieldButton"));
        mat[2] = CustomEditorGUI.ObjectField<Material>("editor style object field", mat[2], style_label, style_editor_obj, style_obj_button, false);
    }
}

上記Scriptを実行すると以下のように動作します。オブジェクトピッカーやドラック&ドロップによりコントロールへオブジェクトを格納できていることが分かります。

参考サイト

エディター拡張入門:第19章 GUI を自作する