【Unity】カスタムエディタでシーンビューにバウンディングボックスを表示する

投稿者: | 2023-04-25

カスタムエディタを使って、シーンビューにバウンディングボックス(AABB)を表示し、ハンドルで位置やサイズを変更してみました。

スクリプタブルオブジェクト

バウンディングボックスはスクリプタブルオブジェクトが持っています。

using UnityEngine;

[CreateAssetMenu(fileName = "TestBounds", menuName = "ScriptableObjects/TestBounds")]
public class TestBounds : ScriptableObject
{
    public Bounds bounds = new Bounds(Vector3.zero, Vector3.one);
}

CreateAssetMenu属性を使って、メインメニューのAssets > Create からこのインスタンスを作成できるようにしています。

カスタムエディタを作る

このスクリプトのためのカスタムエディタを作ります。

using UnityEngine;
using UnityEditor;

[CustomEditor(typeof(TestBounds))]
public class TestBoundsEditor : Editor
{
    void OnEnable()
    {
        // シーンビューのイベントを購読
        SceneView.duringSceneGui += OnSceneGUI;
    }

    void OnDisable()
    {
        // シーンビューのイベント購読を解除
        SceneView.duringSceneGui -= OnSceneGUI;
    }

    void OnSceneGUI(SceneView sv)
    {
        // Boundsの値を取得
        var t = (target as TestBounds);
        var bounds = t.bounds;

        EditorGUI.BeginChangeCheck();

        // バウンディングボックスの中央にハンドルを表示
        bounds.center = Handles.PositionHandle(bounds.center, Quaternion.identity);

        // バウンディングボックスの右面の中央の位置
        var rightPos = bounds.center + new Vector3(bounds.size.x / 2f, 0f, 0f);

        // シーンビューのカメラからの距離
        var distance = Vector3.Distance(rightPos, SceneView.currentDrawingSceneView.camera.transform.position);

        // バウンディングボックスの右面の中央にハンドルを表示
        rightPos = Handles.FreeMoveHandle(rightPos, Quaternion.identity, 0.01f * distance, Vector3.one * 0.5f, Handles.RectangleHandleCap);

        // バウンディングボックスの左面
        var leftPos = bounds.center + new Vector3(-bounds.size.x / 2f, 0f, 0f);
        distance = Vector3.Distance(leftPos, SceneView.currentDrawingSceneView.camera.transform.position);
        leftPos = Handles.FreeMoveHandle(leftPos, Quaternion.identity, 0.01f * distance, Vector3.one * 0.5f, Handles.RectangleHandleCap);
        
        // バウンディングボックスの上面
        var upPos = bounds.center + new Vector3(0f, bounds.size.y / 2f, 0f);
        distance = Vector3.Distance(upPos, SceneView.currentDrawingSceneView.camera.transform.position);
        upPos = Handles.FreeMoveHandle(upPos, Quaternion.identity, 0.01f * distance, Vector3.one * 0.5f, Handles.RectangleHandleCap);

        // バウンディングボックスの下面
        var downPos = bounds.center + new Vector3(0f, -bounds.size.y / 2f, 0f);
        distance = Vector3.Distance(downPos, SceneView.currentDrawingSceneView.camera.transform.position);
        downPos = Handles.FreeMoveHandle(downPos, Quaternion.identity, 0.01f * distance, Vector3.one * 0.5f, Handles.RectangleHandleCap);

        // バウンディングボックスの表面
        var forwardPos = bounds.center + new Vector3(0f,  0f, bounds.size.z / 2f);
        distance = Vector3.Distance(forwardPos, SceneView.currentDrawingSceneView.camera.transform.position);
        forwardPos = Handles.FreeMoveHandle(forwardPos, Quaternion.identity, 0.01f * distance, Vector3.one * 0.5f, Handles.RectangleHandleCap);

        // バウンディングボックスの裏面
        var backPos = bounds.center + new Vector3(0f, 0f, -bounds.size.z / 2f);
        distance = Vector3.Distance(backPos, SceneView.currentDrawingSceneView.camera.transform.position);
        backPos = Handles.FreeMoveHandle(backPos, Quaternion.identity, 0.01f * distance, Vector3.one * 0.5f, Handles.RectangleHandleCap);

        // ハンドルに変更があったとき
        if (EditorGUI.EndChangeCheck())
        {           
            var size = bounds.size;

            // ボウンディングボックスのサイズを変更
            size.x = Mathf.Abs(rightPos.x - leftPos.x);
            size.y = Mathf.Abs(upPos.y - downPos.y);
            size.z = Mathf.Abs(forwardPos.z - backPos.z);

            bounds.size = size;
            t.bounds = bounds;

            // 保存対象にする
            EditorUtility.SetDirty(t);
        }

        // バウンディングボックスを描画
        Handles.DrawWireCube(bounds.center, bounds.size);
    }
}

オブジェクトが有効になるとOnEnableメソッドが呼ばれます。シーンビューにCubeやハンドルを描画するために、duringSceneGuiイベントを購読しています。

using UnityEngine;
using UnityEditor;

