7080 + 1

ゲームプログラミングの記事を書いてます。

【Unity】Gizmoの表示状態をスクリプトから制御する

シーン内のGameObjectの数が増えてくると、シーンに表示されているGizmoの数がとんでもないことになって、何がなんやらわからなくなってくることがあります。
それらをコンポーネントごとに表示、非表示を切り替えるには、GUI上でのみ設定可能でした。
(リフレクションを使えば、スクリプトからでも制御可能です。)

Unity2022になって、ついに公式でスクリプトから制御する方法が追加されました。
新しい方法を紹介するとともに、他の制御方法も併せて記載します。

GUIで制御する

Sceneビューの右上のアイコンから、Gizmoの表示状態をコンポーネントごとに制御するメニューを開くことができます。

Gizmoメニュー
このメニューはコンポーネント名が検索できるようになったり、パフォーマンスが向上したりと改善されてきていますが、ここで1つ1つ設定するのはとても面倒です。

スクリプトで制御する(リフレクション)

リフレクションを使えば、スクリプトからでも制御する方法が存在はします。
が、リフレクションは呼び出してるメソッド名などが変わったら使えなくなったりパフォーマンスの観点から、可能であればやりたくない方法です。
stackoverflow.com

スクリプトで制御する(公式)

Unity2022あたりから、GizmoUtility、GizmoInfoというクラスが登場し、Gizmoの表示状態を制御するAPIが正式に実装されました。
GizmoUtility - Unity スクリプトリファレンス
GizmoInfo - Unity スクリプトリファレンス

以下は、すべてのGizmoの表示非表示と、LightとCameraのみを非表示にするように変更するサンプルです。
youtu.be

これらは以下のスクリプトでできています。
gist.github.com

【Unity】EditorWindowを定義しているスクリプトをすぐに探せるようにする

はじめに

ゲームを開発していると、EditorWindowを継承して、そのゲーム専用のエディタウィンドウを作成することがよくあるかと思います。
開発が進むと、いろんな人がエディタウィンドウを新しく定義して、エディタウィンドウが増えていくこともあるかと思います。
そんな時、ふと「このEditorWindowの実装どうなってるんだろう」と思うことがあると思います。

が、EditorWindowを定義しているスクリプトファイルがどれになるのか、スムーズに探せないことがあります。
EditorWindowのタイトルが別に定義されていたりするとさらにわからなくなります。GUIの表示から処理を推測してスクリプトファイルを探す..なんてことを僕はしょっちゅうやっています。

解決策

あまり効率的ではないので、これを解決する方法を紹介します。
動画のようにメニューからスクリプトの場所を探すことができるようになります。

EditorWindowにIHasCustomMenuを実装する

IHasCustomMenu を実装すると、EditorWindow右上のボタンで出る一覧に自分のメニューを増やすことができます。

using UnityEngine;
using UnityEditor;

public class ExampleEditorWindow : EditorWindow, IHasCustomMenu
{
    [MenuItem("Tools/EditorWIndow")]
    public static void Open()
    {
        EditorWindow.GetWindow<ExampleEditorWindow>("ExampleEditorWindow");
    }

    /// <summary>
    /// メニューにアイテムをを追加する
    /// </summary>
    /// <param name="menu">メニュー</param>
    public void AddItemsToMenu(GenericMenu menu)
    {
        menu.AddItem(new GUIContent("Ping Script"), false, () => {
            var mono = MonoScript.FromScriptableObject(this);
            EditorGUIUtility.PingObject(mono);
        });
    }
}

GenericMenu.AddItem() を使うことで、簡単にメニューを追加することができます。

【Unity】Unity上で直接metaファイルの中身を見られるようにする

Unity での開発中、アセットの meta ファイルを見たくなることがたまにあります。
わざわざエクスプローラーを開いて meta ファイルをテキストエディタで開くのは少し手間です。

そこで、Unity 上で直接アセットの meta ファイルを開けるようにしてみます。

今回はついでに GUID をコピペできるボタンも実装しています。(一番よく使う...と思うので)

ソースコードは以下参照ください。

ProjectView.cs が ProjectView にボタンを追加する処理で、
AssetMetaInfoPopupWindow.cs が meta ファイルの詳細を表示している処理です。

【Unity】GameObjectを配置せずに、オブジェクトを描画する【RenderMesh】

突然ですが、以下の画像を見てください。

