【Unity】カスタムエディタで選択したメッシュだけを描画する

投稿者: | 2021-07-19

カスタムエディタで複数のメッシュを描画して位置と回転をリストに入れるときに、インスペクタのトグルで選択した要素の値だけを、シーンビューのハンドルで変更できるようにして、それに合わせてメッシュを描画してみます。

スクリプトをつける

まず、Cubeにスクリプトを付けて、位置と回転のリストをインスペクタで操作できるようにしました。

using System.Collections.Generic;
using UnityEngine;

[System.Serializable]
public class PositionAndRotation
{
    public Vector3 position;
    public Quaternion rotation = Quaternion.identity;
}

public class PositionsAndRotations : MonoBehaviour
{
    public List<PositionAndRotation> instantiationInfo = new List<PositionAndRotation>();
  
    // Start is called before the first frame update
    void Start()
    {
        
    }

}

カスタムエディタを作る

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

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

[CustomEditor(typeof(PositionsAndRotations))]
public class PositionsAndRotationsEditor : Editor
{  
    Mesh mesh;
    Material material;

    Vector3 pos;
    Quaternion rot;

    PositionsAndRotations targetScript;
    List<PositionAndRotation> info;

    int index = -1;

    void OnEnable()
    {             
        // スクリプトを取得
        targetScript = target as PositionsAndRotations;

        // 位置と回転のリストを取得
        info = targetScript.instantiationInfo;

        // トグルをオフにする
        index = -1;

        // メッシュ・マテリアルを取得
        mesh = targetScript.GetComponent<MeshFilter>().sharedMesh;
        material = targetScript.GetComponent<MeshRenderer>().sharedMaterial;

    }


    // インスペクタ
    public override void OnInspectorGUI()
    {

        // リストを一巡
        for (int i = 0; i < info.Count; i++)
        {
            // EndHorizontal()で囲った要素を横に並べる
            EditorGUILayout.BeginHorizontal();

            // 要素のインデックス
            EditorGUILayout.LabelField(i + "");

            // トグルを表示
            if(EditorGUILayout.Toggle(index == i))
            {
                if(i == 0)Debug.Log("表示状態がオン " + (index == i));

                // オフのときにクリックしたらチェックを入れたままにする
                if(index != i)
                {                    
                    index = i;
                }
            }else
            {
                if (i == 0) Debug.Log("表示状態がオフ " + (index != i));


                // オンのときにクリックしたらチェックを外したままにする
                if (index == i)
                {
                    index = -1;
                }

            }
            EditorGUILayout.EndHorizontal();

            // 位置のフィールド
            EditorGUILayout.Vector3Field("Position",info[i].position);

            // 回転のフィールド
            EditorGUILayout.Vector3Field("Rotation",info[i].rotation.eulerAngles);
        }

        // EndHorizontal()までのボタンを横に並べる
        EditorGUILayout.BeginHorizontal();

        // Addボタンを表示
        if(GUILayout.Button("Add"))
        {
            // Addボタンが押されたときの処理

            // リストに新しい要素を追加
            info.Add(new PositionAndRotation());

        }
        // 要素数が0でないとき、Removeボタンを表示
        if (info.Count > 0 && GUILayout.Button("Remove"))
        {
            // Removeボタンが押されたときの処理

            // トグルをオフにする
            if (index >= info.Count - 1)
            {
                index = -1;
            }
            // 最後の要素を削除
            info.RemoveAt(info.Count - 1);

        }

        EditorGUILayout.EndHorizontal();
    }

    // シーンビュー
    public void OnSceneGUI()
    {
        // トグルのどれかにチェックがあるとき
        if (index >= 0)
        {
            // GUI要素を囲う
            EditorGUI.BeginChangeCheck();

            // 移動ツールのとき
            if (Tools.current == Tool.Move)
            {
                // ハンドルで位置を取得
                pos = Handles.PositionHandle(info[index].position, info[index].rotation);
                rot = info[index].rotation;
            }
            // 回転ツールのとき
            else if (Tools.current == Tool.Rotate)
            {

                // ハンドルで回転を取得
                rot = Handles.RotationHandle(info[index].rotation, info[index].position);
                pos = info[index].position;

            }

            // 囲われたGUI要素に変更があれば
            if (EditorGUI.EndChangeCheck())
            {
                // 以降の変更点を記録してアンドゥできるようにする
                Undo.RecordObject(target, "Move point");

                // 位置と回転を設定
                info[index].position = pos;
                info[index].rotation = rot;

            }

            // メッシュを描画
            Graphics.DrawMesh(mesh, pos, rot, material, 0);
        }

    }
}

