【Unity】シーンビューにメッシュを表示してハンドルで位置と回転を変更する

投稿者: | 2021-07-15

カスタムエディタで画面にメッシュを表示して、シーンビューのハンドルを使って位置や回転を変更できるようにしてみました。その値をスクリプトに設定して、アイテムをインスタンス化するときに使ってみます。

位置と回転を設定

まず、Cubeオブジェクトにスクリプトをつけて、位置と回転の値をインスペクタで設定できるようにし、スタート時にトランスフォームがその位置と回転になるようにしました。

using UnityEngine;

namespace Test1
{
    public class DisplayMesh : MonoBehaviour
    {
        public Vector3 location;
        public Quaternion rotation = Quaternion.identity;
      
        // Start is called before the first frame update
        void Start()
        {
            transform.position = location;
            transform.rotation = rotation;
        }

        // Update is called once per frame
        void Update()
        {

        }
    }
}

カスタムエディタを作る

そして、このスクリプトのインスペクタやシーンビューでの表示をカスタマイズするための、カスタムエディタを作ります。

using UnityEngine;
using UnityEditor;

namespace Test1
{

    [CustomEditor(typeof(DisplayMesh))]
    [CanEditMultipleObjects]
    public class DisplayMeshEditor : Editor
    {
        SerializedProperty location;
        SerializedProperty rotation;

        Mesh mesh; // 表示するメッシュ
        Material material; // マテリアル

        Vector3 pos;
        Quaternion rot;

        // トグルの状態
        bool showBtn = false;

        void OnEnable()
        {
            // SerializedPropertyを取得
            location = serializedObject.FindProperty("location");
            rotation = serializedObject.FindProperty("rotation");

            // プロパティの値を変数に入れる
            pos = location.vector3Value;
            rot = rotation.quaternionValue;

            // メッシュを取得
            mesh = (target as DisplayMesh).GetComponent<MeshFilter>().sharedMesh;

            // マテリアルを取得
            material = (target as DisplayMesh).GetComponent<MeshRenderer>().sharedMaterial;
        }

        public override void OnInspectorGUI()
        {
            // トグルを表示
            showBtn = EditorGUILayout.Toggle("メッシュを表示", showBtn);

            serializedObject.Update();

            // 位置を表示
            EditorGUILayout.PropertyField(location);

            // 回転を表示
            EditorGUILayout.PropertyField(rotation);

            serializedObject.ApplyModifiedProperties();
        }

        void OnSceneGUI()
        {

            // トグルがオンのとき
            if (showBtn)
            {
                // ターゲットのスクリプト
                var t = (target as DisplayMesh);

                EditorGUI.BeginChangeCheck();

                // 移動ツールのとき
                if (Tools.current == Tool.Move)
                {
                    
                    pos = Handles.PositionHandle(t.location, t.rotation);

                }
                // 回転ツールのとき
                else if (Tools.current == Tool.Rotate)
                {

                    rot = Handles.RotationHandle(t.rotation, t.location);

                }

                // ハンドルに変更があるとき
                if (EditorGUI.EndChangeCheck())
                {

                    Undo.RecordObject(target, "Move point");

                    // スクリプトの変数にセット
                    t.location = pos;
                    t.rotation = rot;

                }

                // メッシュを描画
                Graphics.DrawMesh(mesh, t.location, t.rotation, material, 0);



            }
        }

    }
}

EditorフォルダにC#スクリプトを作り、クラスにCustomEditor属性を付けて、Editorクラスを継承させます。CustomEditor属性で対象の型名を知らせます。

[CustomEditor(typeof(DisplayMesh))]
[CanEditMultipleObjects]
public class DisplayMeshEditor : Editor

オブジェクトがロードされたときに、そのクラスのプロパティ名からSerializedPropertyを取得し、その値を変数に入れておきます。また、メッシュフィルターとメッシュレンダラーコンポーネントからメッシュとマテリアルを取得しておきます。

SerializedProperty location;
SerializedProperty rotation;

Mesh mesh; // 表示するメッシュ
Material material; // マテリアル

Vector3 pos;
Quaternion rot;