シーンに赤い立方体が10個存在しているなんてことないシーンですが、ヒエラルキーには GameObject が3つしかありません。
どういうことでしょうか??

Graphics.RenderMesh

Unity で何かキャラクターなどの3Dモデルを描画しようと思うと、MeshRendererSkinnedMeshRenderer のついた GameObject をシーンに配置しないといけないと思っている方が多いかもしれません。
実は、GameObject を配置しなくてもシーン上に3Dモデルを描画することができます。

実は、 RenderMesh オブジェクトについている、RenderMesh コンポーネントが描画処理をしています。

public class RenderMesh : MonoBehaviour
{
    [SerializeField]
    Mesh _mesh;

    [SerializeField] 
    Material _material;

    [SerializeField, Range(0, 5)]
    float _interval = 3f;

    RenderParams _renderParams;

    void Start()
    {
        _renderParams = new RenderParams(_material);
    }

    void Update()
    {
        var matrix = new Matrix4x4(
            new Vector4(1, 0, 0, 0),
            new Vector4(0, 1, 0, 0),
            new Vector4(0, 0, 1, 0),
            new Vector4(0, 0, 0, 1));

        var row0 = matrix.GetRow(0);
        for (int i = 0; i < 10; i++) {
            matrix.SetRow(0, row0);

            // 描画
            Graphics.RenderMesh(_renderParams, _mesh, 0, matrix);

            // X座標を等間隔にずらして配置
            var c = row0;
            c.w += _interval;
            row0 = c;
        }
    }
}

Graphics.RenderMesh に、描画のパラメータ (RenderParams)とメッシュ、あとは座標を渡すことで、簡単にオブジェクトを描画することができます。

RenderParams

描画時に使うありとあらゆるデータを設定することができます。唯一必要なのはマテリアルくらいかなと思います。ほかにもどのカメラに映すのかなどの設定ができるみたいです。
詳細はリファレンス見たほうが早いかと思います。
docs.unity3d.com

メリット

スクリプトからものを描画できるメリットは、主に以下かと思います。

  • GameObject を Instantiate する必要がないので、パフォーマンス的に早いし、メモリにも優しい
  • GameObject をシーンに配置するわけではないので、シーンに差分が出ない
  • 配置を数学的に制御できる

デメリット

GameObjectを使わないことによるデメリットは結構あります。

  • シーン上で配置をいじることができないし、そもそも選択することができない
  • スキンメッシュアニメーションさせることはできない
  • コンポーネントをつけることができないので、例えば当たり判定などは自前で用意する必要がある

使いどころ

個人的には、あまりゲーム中で使うものではないかもと思いました。どちらかというと、デバッグ用などに使えるかもしれません。
例えば、ゲームを作っていると、デバッグで一部のメッシュだけをハイライトしたくなるということが出てきます。
その時にこれを使えば、シーンにある GameObject を一切いじることなく、またシーンを汚さずにハイライト用のメッシュを描画することができます。
個人的にはシーンに差分が出ないのがメリットとして一番大きいかもと思いました。

【Unity】Gizmoの表示非表示をスクリプトから操作する【Annotation】

Gizmo の表示非表示は、シーンビュー右上のアイコンからそれぞれ表示したいものにチェックを入れることで制御可能です。
いつからから検索もできるようもなり、さらに便利になりました。

が、これを1つずつ手作業でやるのは大変です。
スクリプトから制御できればいくらか作業も楽になるかもしれません。

ということで、今回はスクリプトからこの表示非表示を制御する方法です。
※ Unityのバージョンは 2021.3.3f1 を使用しています。

今回はリフレクションで Annotation や AnnotationUtility を持ってきて実装しています。

GizmoUtility

2022.1 からは GizmoUtility というのが実装されて、リフレクションを使わなくても同じことができるようになっているようです。
2022.1 以上を使う人はこちらの手法を使ったほうがより安全で効率も良いかと思います。
docs.unity3d.com
unity.com

【Unity】矩形の空間を定義、シーン上でいじれる処理を書いてみる【BoxBoundsHandle】

ゲームを作っていると、ゲーム空間の中にエリアを定義したくなることがあると思います。
今回はそれをシーン上で調整できるようにする BoxBoundsHandle を紹介します。

とりあえずシーンに矩形を描画してみる