前の記事からの変更点は、リストの中のシーンビューで値を操作する要素のインデックスを作って、トグルで値を切り替えられるようにしたことです。また、その要素だけをシーンビューで変更できるようにしました。

すべてのトグルをオフにしたいときは、インデックスを-1にすることにしたので、オブジェクトをロードしたときに-1を入れています。

int index = -1;

void OnEnable()
{             
    // スクリプトを取得
    targetScript = target as PositionsAndRotations;

    // 位置と回転のリストを取得
    info = targetScript.instantiationInfo;

    // トグルをオフにする
    index = -1;

    // メッシュ・マテリアルを取得
    mesh = targetScript.GetComponent<MeshFilter>().sharedMesh;
    material = targetScript.GetComponent<MeshRenderer>().sharedMaterial;

}

インスペクタでは、シーンビューに表示する要素のインデックスを変更するためのトグルを、ラベルの横に表示しています。横並びに表示するには、このラベルとトグルをEditorGUILayout.BeginHorizontalメソッドとEditorGUILayout.EndHorizontalメソッドで囲います。

// リストを一巡
for (int i = 0; i < info.Count; i++)
{
    // EndHorizontal()で囲った要素を横に並べる
    EditorGUILayout.BeginHorizontal();

    // 要素のインデックス
    EditorGUILayout.LabelField(i + "");

    // トグルを表示
    if(EditorGUILayout.Toggle(index == i))
    {
        // オフのときにクリックしたらチェックを入れたままにする
        if(index != i)
        {                    
            index = i;
        }
    }else
    {
        // オンのときにクリックしたらチェックを外したままにする
        if(index == i)
        {
            index = -1;
        }

    }
    EditorGUILayout.EndHorizontal();
    
    // ...

for文でリストの要素を一巡しているときに、今のインデックスがシーンビューに表示する要素のインデックスと同じであれば、EditorGUILayout.Toggleメソッドの引数がtrueになるので、チェックが入っている状態です。このときにクリックすると、引数がtrueでも戻り値はfalseになるようです。

戻り値で場合分けしてfalseのときに、引数がtrueなら、チェックが入っているトグルをクリックしたときなので、シーンビューに表示するインデックスを-1にしてチェックを切っています。

また、Removeボタンを押すとリストの最後の要素が削除されますが、その要素のためのトグルがオンだと、範囲外のリストを指定してエラーになってしまうので、インデックスを-1にします。

// 要素数が0でないとき、Removeボタンを表示
if (info.Count > 0 && GUILayout.Button("Remove"))
{
    // Removeボタンが押されたときの処理

    // トグルをオフにする
    if (index >= info.Count - 1)
    {
        index = -1;
    }
    // 最後の要素を削除
    info.RemoveAt(info.Count - 1);

}

OnSceneGUIメソッドでは、チェックが入っている要素だけをシーンビューに表示します。

// シーンビュー
public void OnSceneGUI()
{
    // トグルのどれかにチェックがあるとき
    if (index >= 0)
    {
        // GUI要素を囲う
        EditorGUI.BeginChangeCheck();

        // 移動ツールのとき
        if (Tools.current == Tool.Move)
        {
            // ハンドルで位置を取得
            pos = Handles.PositionHandle(info[index].position, info[index].rotation);
            rot = info[index].rotation;
        }
        // 回転ツールのとき
        else if (Tools.current == Tool.Rotate)
        {

            // ハンドルで回転を取得
            rot = Handles.RotationHandle(info[index].rotation, info[index].position);
            pos = info[index].position;

        }

        // 囲われたGUI要素に変更があれば
        if (EditorGUI.EndChangeCheck())
        {
            // 以降の変更点を記録してアンドゥできるようにする
            Undo.RecordObject(target, "Move point");

            // 位置と回転を設定
            info[index].position = pos;
            info[index].rotation = rot;

        }

        // メッシュを描画
        Graphics.DrawMesh(mesh, pos, rot, material, 0);
    }

}

シーンビューで値を変更する

カスタムエディタを作ると、Cubeを選択中に、インスペクタのこのスクリプトコンポーネントにAddボタンが表示されます。

ボタンをクリックすると、リストにはじめの要素が追加されます。

チェックを入れると、シーンビューではワールド空間の原点にCubeと同じメッシュとマテリアルが表示されます。

移動・回転ツールでハンドルを動かすと、インスペクタの値が変更されます。

トグルをもう一度クリックすると、メッシュが描画されなくなります。

Addボタンで要素を追加してトグルをオンにすると、その要素の値が変更されます。

他の要素のトグルをオンにすると、その要素だけがシーンビューに表示されます。

Cubeが選択されていないときは、メッシュは描画されません。

コメントを残す

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