Tag / Layer / LayerMask とは?

Unityでオブジェクトを扱っていると、「これは敵かどうか判定したい」「カメラに映すものを制限したい」「Raycastで特定の対象だけ拾いたい」といった場面が出てきます。

これらを実現するために用意されているのがTagLayerLayerMaskの3つです。

役割を一言でまとめると以下のようになります。

機能役割主な用途
Tagオブジェクトを文字列で識別するプレイヤーや敵の判定、Find系メソッド
Layerオブジェクトを整数で分類するCameraの描画制御、Physicsの衝突制御
LayerMask複数のLayerビット表現で扱うPhysics.RaycastなどのAPI引数

どれも似ているように見えますが、それぞれ得意な領域があります。

どの場面でどれを使うのか、順を追って見ていきます。

Tagの使い方

TagGameObjectを識別するための文字列です。

GameObject1つにつき1つのTagしか設定できません。

Tagの設定

TagEdit → Project Settings → Tags and Layersから定義します。

定義したTagは、GameObjectのインスペクタ上部にあるドロップダウンから選択できます。

スクリプトからはgameObject.tagプロパティで取得できます。

using UnityEngine;

public class TagSample : MonoBehaviour
{
    private void Start()
    {
        // Tagの値を取得
        Debug.Log(gameObject.tag);
    }
}

CompareTagでGCを回避する

Tagの比較で見かける典型的なコードがこちらです。

private void OnTriggerEnter(Collider other)
{
    // 文字列比較。内部で文字列を取得しているのでGCが発生する
    if (other.gameObject.tag == "Enemy")
    {
        Debug.Log("敵に触れた");
    }
}

一見問題なさそうに見えますが、gameObject.tagはプロパティアクセスのたびにマネージド側の文字列を返すため、参照のたびにアロケーションが発生する可能性があります。

一方、CompareTag()は内部で最適化されていて、文字列を生成せずに比較してくれます。

private void OnTriggerEnter(Collider other)
{
    // CompareTagを使うとアロケーションが発生しない
    if (other.CompareTag("Enemy"))
    {
        Debug.Log("敵に触れた");
    }
}

UpdateOnTriggerStayのように毎フレーム呼ばれる箇所では、CompareTag()を使う癖をつけておくと安心です。

なお、CompareTag()Componentにも実装されているので、other.CompareTag("Enemy")のようにColliderから直接呼び出せます。

Find系メソッドとの組み合わせ

TagGameObject.FindWithTag()GameObject.FindGameObjectsWithTag()の引数としても使えます。

// "Enemy"タグの付いたGameObjectを1つ取得
var enemy = GameObject.FindWithTag("Enemy");

// "Enemy"タグの付いたGameObjectをすべて取得
var enemies = GameObject.FindGameObjectsWithTag("Enemy");

ただしFind系の処理コストは高めなので、AwakeStartで一度だけ実行する、SerializeFieldで参照を渡すなど、Update内での呼び出しは避けるのが基本です。

Layerの使い方

LayerGameObjectを**整数(0〜31)**で分類する仕組みです。

文字列のTagと違い、CameraPhysicsといったUnity内部の機能と密に連携しています。

Layerの設定

Tagと同じくEdit → Project Settings → Tags and LayersLayersセクションから定義します。

定義可能なレイヤーは最大32個で、0〜7はDefaultTransparentFXIgnore RaycastWaterUIなどUnityが予約しています。

残りの枠(8〜31)に独自のレイヤーを登録していくことになります。

スクリプトからはgameObject.layerで取得・設定できます。

using UnityEngine;

public class LayerSample : MonoBehaviour
{
    private void Start()
    {
        // レイヤー名からインデックスを取得して設定
        gameObject.layer = LayerMask.NameToLayer("Enemy");
    }
}

カメラのCulling Mask

CameraコンポーネントにはCulling Maskという項目があり、特定のLayerだけを描画対象にできます。

例えばミニマップ用のカメラには「ミニマップ用のアイコンだけ映す」「メインカメラには逆にそれを映さない」といった切り分けが可能です。

