【Unity】レイヤーをインスペクターから選択可能にする構造体

こじゃらこじゃら

次のようにレイヤー(整数)の値をインスペクターから選択式で設定させる方法はないの?

このはこのは

やり方はいくつかあるけど、構造体を自作すれば可能だわ。

スクリプト中で使用するレイヤー値をインスペクターからドロップダウンで選択可能にする方法の解説記事です。

Unityのレイヤー0〜31の値で表現される整数値です。通常、スクリプト側からはint型として扱われます。

しかし、上記レイヤー値をインスペクター側から設定する場合、数値を直接入力しなければなりません。

これをレイヤー名などのドロップダウン形式で指定可能にする方法はいくつかありますが、本記事では独自構造体とエディタ拡張で実現する方法を紹介します。

構造体として実装することで、次のようにレイヤー名(文字列)によるアクセスなどスクリプトの可読性向上も期待できます。

// Layer構造体
[SerializeField] private Layer _layer;

・・・(中略)・・・

// レイヤー値をUI(=5)に変更
_layer.Value = 5;

// レイヤー値を"Water"に変更
_layer.Name = "Water";
動作環境
  • Unity 2022.3.0f1

スポンサーリンク

スクリプトの完成系

まず初めに、レイヤーをインスペクターから選択式で指定可能にする構造体のスクリプトを示します。

Layer.cs
using UnityEngine;

#if UNITY_EDITOR
using UnityEditor;
#endif

[System.Serializable]
public struct Layer
{
    [SerializeField] private int _value;

    // レイヤー値
    public int Value
    {
        get => _value;
        set
        {
            // レイヤーの範囲チェック
            if (value < 0 || value > 31)
                throw new System.ArgumentOutOfRangeException(
                    nameof(value),
                    "レイヤーは0~31の範囲で指定してください。"
                );

            _value = value;
        }
    }

    // レイヤー名
    public string Name
    {
        get => LayerMask.LayerToName(_value);
        set
        {
            // レイヤー名からレイヤー値を取得
            var layerValue = LayerMask.NameToLayer(value);

            // レイヤー名が存在しない場合はエラー
            if (layerValue == -1)
                throw new System.ArgumentException(
                    $"レイヤー名「{value}」は存在しません。",
                    nameof(value)
                );

            _value = layerValue;
        }
    }

    // int型への変換
    public static implicit operator int(Layer layer)
    {
        return layer.Value;
    }

    // Layer型への変換
    public static explicit operator Layer(int value)
    {
        return new Layer { Value = value };
    }

    // string型への変換
    public override string ToString()
    {
        return $"{Name}({_value})";
    }
}

#if UNITY_EDITOR
[CustomPropertyDrawer(typeof(Layer))]
public class LayerPropertyDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        EditorGUI.BeginProperty(position, label, property);

        var valueProperty = property.FindPropertyRelative("_value");

        // 現在設定されているレイヤー値を取得
        var currentValue = valueProperty.intValue;

        // レイヤー一覧を表示
        var newValue = EditorGUI.LayerField(position, label, currentValue);

        // レイヤー値を更新
        valueProperty.intValue = newValue;

        EditorGUI.EndProperty();
    }
}
#endif

上記をLayer.csとしてUnityプロジェクトに保存すると使用可能になります。

スクリプトの使用例

以下、前述のLayer構造体の使い方の例です。

UseExample.cs
using UnityEngine;

public class UseExample : MonoBehaviour
{
    // レイヤー構造体をインスペクターで設定
    [SerializeField] private Layer _layer;

    private void Start()
    {
        // レイヤー値を取得
        var layerValue = _layer.Value;

        // int型への暗黙変換
        int layerValue2 = _layer;

        // レイヤー名を取得
        var layerName = _layer.Name;

        // ログ出力
        Debug.Log($"_layer: {_layer}");
        Debug.Log($"layerValue: {layerValue}");
        Debug.Log($"layerValue2: {layerValue2}");
        Debug.Log($"layerName: {layerName}");

        // 更新テスト
        Debug.Log("===== 更新テスト =====");

        // レイヤー値を試しに変更(5 = UIレイヤー)
        _layer.Value = 5;
        Debug.Log($"_layer(5を設定): {_layer}");

        // レイヤー名を試しに変更
        _layer.Name = "Water";
        Debug.Log($"_layer(\"Water\"を設定): {_layer}");
    }
}

上記をUseExample.csという名前でUnityプロジェクトに保存し、適当なゲームオブジェクトにアタッチすると機能します。

