【Unity】デフォルトのオブジェクト移動ハンドルを再現して特定軸の移動だけ有効にする

f:id:halya_11:20180214202037g:plain

以前同じようなものを作りましたが、オブジェクトを回転したときの移動に対応していなかったり操作感がいけてなかったので改修。

light11.hatenadiary.com

ソースコード

まずはSliderとFreeMoveHandleで作った独自Handleを生成するUtilityを作ります。

#if UNITY_EDITOR
using UnityEngine;
using UnityEditor;

public class AdvancedHandles {

    /// <summary>
    /// 指定した軸のFreeMoveハンドルを生成する
    /// </summary>
    public static Vector3 AxisMove (Vector3 position, Quaternion rotation, bool enableXAxis = true, bool enableYAxis = true, bool enableZAxis = true) {
        
        var rotationMatrix = Matrix4x4.TRS(Vector3.zero, rotation, Vector3.one);
        var dirX = rotationMatrix.MultiplyPoint(Vector3.right);
        var dirY = rotationMatrix.MultiplyPoint(Vector3.up);
        var dirZ = rotationMatrix.MultiplyPoint(Vector3.forward);

        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 handleCapPosOffset = Vector3.zero;
        var handleCapEuler = rotation.eulerAngles;
        var handleSize = HandleUtility.GetHandleSize(position) * 0.2f;
        Handles.CapFunction RectangleHandleCap2D = (id, pos, rot, size, eventType) => {
            Handles.RectangleHandleCap(id, pos + rotationMatrix.MultiplyPoint(handleCapPosOffset), rotation * Quaternion.Euler(handleCapEuler), size, eventType);
        };
        if (enableXAxis && enableYAxis) {
            Handles.color = Handles.zAxisColor;
            handleCapPosOffset = new Vector3(1.0f, 1.0f, 0.0f) * handleSize;
            handleCapEuler = Vector3.zero;
            var movePoint = Handles.FreeMoveHandle(position, rotation, handleSize, snap, RectangleHandleCap2D);
            // XY平面上の近傍点を新しい位置とする
            position = movePoint - dirZ * Vector3.Dot(movePoint - position, dirZ);
        }
        if (enableXAxis && enableZAxis) {
            Handles.color = Handles.yAxisColor;
            handleCapPosOffset = new Vector3(1.0f, 0.0f, 1.0f) * handleSize;
            handleCapEuler = new Vector3(90.0f, 0.0f, 0.0f);
            var movePoint = Handles.FreeMoveHandle(position, rotation, handleSize, snap, RectangleHandleCap2D);
            // XZ平面上の近傍点を新しい位置とする
            position = movePoint - dirY * Vector3.Dot(movePoint - position, dirY);
        }
        if (enableYAxis && enableZAxis) {
            Handles.color = Handles.xAxisColor;
            handleCapPosOffset = new Vector3(0.0f, 1.0f, 1.0f) * handleSize;
            handleCapEuler = new Vector3(0.0f, 90.0f, 0.0f);
            var movePoint = Handles.FreeMoveHandle(position, rotation, handleSize, snap, RectangleHandleCap2D);
            // YZ平面上の近傍点を新しい位置とする
            position = movePoint - dirX * Vector3.Dot(movePoint - position, dirX);
        }

        // Slider
        return AxisSlider(position, rotation, enableXAxis, enableYAxis, enableZAxis);
    }
    
    /// <summary>
    /// 指定した軸のSliderハンドルを生成する
    /// </summary>
    public static Vector3 AxisSlider (Vector3 position, Quaternion rotation, bool enableXAxis = true, bool enableYAxis = true, bool enableZAxis = true) {
        var rotationMatrix = Matrix4x4.TRS(Vector3.zero, rotation, Vector3.one);

        if (enableXAxis) {
            Handles.color = Handles.xAxisColor;
            position = Handles.Slider(position, rotationMatrix.MultiplyPoint(Vector3.right));
        }
        if (enableYAxis) {
            Handles.color = Handles.yAxisColor;
            position = Handles.Slider(position, rotationMatrix.MultiplyPoint(Vector3.up));
        }
        if (enableZAxis) {
            Handles.color = Handles.zAxisColor;
            position = Handles.Slider(position, rotationMatrix.MultiplyPoint(Vector3.forward));
        }
        return position;
    }
}
#endif

これをエディタスクリプトで制御します。

using UnityEngine;
using UnityEditor;

[CustomEditor (typeof(RestrictedPositionHandler))]
public class RestrictedPositionHandlerEditor : Editor
{
    private RestrictedPositionHandler _target;
    private Transform _transform;
    private Tool _previousTool;

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

        var isXAxisControllable = serializedObject.FindProperty("_isXAxisControllable").boolValue;
        var isYAxisControllable = serializedObject.FindProperty("_isYAxisControllable").boolValue;
        var isZAxisControllable = serializedObject.FindProperty("_isZAxisControllable").boolValue;
        
        Tools.current = Tool.None;

        // 位置を更新
        _transform.position = AdvancedHandles.AxisMove(_transform.position, _transform.rotation, isXAxisControllable, isYAxisControllable, isZAxisControllable);
    }

    private void OnEnable()
    {
        _target = target as RestrictedPositionHandler;
        _transform = _target.transform;
        _previousTool = Tools.current;
    }

    private void OnDisable()
    {
        Tools.current = _previousTool;
    }
}

MonoBehaviourはフィールドを持つだけです。

using UnityEngine;

public class RestrictedPositionHandler : MonoBehaviour
{
    [SerializeField, Tooltip("X軸を操作可能にするか")]
    private bool _isXAxisControllable = false;
    [SerializeField, Tooltip("Y軸を操作可能にするか")]
    private bool _isYAxisControllable = false;
    [SerializeField, Tooltip("Z軸を操作可能にするか")]
    private bool _isZAxisControllable = false;
}

使い方

使い方は前回の記事と同様、RestrictedPositionHandlerをアタッチして軸を選択するだけです。

f:id:halya_11:20180214202116p:plain

Undoを追加したいなー

関連記事

light11.hatenadiary.com