UI専用のカメラを別途用意するときも、Culling MaskUIだけに絞るのがよくあるパターンです。

Physicsの衝突マトリクス

Project Settings → PhysicsLayer Collision Matrixでは、レイヤー同士の衝突可否を一括設定できます。

  • プレイヤーの攻撃判定は敵にだけ当てたい
  • 味方同士はすり抜けたい
  • アイテムは地形とだけ衝突させたい

このような「どのレイヤーとどのレイヤーが衝突するか」をスクリプトを書かずに制御できるのが強みです。

物理演算の負荷軽減にも効果があるので、レイヤー設計は早めにやっておくと後が楽になります。

LayerMaskの使い方

LayerMaskは複数のLayerを1つのintに詰め込んだもの**です。

Physics.RaycastPhysics.OverlapSphereなど、Unityの多くのAPIがLayerMaskを引数に取ります。

ビットマスクの考え方

Layerは0〜31の整数なので、ちょうどint型(32bit)の各ビットに1対1で対応させられます。

例えばEnemyレイヤーが8番目だった場合、LayerMaskとしては以下のように表現します。

// 8番目のビットだけが1になる
int enemyMask = 1 << 8;
// 2進数で表すと: 0000_0000_0000_0000_0000_0001_0000_0000

この「1 << レイヤー番号」がビットシフト記法です。

ビットシフトでマスクを作る

レイヤー名から動的にマスクを作るには、LayerMask.NameToLayer()と組み合わせます。

using UnityEngine;

public class LayerMaskSample : MonoBehaviour
{
    private void Start()
    {
        // Enemyレイヤーのビットだけが立ったマスク
        int enemyMask = 1 << LayerMask.NameToLayer("Enemy");

        // EnemyとBoss、両方のレイヤーを含むマスク(OR演算)
        int targetMask = (1 << LayerMask.NameToLayer("Enemy"))
                       | (1 << LayerMask.NameToLayer("Boss"));

        // Player以外すべてを対象にしたマスク(NOT演算)
        int notPlayerMask = ~(1 << LayerMask.NameToLayer("Player"));
    }
}
  • |(OR) で複数レイヤーを組み合わせる
  • ~(NOT) で指定レイヤー以外をすべて含める

この2つを押さえておけば大体のケースに対応できます。

LayerMask.GetMaskで楽に作る

文字列からマスクを直接組み立てたいときはLayerMask.GetMask()が便利です。

// EnemyとBossの両方を含むマスクを一発で取得
int targetMask = LayerMask.GetMask("Enemy", "Boss");

LayerMask.NameToLayer()レイヤーのインデックス (存在しない場合は-1)を返すのに対し、LayerMask.GetMask()マスク値(ビット列) を返します。

両者は返り値が全く違うので、混同しないよう注意しましょう。

Physics.Raycastでの活用

LayerMaskの代表的な使い道はPhysics.Raycastの対象フィルタリングです。

using UnityEngine;

public class RaycastSample : MonoBehaviour
{
    [SerializeField] private LayerMask _targetMask;

    private void Update()
    {
        // 前方に向かってRaycastし、targetMaskに含まれるLayerだけを対象にする
        if (Physics.Raycast(transform.position, transform.forward, out RaycastHit hit, 10f, _targetMask))
        {
            Debug.Log($"ヒット: {hit.collider.name}");
        }
    }
}

LayerMask型のフィールドをSerializeFieldにすると、インスペクタ上ではチェックボックス形式のドロップダウンで指定できます。

これでコードに書かずインスペクタからフィルタ対象を変えられます。

あるGameObjectがマスクに含まれるか判定する

OnTriggerEnterなどで、相手のLayerがマスクに含まれているかをチェックしたい場合は、AND演算(&)を使います。

using UnityEngine;

public class TriggerSample : MonoBehaviour
{
    // インスペクタから対象レイヤーを設定
    [SerializeField] private LayerMask _targetMask;