実行結果

インスペクターからは、次のようにドロップダウンとしてレイヤーを指定可能になります。

また、ゲームを実行すると、コンソールログにレイヤー情報が出力されます。

レイヤーをint型に変換した数値やレイヤー名を取得できています。また、Layer構造体に対してint型やレイヤー名(string型)で値を指定できることも確認できました。

スクリプトの説明

前述のLayer構造体エディタ拡張の実装について説明していきます。

Layer構造体

構造体はシリアライズ可能な型として定義します。また、内部的にはレイヤー値を表すint型フィールドを持つものとして定義します。

[System.Serializable]
public struct Layer
{
    [SerializeField] private int _value;

レイヤー値をint型整数として取得可能にするために、次のプロパティを実装しています。

// レイヤー値
public int Value
{
    get => _value;
    set
    {
        // レイヤーの範囲チェック
        if (value < 0 || value > 31)
            throw new System.ArgumentOutOfRangeException(
                nameof(value),
                "レイヤーは0~31の範囲で指定してください。"
            );

        _value = value;
    }
}

レイヤー値の取り得る範囲は0〜31のため、セットされる時は範囲チェックを行っています。範囲外なら例外ArgumentOutOfRangeExceptionをスローするようにしました。

また、レイヤー名でレイヤー値を読み書きするプロパティも実装しています。

// レイヤー名
public string Name
{
    get => LayerMask.LayerToName(_value);
    set
    {
        // レイヤー名からレイヤー値を取得
        var layerValue = LayerMask.NameToLayer(value);

        // レイヤー名が存在しない場合はエラー
        if (layerValue == -1)
            throw new System.ArgumentException(
                $"レイヤー名「{value}」は存在しません。",
                nameof(value)
            );

        _value = layerValue;
    }
}

数値からレイヤー名への変換LayerMask.LayerToNameメソッド、逆にレイヤー名から数値への変換LayerMask.NameToLayerメソッドで行えます。

Nameプロパティはこれらを使いやすくラップしています。

また、int型のレイヤー値と暗黙的な変換を行えるように、次のオペレータを実装しています。

// int型への変換
public static implicit operator int(Layer layer)
{
    return layer.Value;
}

// Layer型への変換
public static explicit operator Layer(int value)
{
    return new Layer { Value = value };
}

Property Drawerの説明

次のコードでLayer構造体のインスペクター上の表示をカスタマイズするためのProperty Drawerを定義しています。

#if UNITY_EDITOR
[CustomPropertyDrawer(typeof(Layer))]
public class LayerPropertyDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {

そして、レイヤー一覧をドロップダウンで選択可能にするコードは以下部分です。

// 現在設定されているレイヤー値を取得
var currentValue = valueProperty.intValue;

// レイヤー一覧を表示
var newValue = EditorGUI.LayerField(position, label, currentValue);

// レイヤー値を更新
valueProperty.intValue = newValue;

EditorGUI.LayerFieldメソッドを呼び出すことで、レイヤー選択のUIを簡単に描画できます。

運用上における注意点

当記事で紹介したLayer構造体の運用上における注意点についても示しておきます。

整数値としてシリアライズされるので、レイヤー名が変更されると表示も変わる

Layer構造体は内部的にはint型フィールドとしてレイヤー値を保持しています。

そのため、シリアライズ内容を覗いてみると、次のように数値のみがデータとして存在します。

そのため、レイヤー名を変更したり移動したりすると、表示上のレイヤー名も変化します。

Layer構造体の注意点として挙げましたが、これはゲームオブジェクトのレイヤー名にも言える事です。

LayerMask構造体と併用する時は注意

LayerMask構造体と併用する場合は、値の変換に注意する必要があります。

Layer layer;
LayerMask layerMask;

・・・(中略)・・・

// これはNG
layerMask = _layer.Value;

// これはOK
layerMask = 1 << layer.Value;

int型のレイヤー値を扱う場面全般に言えることですが、レイヤーをLayerMaskに指定する場合はシフト演算などで指定する必要があります。

さいごに

レイヤー値をインスペクターから選択可能にするには、エディタ拡張のEditorGUI.LayerFieldメソッドを使えば簡単です。

本記事では、どのスクリプトでも汎用的かつ簡単にレイヤー情報を扱えるようにするためにLayer構造体として実装する方法を紹介しました。

簡単のため必要最小限の機能に留めましたが、場面に応じてカスタマイズして使うと良いでしょう。

参考サイト

スポンサーリンク