【Unity】エディタ拡張でDecal Projectorを簡単に配置するツールを作る

投稿者: | 2024-04-17

Decal Projectorを簡単に配置するエディタウィンドウを作ってみました。

概要

シーンビューを左クリックして、コライダーの面にDecal Projectorのプレハブを配置します。配置した後にドラッグするとインスタンスを移動できます。

アスペクト比を保ったスケーリングやランダムの回転もできるようにします。

プレハブ

Decal Projectorゲームオブジェクトを作って、マテリアルを設定します。

テクスチャ画像のアスペクト比が保たれるように、Decal Projectorコンポーネントでサイズを設定しています。

プレハブ化します。

スクリプト

EditorフォルダにC#スクリプトを作成します。

using UnityEngine;
using UnityEngine.Rendering.HighDefinition;
using UnityEditor;

public class DecalProjectorPlacer : EditorWindow
{
    DecalProjector decalProjectorPrefab;
    GameObject currentPrefabInstance;
    Vector2 scaleMinMax = new Vector2(1f, 2f);
    float fixedRotationAngle;
    float randomRotationAngle;
    private bool placingPrefab = false;
    float distance;
    bool applyRandomRotation;

    [MenuItem("Window/Test/Decal Projector Placer")]
    public static void ShowWindow()
    {
        GetWindow<DecalProjectorPlacer>("Decal Projector Placer");
    }

    private void OnEnable()
    {
        SceneView.duringSceneGui += OnSceneGUI;
    }

    void OnDisable()
    {
        SceneView.duringSceneGui -= OnSceneGUI;
    }

    void OnGUI()
    {
        EditorGUILayout.BeginVertical();

        // 各フィールドを表示
        decalProjectorPrefab = (DecalProjector)EditorGUILayout.ObjectField("Prefab ", decalProjectorPrefab, typeof(DecalProjector), false);

        distance = EditorGUILayout.FloatField("Projection Distance", distance);
        scaleMinMax = EditorGUILayout.Vector2Field("Scale Range (Min to Max) ", scaleMinMax, null);
        applyRandomRotation = EditorGUILayout.Toggle("Apply Random Rotation ", applyRandomRotation);
        fixedRotationAngle = EditorGUILayout.FloatField("Fixed Rotation Angle ", fixedRotationAngle);
        
        // ボタンを表示
        if (GUILayout.Button(placingPrefab ? "Stop Placing Decal Projector" : "Start Placing Decal Projector"))
        {
            if (!placingPrefab)
            {
                placingPrefab = true;

            }
            else
            {
                placingPrefab = false;

                if (currentPrefabInstance != null)
                {

                    currentPrefabInstance = null;
                }
            }
        }

        EditorGUILayout.EndVertical();
    }

    void OnInspectorUpdate()
    {
        Repaint();
    }

    void OnSceneGUI(SceneView sceneView)
    {

        if (placingPrefab)
        {
            HandleUtility.AddDefaultControl(GUIUtility.GetControlID(FocusType.Passive));

            // 左クリックしたとき
            if (Event.current.type == EventType.MouseDown && Event.current.button == 0)
            {

                Ray ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition);
                RaycastHit hit;

                // レイを投げる
                if (Physics.Raycast(ray, out hit))
                {
                    // プレハブを配置
                    currentPrefabInstance = PrefabUtility.InstantiatePrefab(decalProjectorPrefab.gameObject) as GameObject;
                    Undo.RegisterCreatedObjectUndo(currentPrefabInstance, "create " + currentPrefabInstance.name);

                    if (currentPrefabInstance != null)
                    {
                        // 位置を設定
                        currentPrefabInstance.transform.position = hit.point + hit.normal * distance;

                        // 回転を設定
                        randomRotationAngle = Random.Range(0, 360f);
                        currentPrefabInstance.transform.rotation = CalculateDecalRotation(hit.normal);


                        var decalProjector = currentPrefabInstance.GetComponent<DecalProjector>();

                        // 元のサイズを取得
                        Vector3 originalSize = decalProjector.size;

                        // ランダムなスケールファクターを生成
                        float scaleFactor = Random.Range(scaleMinMax.x, scaleMinMax.y);

                        // 新しいサイズを設定(アスペクト比を保持)
                        decalProjector.size = new Vector3(originalSize.x * scaleFactor, originalSize.y * scaleFactor, originalSize.z);

                    }
                }

                Event.current.Use();

            }
            // ドラッグ
            else if (Event.current.type == EventType.MouseDrag && Event.current.button == 0)
            {

                Ray ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition);
                RaycastHit hit;

                // レイを投げる
                if (Physics.Raycast(ray, out hit))
                {
                    // 位置と回転を設定
                    currentPrefabInstance.transform.position = hit.point + hit.normal * distance;
                    currentPrefabInstance.transform.rotation = CalculateDecalRotation(hit.normal);
                }

                Event.current.Use();
            }

        }
    }

    Quaternion CalculateDecalRotation(Vector3 hitNormal)
    {

        // デカールプロジェクタのデフォルトの姿勢
        Quaternion rot = Quaternion.Euler(90, 0, 0);

        // デカールの絵を回転する
        rot = Quaternion.AngleAxis(applyRandomRotation ? randomRotationAngle : fixedRotationAngle, Vector3.up) * rot;

        // 法線とワールド上向きの外積から、デカールが向くべき右方向を計算する
        Vector3 toRight = Vector3.Cross(hitNormal, Vector3.up);
        Quaternion rightRotation = Quaternion.FromToRotation(Vector3.right, toRight);

        // 面の法線がワールド上を向くようにデカールプロジェクタを回転する
        Quaternion normalRotation = Quaternion.FromToRotation(Vector3.up, hitNormal);

        // 全ての回転を組み合わせる
        return normalRotation * rightRotation * rot;
    }
}

使い方

メインメニューからエディタウィンドウを開けます。フィールドにDecal Projectorのプレハブをドラッグアンドドラップします。配置する面からの距離やスケール、角度などを設定できます。

ボタンを押すと、シーンビューで左クリックした位置にプレハブを配置できるようになります。他のゲームオブジェクトは操作できなくなります。

ドラッグで移動できます。面に合わせて自動でDecal Projectorの向きを変えます。

スケールと回転をランダムに設定できます。

ボタンをもう一度押すと終了し、通常の操作に戻ります。

これで、簡単にDecal Projectorを配置できました。

コメントを残す

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