    private void OnTriggerEnter(Collider other)
    {
        // 相手のlayerをビット位置に変換し、マスクとAND演算
        if ((_targetMask.value & (1 << other.gameObject.layer)) != 0)
        {
            Debug.Log("対象レイヤーに含まれる");
        }
    }
}

LayerMask型は内部でintvalueを持っているので、mask.valueとしてアクセスします。

なお、Unity 6ではLayerMaskIncludes()のような便利メソッドはまだ提供されていないので、上記のAND演算が定番の判定方法です。

使い分けの判断基準

どんなときに何を使うか、ざっくり表にまとめます。

やりたいこと使うもの
「これはプレイヤーか?」など種類の識別Tag
カメラの描画対象をまとめて制御Layer + Culling Mask
物理衝突をレイヤー単位で制御Layer + Layer Collision Matrix
RaycastOverlapSphereで対象を絞り込むLayerMask
複数のレイヤーを1つの値で扱いたいLayerMask

判断の順番はこんな感じです。

  1. 「衝突や描画をまとめて制御したい」→ Layerを設計
  2. RaycastなどのAPIにレイヤーを渡したい」→ LayerMaskを作る
  3. 「個別のオブジェクトをスクリプトで判定したい」→ Tagを使う

TagLayerは併用できるので、「LayerEnemyTagBoss」のように分けると、衝突制御と個別判定を両立できます。

気をつける点

Tagの文字列指定はタイプミスに弱い

Tagは文字列で指定するため、"Enemy""enemy"を間違えるとコンパイルエラーにならず、実行時まで気付けません。

定義されていないTagを指定するとUnityExceptionが発生します。

対策として、Tag名を定数化しておく方法があります。

public static class Tags
{
    public const string Player = "Player";
    public const string Enemy = "Enemy";
}

// 使用例
if (other.CompareTag(Tags.Enemy))
{
    // ...
}

こうしておけば、タイプミスはコンパイル時に気付けます。

Layerは最大32個まで

Layerint型のビットで管理されているため、32個が上限です。

そのうちUnityの予約レイヤー(DefaultUIIgnore Raycastなど)があるので、実質ユーザーが使えるのは24個前後となります。

規模の大きなプロジェクトでは早めにレイヤー設計を済ませて、無計画に追加しないようにしましょう。

NameToLayerとGetMaskを混同しない

この2つは返り値が違います。

// レイヤーのインデックス(例: 8)
int layerIndex = LayerMask.NameToLayer("Enemy");

// マスク値(例: 256 = 1 << 8)
int maskValue = LayerMask.GetMask("Enemy");

Physics.Raycastの引数にはマスク値が必要なので、NameToLayer()の結果をそのまま渡すと意図しないレイヤーが対象になります。

NameToLayer()の結果は必ず1 << ...でシフトしてから使うか、最初からGetMask()を使うようにしましょう。

存在しないレイヤー名を渡すと-1

LayerMask.NameToLayer()に存在しないレイヤー名を渡すと、例外ではなく**-1**が返ります。

これに気付かずビットシフトすると意図しないマスクになるので、初期化時にチェックしておくと安全です。

int layer = LayerMask.NameToLayer("Enemy");
if (layer < 0)
{
    Debug.LogError("Enemyレイヤーが定義されていません");
}

まとめ

今回はTagLayerLayerMaskの役割と使い分けを見てきました。

  • Tagはオブジェクトを文字列で識別する。CompareTag()でGC回避が基本
  • Layerはオブジェクトを整数で分類して、CameraPhysicsと連携する
  • LayerMaskは複数のLayerをビット表現でAPIに渡すための型

似た名前で混乱しがちですが、それぞれ得意な領域は別物です。

「識別ならTag、衝突や描画ならLayer、APIに渡すならLayerMask」と覚えておくと、迷う場面はかなり減ります。

最初にTagLayerの設計を決めておくと、後のコードがぐっと書きやすくなります。プロジェクトの立ち上げ時に意識してみてください。