Tsuの雑記¯\_(ツ)_/¯

主に製作メモ・備忘録として使用。製作したアプリのリンクもあります。

【Unity】複数選択式プルダウンメニューに重複要素を表示する【エディター拡張】

Unity Editor の拡張機能には,ドロップダウンメニューの作成方法が複数用意されています。

このドロップダウンメニューには独自の仕様があり,特に同じ要素を2つ以上含む場合は注意が必要です。

本稿では,その仕様と対策について御説明いたします。

(Unity 2018.3.0f2)

EditorGUILayout.MaskField

今回は,こちらの関数で確認します。

docs.unity3d.com

文字列配列string[]から項目を選択し,フラグを10進数intで返します。

(もちろん,ToString()関数によって文字列stringを取得する事も出来ます。)

using UnityEngine;

// ここからエディター上でのみ有効
#if UNITY_EDITOR
using UnityEditor;

// エディター拡張クラス
[CustomEditor(typeof(Extend))]
public class ExtendedEditor : Editor
{// Editor クラスを継承
    // Extend クラスの変数を扱うために宣言
    Extend extend;

    void OnEnable()
    {// 最初に実行
        // Extend クラスに target を代入
        extend = (Extend)target;
    }

    public override void OnInspectorGUI()
    {// Inspector に表示
        // これ以降の要素に関してエディタによる変更を記録
        EditorGUI.BeginChangeCheck();

        // ラベルの作成
        var label = "List";
        // 初期値として表示する項目のインデックス番号
        var selectedIndex = extend.index;
        // プルダウンメニューに登録する文字列配列
        var displayOptions = Extend.list;
        // プルダウンメニューの作成
        var index = Extend.list.Length > 0 ? EditorGUILayout.MaskField(label, selectedIndex, displayOptions)
            : 0;

        for (var i = 0; i < Extend.list.Length; i++)
        {// ビット演算で選択状態を判定
            if ((index & 1 << i) != 0)
            {// 選択状態のインデックス番号をログ出力
                Debug.Log(i);
            }
        }

        if (EditorGUI.EndChangeCheck())
        {// 操作を Undo に登録
            // Extend クラスの変更を記録
            var objectToUndo = extend;
            // Undo メニューに表示する項目名
            var name = "Extend";
            // 記録準備
            Undo.RecordObject(objectToUndo, name);
            // Undo に記録したい変数を登録
            extend.index = index;
        }
    }
}
// ここまでエディター上でのみ有効
#endif

// 同一オブジェクトへの複数追加を禁止
[DisallowMultipleComponent]
public class Extend : MonoBehaviour
{// エディター拡張の中身
    // リスト
    public static readonly string[] list = { "か", "た", "た", "た", "き", "き" };
    // 初期フラグは2進数で“100011”
    public int index = 35;
}

配列の中身に御注目ください。

上記のスクリプトから生成したプルダウンメニューは,次の画像のようになります。

UnityEditor の ドロップダウンメニューから重複した要素がなくなっている画像
重複した要素がなくなっている

「かたたたきき」が「かたき」になってしまいました。

ここであえて,「き」を再選択してみます。

デバッグログに &quot;4&quot; が追加されている画像
「き」は選択状態のまま index の値に "4" が追加

ログにインデックス番号を出力するよう記述していたため,indexの値に4が追加された事が分かります。

for (var i = 0; i < extend.list.Length; i++)
{// ビット演算で選択状態を判定
    if ((index & 1 << i) != 0)
    {// 選択状態のインデックス番号をログ出力
        Debug.Log(i);
    }
}

この後は何度「き」を選んでも,5の選択状態は解除されません。

同じように「た」を選ぶと1が消えるため,「重複した要素はインデックス番号が最も小さい物のみ切り替わる」という事が分かります。

デバッグログから &quot;1&quot; が消えている画像
「た」は選択状態が切り替わる

対策

プルダウンメニューに表示する項目名に,インデックス番号を加えます。

using UnityEngine;
// LINQ の使用
using System.Linq;

// ここからエディター上でのみ有効
#if UNITY_EDITOR
using UnityEditor;

// エディター拡張クラス
[CustomEditor(typeof(Extend))]
public class ExtendedEditor : Editor
{// Editor クラスを継承
    // Extend クラスの変数を扱うために宣言
    Extend extend;

    void OnEnable()
    {// 最初に実行
        // Extend クラスに target を代入
        extend = (Extend)target;
    }

    public override void OnInspectorGUI()
    {// Inspector に表示
        // これ以降の要素に関してエディタによる変更を記録
        EditorGUI.BeginChangeCheck();

        // ラベルの作成
        var label = "List";
        // 初期値として表示する項目のインデックス番号
        var selectedIndex = extend.index;
        // ドロップダウンメニューの項目にインデックス番号を付与
        System.Func<string, int, string> selector = (string name, int number) => $"{number}: \r{name}";
        // プルダウンメニューに登録する文字列配列
        var displayOptions = Extend.list.Select(selector).ToArray();
        // プルダウンメニューの作成
        var index = Extend.list.Length > 0 ? EditorGUILayout.MaskField(label, selectedIndex, displayOptions)
            : 0;

        for (var i = 0; i < Extend.list.Length; i++)
        {// ビット演算で選択状態を判定
            if ((index & 1 << i) != 0)
            {// 選択状態の要素をログ出力
                Debug.Log(Extend.list[i]);
            }
        }

        if (EditorGUI.EndChangeCheck())
        {// 操作を Undo に登録
            // Extend クラスの変更を記録
            var objectToUndo = extend;
            // Undo メニューに表示する項目名
            var name = "Extend";
            // 記録準備
            Undo.RecordObject(objectToUndo, name);
            // Undo に記録したい変数を登録
            extend.index = index;
        }
    }
}
// ここまでエディター上でのみ有効
#endif

// 同一オブジェクトへの複数追加を禁止
[DisallowMultipleComponent]
public class Extend : MonoBehaviour
{// エディター拡張の中身
    // リスト
    public static readonly string[] list = { "か", "た", "た", "た", "き", "き" };
    // 初期フラグは2進数で“100011”
    public int index = 35;
}

今回は,LINQSelect関数を使用しました。

docs.microsoft.com

元のリストには手を加えないため,選択した値の取得にも支障を来しません。

デバッグログに「か」「た」「き」と表示されている画像
list 配列の要素にはインデックス番号が割り振られていないまま

インデックス番号を付与した行には,文字列補間機能を使用しています。

C# のバージョンが 5 以下であれば,次のように記述してください。

// ドロップダウンメニューの項目にインデックス番号を付与
System.Func<string, int, string> selector = (string name, int number) => (number + ": \r" + name);

docs.microsoft.com

以上,エディター拡張の複数選択式プルダウンメニューに重複要素を表示する方法でした。

択一選択式プルダウンメニューの場合は,こちらの記事をお読みください。

tsu-games.hatenablog.com

重複した要素にのみ連番を振りたい場合は,こちらの記事が参考になるかもしれません。

tsu-games.hatenablog.com