[CustomEditor(typeof(TestBounds))]
public class TestBoundsEditor : Editor
{
    void OnEnable()
    {
        // シーンビューのイベントを購読
        SceneView.duringSceneGui += OnSceneGUI;
    }

    void OnDisable()
    {
        // シーンビューのイベント購読を解除
        SceneView.duringSceneGui -= OnSceneGUI;
    }

OnDisableメソッドで購読を解除しています。

シーンビューでは、ターゲットのBounds型のフィールドを取得して、バウンディングボックスの中心にハンドルを表示しています。

    void OnSceneGUI(SceneView sv)
    {
        // Boundsの値を取得
        var t = (target as TestBounds);
        var bounds = t.bounds;

        EditorGUI.BeginChangeCheck();

        // バウンディングボックスの中央にハンドルを表示
        bounds.center = Handles.PositionHandle(bounds.center, Quaternion.identity);

サイズもハンドルで変更できるようにします。まずバウンディングボックスの右面の中央の位置を取得して、そこにハンドルを表示します。位置はバウンディングボックスの中心のX座標にサイズのX座標の半分を足したベクトルです。

        // バウンディングボックスの右面の中央の位置
        var rightPos = bounds.center + new Vector3(bounds.size.x / 2f, 0f, 0f);

        // シーンビューのカメラからの距離
        var distance = Vector3.Distance(rightPos, SceneView.currentDrawingSceneView.camera.transform.position);

        // バウンディングボックスの右面の中央にハンドルを表示
        rightPos = Handles.FreeMoveHandle(rightPos, Quaternion.identity, 0.01f * distance, Vector3.one * 0.5f, Handles.RectangleHandleCap);

それをHandles.FreeMoveHandleメソッドの第一引数に入れて、同じ変数で戻り値を受けるとハンドルを動かせます。

遠くからでもハンドルが見やすいように、ハンドルのサイズにシーンビューのカメラからの距離をかけています。第5引数で矩形を指定しています。

左面、上下面、表裏面にも同様にハンドルを表示しています。

 // バウンディングボックスの左面
        var leftPos = bounds.center + new Vector3(-bounds.size.x / 2f, 0f, 0f);
        distance = Vector3.Distance(leftPos, SceneView.currentDrawingSceneView.camera.transform.position);
        leftPos = Handles.FreeMoveHandle(leftPos, Quaternion.identity, 0.01f * distance, Vector3.one * 0.5f, Handles.RectangleHandleCap);
        
        // バウンディングボックスの上面
        var upPos = bounds.center + new Vector3(0f, bounds.size.y / 2f, 0f);
        distance = Vector3.Distance(upPos, SceneView.currentDrawingSceneView.camera.transform.position);
        upPos = Handles.FreeMoveHandle(upPos, Quaternion.identity, 0.01f * distance, Vector3.one * 0.5f, Handles.RectangleHandleCap);

        // バウンディングボックスの下面
        var downPos = bounds.center + new Vector3(0f, -bounds.size.y / 2f, 0f);
        distance = Vector3.Distance(downPos, SceneView.currentDrawingSceneView.camera.transform.position);
        downPos = Handles.FreeMoveHandle(downPos, Quaternion.identity, 0.01f * distance, Vector3.one * 0.5f, Handles.RectangleHandleCap);

        // バウンディングボックスの表面
        var forwardPos = bounds.center + new Vector3(0f,  0f, bounds.size.z / 2f);
        distance = Vector3.Distance(forwardPos, SceneView.currentDrawingSceneView.camera.transform.position);
        forwardPos = Handles.FreeMoveHandle(forwardPos, Quaternion.identity, 0.01f * distance, Vector3.one * 0.5f, Handles.RectangleHandleCap);

        // バウンディングボックスの裏面
        var backPos = bounds.center + new Vector3(0f, 0f, -bounds.size.z / 2f);
        distance = Vector3.Distance(backPos, SceneView.currentDrawingSceneView.camera.transform.position);
        backPos = Handles.FreeMoveHandle(backPos, Quaternion.identity, 0.01f * distance, Vector3.one * 0.5f, Handles.RectangleHandleCap);

ハンドルに変更があれば、ハンドルの位置からボウンディングボックスのサイズを変更します。例えば、サイズのX座標は、右面のX座標と左面のX座標の差にします。

        // ハンドルに変更があったとき
        if (EditorGUI.EndChangeCheck())
        {           
            var size = bounds.size;

            // ボウンディングボックスのサイズを変更
            size.x = Mathf.Abs(rightPos.x - leftPos.x);
            size.y = Mathf.Abs(upPos.y - downPos.y);
            size.z = Mathf.Abs(forwardPos.z - backPos.z);

            bounds.size = size;
            t.bounds = bounds;

            // 保存対象にする
            EditorUtility.SetDirty(t);
        }

EditorUtility.SetDirtyメソッドを使わないと保存されないようです。

最後にバウンディングボックスを描画します。

        Handles.DrawWireCube(bounds.center, bounds.size);
    }
}

これでシーンビューにバウンディングボックスを表示して、ハンドルで変更できました。

コメントを残す

メールアドレスが公開されることはありません。