// トグルの状態
bool showBtn = false;

void OnEnable()
{
    // SerializedPropertyを取得
    location = serializedObject.FindProperty("location");
    rotation = serializedObject.FindProperty("rotation");

    // プロパティの値を変数に入れる
    pos = location.vector3Value;
    rot = rotation.quaternionValue;

    // メッシュを取得
    mesh = (target as DisplayMesh).GetComponent<MeshFilter>().sharedMesh;

    // マテリアルを取得
    material = (target as DisplayMesh).GetComponent<MeshRenderer>().sharedMaterial;
}

インスペクタでは、まずトグルを表示します。このトグルがオンのときはメッシュが表示されるようにしました。そして、位置と回転のプロパティを入力欄で変更できるようにしています。

public override void OnInspectorGUI()
{
    // トグルを表示
    showBtn = EditorGUILayout.Toggle("メッシュを表示", showBtn);

    serializedObject.Update();

    // 位置を表示
    EditorGUILayout.PropertyField(location);

    // 回転を表示
    EditorGUILayout.PropertyField(rotation);

    serializedObject.ApplyModifiedProperties();
}

これでインスペクタの表示が変わりました。

シーンビューでの制御はOnSceneGUIメソッドに書きます。

void OnSceneGUI()
{

    // トグルがオンのとき
    if (showBtn)
    {
        // ターゲットのスクリプト
        var t = (target as DisplayMesh);

        EditorGUI.BeginChangeCheck();

        // 移動ツールのとき
        if (Tools.current == Tool.Move)
        {
                    
            pos = Handles.PositionHandle(t.location, t.rotation);

        }
        // 回転ツールのとき
        else if (Tools.current == Tool.Rotate)
        {

            rot = Handles.RotationHandle(t.rotation, t.location);

        }

        // ハンドルに変更があるとき
        if (EditorGUI.EndChangeCheck())
        {

            Undo.RecordObject(target, "Move point");

            // スクリプトの変数にセット
            t.location = pos;
            t.rotation = rot;

        }

        // メッシュを描画
        Graphics.DrawMesh(mesh, t.location, t.rotation, material, 0);



    }
}

インスペクタのトグルによってbool型の変数の値を切り替えて、それがオンのときはハンドルとメッシュを表示します。ツールの種類はTools.currentの値で判別できます。

// ターゲットのスクリプト
var t = (target as DisplayMesh);

EditorGUI.BeginChangeCheck();

// 移動ツールのとき
if (Tools.current == Tool.Move)
{
                    
    pos = Handles.PositionHandle(t.location, t.rotation);

}
// 回転ツールのとき
else if (Tools.current == Tool.Rotate)
{

    rot = Handles.RotationHandle(t.rotation, t.location);

}

まず、ターゲットのCubeに付けたスクリプトを取得して、そのフィールドの値を直接使っています。ハンドルの引数にそれらを渡しています。

変更を知りたいGUI要素をEditorGUI.BeginChangeCheckメソッドとEditorGUI.EndChangeCheckメソッドで囲っています。変更があればEditorGUI.EndChangeCheckメソッドがtrueになります。

// ハンドルに変更があるとき
if (EditorGUI.EndChangeCheck())
{

    Undo.RecordObject(target, "Move point");

    // スクリプトの変数にセット
    t.location = pos;
    t.rotation = rot;

}

このときに、Undo.RecordObjectメソッドを使って変更点を記録し、Ctrl + Zで操作をもとに戻せるようにしています。

呼び出された後の変更点を記録するので、ハンドルの位置や回転はいったん他の変数に入れておいて、このメソッド呼び出しの後にスクリプトにセットしています。

そして、その位置と回転の値を使ってメッシュを描画します。このときにメッシュとマテリアルも引数に渡します。

// メッシュを描画
Graphics.DrawMesh(mesh, t.location, t.rotation, material, 0);

ハンドルを使って値を設定

このCubeをシーンに配置しました。

インスペクタのチェックボックスをオンにすると、もう一つ同じメッシュが表示されました。

このメッシュを移動回転させると、インスペクタの値も変わります。

コメントを残す

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