【Unity】【エディタ拡張】Rectクラスをシーンビューで視覚的に編集できるようにするエディタ拡張

f:id:halya_11:20180228195449g:plain

何かしら矩形の範囲を決めるとき、Rectクラスを使うことがあります。 ただこのRectクラス、インスペクタでは右下の座標と横幅、縦幅で指定することになり、レベルデザイン時など使いづらいケースも少なくありません。

f:id:halya_11:20180228195506p:plain

そこで、Rectクラスをシーンビューで視覚的に編集できるようにしてみました。

ソースコード

まずRectを持つクラスを作ります。

using UnityEngine;

public class VisuallyEditableRect: MonoBehaviour {
    
      [SerializeField]
    private Rect _rect = new Rect(Vector2.one * -0.5f, Vector2.one);
    public Rect Rect => _rect;
}

次にこのクラスのカスタムエディタを作ります。

using UnityEngine;
using UnityEditor;

[CustomEditor(typeof(VisuallyEditableRect))]
public class VisuallyEditableRectEditor : Editor {

    /// <summary>
    /// 編集用のアンカー位置情報
    /// </summary>
    private struct Anchors
    {
        public Vector2 downLeft;
        public Vector2 upRight;
        public Vector2 upLeft;
        public Vector2 downRight;

        public Rect ToRect()
        {
            return new Rect(downLeft, upRight - downLeft);
        }
    }

    private VisuallyEditableRect _target;
    private Anchors _anchors;

    private void OnSceneGUI()
    {
        serializedObject.Update();

        Handles.color = Color.white;
        DrawLine(_anchors);
        Handles.color = Handles.zAxisColor;
        _anchors = MoveAnchors(_anchors);

        serializedObject.FindProperty("_rect").rectValue = _anchors.ToRect();
        serializedObject.ApplyModifiedProperties();
    }

    /// <summary>
    /// アンカー同士をつなぐ線を描画
    /// </summary>
    private void DrawLine(Anchors anchors)
    {
        Handles.DrawAAPolyLine(anchors.upLeft, anchors.upRight, anchors.downRight, anchors.downLeft, anchors.upLeft);
    }

    /// <summary>
    /// 全アンカーの移動ハンドル
    /// </summary>
    private Anchors MoveAnchors(Anchors anchors)
    {
        anchors.downLeft = AnchorHandle(anchors.downLeft);
        anchors.upLeft.x = anchors.downLeft.x;
        anchors.downRight.y = anchors.downLeft.y;
        
        anchors.upRight = AnchorHandle(anchors.upRight);
        anchors.upLeft.y = anchors.upRight.y;
        anchors.downRight.x = anchors.upRight.x;
        
        anchors.upLeft = AnchorHandle(anchors.upLeft);
        anchors.downLeft.x = anchors.upLeft.x;
        anchors.upRight.y = anchors.upLeft.y;

        anchors.downRight = AnchorHandle(anchors.downRight);
        anchors.upRight.x = anchors.downRight.x;
        anchors.downLeft.y = anchors.downRight.y;

        return anchors;
    }
    
    /// <summary>
    /// アンカーの移動ハンドル
    /// </summary>
    public Vector3 AnchorHandle (Vector3 position) {
        
        var snap = Vector3.one;
        snap.x = EditorPrefs.GetFloat("MoveSnapX", 1.0f);
        snap.y = EditorPrefs.GetFloat("MoveSnapY", 1.0f);
        snap.z = EditorPrefs.GetFloat("MoveSnapZ", 1.0f);

        // FreeMove
        var handleSize = HandleUtility.GetHandleSize(position) * 0.1f;
        Handles.CapFunction RectangleHandleCap2D = (id, pos, rot, size, eventType) => {
            Handles.CubeHandleCap(id, pos, rot, size, eventType);
        };
        
        var movePoint = Handles.FreeMoveHandle(position, Quaternion.identity, handleSize, snap, RectangleHandleCap2D);
        // XY平面上の近傍点を新しい位置とする
        position = movePoint - Vector3.forward * Vector3.Dot(movePoint - position, Vector3.forward);

        return position;
    }

    private void OnEnable()
    {
        _target = target as VisuallyEditableRect;
        _anchors.downLeft = _target.Rect.min;
        _anchors.upRight = _target.Rect.max;
    }
}

先のMonoBehaviourを適当なオブジェクトにアタッチするとRectがシーン上で視覚的に編集できます。

f:id:halya_11:20180228195449g:plain

RectTransformを使うこともできる

細かくカスタムできなくていいのであれば、RectTransformを使ってもよさそうです。

using UnityEngine;

[RequireComponent(typeof(RectTransform)), ExecuteInEditMode]
public class CustomRectTransform : MonoBehaviour {

    private RectTransform _rectTransform;
    private Rect _rect = new Rect();

    /// <summary>
    /// シーンビューに描画されているのと同じ位置とサイズのRect
    /// </summary>
    public Rect Rect {
        get {
            _rect.center = (Vector2)_rectTransform.position + _rectTransform.rect.center;
            Vector2 size = _rectTransform.rect.size;
            size.x *= _rectTransform.lossyScale.x;
            size.y *= _rectTransform.lossyScale.y;
            _rect.size = size;
            return _rect;
        }
    }

    private void Awake()
    {
        _rectTransform = GetComponent<RectTransform>();
    }

    private void OnDrawGizmos()
    {
        Gizmos.color = new Color(0.0f, 1.0f, 0.0f, 0.5f);
        Gizmos.DrawCube(Rect.center, Rect.size);
    }
}

これをEmptyなGameObjectにアタッチして

f:id:halya_11:20180228195547g:plain

一つの矩形情報を得るためにGameObjectを一つ使わなければならないなどの制約はできてしまいますが、手軽です。