パープルハット

※当サイトではGoogleアドセンス広告を利用しています

Unity LayerMask.valueとgameObject.layer





前提

以下の説明ではUnityのLayersが以下のようになっていると仮定します。





Layerを使っていたときのトラブル

LayerMaskに設定したLayerとゲームオブジェクトのレイヤーが等しいか確認する処理を実装するために、こんな感じのクラスを

using UnityEngine;

public class TestLayer01 : MonoBehaviour
{
    [SerializeField] LayerMask sampleLayer;

    private void Start()
    {
        Debug.Log($"sampleLayer.value = {sampleLayer.value}");
        Debug.Log($"gameObject.layer = {gameObject.layer}");
    }
}


こんな感じのオブジェクトに


貼り付けたら出力は両方とも3で同じ数字になると思っていたのですが、実際はこうなりました…。
sampleLayer.valueで表示される数字について少し検討しなければならないようです。

実行結果
sampleLayer.value = 8
gameObject.layer = 3




解決法の模索

「sampleLayer」の値を変更しながら実行すると以下のような値が得られました。

sampleLayer value
Nothing 0
Everything -1
Default 1
TransparentFX 2
Ignore Raycast 4
Sample 8
Water 16
b 128





仮説

先ほどの表を見ると「Nothing」と「Everything」は少し例外のようですが、「Default」以下の値を見るとどうやら2の累乗の形となっています。
だから、「sampleLayer.value」は2進数で表した時にこうなっているのではないかと考えました。
下から1桁目が「Default」の状態、2桁目が「TransparentFX」の状態、3桁目が「Ignore Raycast」の状態というように対応しており、それぞれチェックをつけている時が1、そうでないときが0になっているのではないか。

つまり、このようになっているのではないかということです。

Layer名 b Water Sample Ignore Raycast TransparentFX Default
対応する桁 8 5 4 3 2 1

それぞれを10進数に直す場合は、n桁目であったら2^{n-1}をかければよいので、

  • Default:n=1より、1\times2^{1-1}=1
  • Sample:n=4より、4\times2^{4-1}=8

となります。




仮説の検証

先ほどの仮説が正しいなら、

のようにしたとき、に出力される「sampleLayer.value」はn=1の「Default」とn=4の「Sample」の和となるので、
1\times2^{1-1}+4\times2^{4-1}=9
となるはずですが、実際に確認したらそうなりました




仮説の原因

おそらく、Unity側は仮説のように保存することで「sampleLayer.value」を一意に区別できるようになるのだと思います。
例えば、

Layer名 b Water Sample Ignore Raycast TransparentFX Default
対応する10進数 8 5 4 3 2 1

というように保存すると、「sampleLayer」に「Default」と「TransparentFX」を保存しても、「Ignore Raycast」だけを保存しても共に「sampleLayer.value」が3となり区別できません。




解決策

今回は、「sampleLayer」に設定するLayerが1つだけの場合を考えます
最初の「Layerを使っていたときのトラブル」の場合について考えると、以下のように「TestLayer01」クラスを書き換えればよいことがわかります。

using UnityEngine;

public class TestLayer01 : MonoBehaviour
{
    [SerializeField] LayerMask sampleLayer;

    private void Start()
    {
        Debug.Log($"sampleLayer.value = {sampleLayer.value}");
        Debug.Log($"gameObject.layer = {gameObject.layer}");

        if (Pow(2, gameObject.layer) == sampleLayer.value)
        {
            Debug.Log("Layerの一致");
        }
    }

    int Pow(int n, int p)
    {
        int result = 1;
        for (int i = 0; i < p; i++)
        {
            result *= n;
        }
        return result;
    }
}




LayerMaskに2つ以上のLayerを割り当てた場合

LayerMaskを2進数に変換して、2進数のlayer桁目の数値が1ならそのlayerは含まれているとみなせます。
文字列の操作方法は次のサイトを参考にしました。


ソースコード

public static class MyLayerExtensions
{
    public static bool ContainLayer(this LayerMask layerMask, GameObject gameObject)
    {
        //layerMaskを2進数で取得
        var s1 = Convert.ToString(layerMask, 2);

        //layerの数値を取得
        int n = gameObject.layer;

        //s1の文字数がn以上かつ、s1のn桁目が1ならtrue
        return s1.Length >= n && s1.Substring(s1.Length - n - 1, 1) == "1";
    }
}


使用例

bool v = layerMask.ContainLayer(gameObject);