単純にシーン上で範囲を表すために図形を描画したい場合、GizmosクラスのDraw〇〇というメソッドを使います。今回は矩形を描画したいので、DrawWireCube()を使いました。

public class BoxArea : MonoBehaviour
{
    [SerializeField] 
    private Bounds bounds;

    private void OnDrawGizmosSelected()
    {
        Gizmos.DrawWireCube(bounds.center, bounds.size);
    }
}

これで範囲をシーン上でグラフィカルにみることができますが、矩形の調整が数値でしかできないです。
ピッタリ数値を合わすときなどは数値でできたほうが便利なことがありますが、シーン上で矩形の大きさを見ながら直接調節できたほうがやりやすい場合もあるかと思います。
そこで、シーン上から直接矩形の範囲を調節することができる方法を紹介します。

BoxBoundsHandle を使う

シーン上で矩形の大きさを調整できるBoxBoundsHandleというものがあります。
OnDrawGizmos() 上では使えないので、インスペクタをエディタ拡張する必要があります。

[CustomEditor(typeof(BoxArea))]
public class BoxAreaInspector : Editor
{
    BoxBoundsHandle handle;

    Bounds cacheBounds;

    private void OnEnable()
    {
        handle = new BoxBoundsHandle();
    }

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

        var bounds = serializedObject.FindProperty("bounds");
        handle.center = bounds.boundsValue.center;
        handle.size = bounds.boundsValue.size;

        using (var check = new EditorGUI.ChangeCheckScope()) {
            // 描画
            handle.DrawHandle();

            // 変更を適用する
            if (check.changed) {
                cacheBounds.center = handle.center;
                cacheBounds.size = handle.size;
                bounds.boundsValue = cacheBounds;
            }
        }

        serializedObject.ApplyModifiedProperties();
    }
}

これでシーン上からグラフィカルに矩形を調整できるようになりました。
が、実はまだ問題があります。

回転ができない

BoxBoundsHandleには回転の値がありません。
Handles.DrawingScope()を使う必要があります。
ここに行列を渡すことで、回転させることができます。

[CustomEditor(typeof(BoxArea))]
public class BoxAreaInspector : Editor
{
    BoxBoundsHandle handle;

    Bounds cacheBounds;

    private void OnEnable()
    {
        handle = new BoxBoundsHandle();
    }

    private void OnSceneGUI()
    {
        var self = target as BoxArea;
        serializedObject.Update();
        var bounds = serializedObject.FindProperty("bounds");

        // GameObjectの空間で回転、拡縮、移動させる
        var matrix = Handles.matrix * self.transform.localToWorldMatrix; 

        handle.center = bounds.boundsValue.center;
        handle.size = bounds.boundsValue.size;

        using (var check = new EditorGUI.ChangeCheckScope()) {
            // 描画する
            using (new Handles.DrawingScope(matrix)) {
                handle.DrawHandle();
            }

             // 変更を適用する
            if (check.changed) {
                cacheBounds.center = handle.center;
                cacheBounds.size = handle.size;
                bounds.boundsValue = cacheBounds;
            }
        }

        serializedObject.ApplyModifiedProperties();
    }
}

最後に

BoxBoundsHandle以外にも、球を同じように制御できるSphereBoundsHandleなどもあるので、いろいろ探してみてください。
docs.unity3d.com

また、PrimitiveBoundsHandleを継承することで、自分だけの Handle を作ることもできそうです。
docs.unity3d.com

【Unity】インスペクタ拡張で元のスクリプトを表示する

何もインスペクタ拡張をしていない場合、コンポーネントにはそのスクリプトが ObjectField で表示されます。
これをクリックするとスクリプトの場所を教えてくれるため、意外と便利です。

public class Example : MonoBehaviour
{
    [SerializeField]
    int example;
}


が、インスペクタ拡張で特に何もしないでいると、この表示は消えてしまいます。


スクリプトm_Script で宣言されているので、 SerializedProperty を取得すれば実現できます。

[CustomEditor(typeof(Example))]
public class ExampleInspector : Editor
{
    public override void OnInspectorGUI()
    {
        // Scriptを編集できない状態で表示する
        using (new EditorGUI.DisabledScope(true)) {
            EditorGUILayout.PropertyField(serializedObject.FindProperty("m_Script"));
        }

        EditorGUILayout.PropertyField(serializedObject.FindProperty("example"));
    }
}

この処理は Unity の公式の GitHub と同じ処理をしています。
github.com