Unityの衝突判定を正しく設計する ― Tag判定/LayerMask/衝突マトリクス徹底解説

Unity 6系前提、2D/3D両対応

本記事は次のテーマを一気に整理します。

  • タグ判定 vs レイヤー衝突マトリクスの使い分け
  • Layer / LayerMask の正しい理解と実践
  • OnTriggerEnter(2D) での軽量フィルタ実装
  • Hitbox/HurtboxUI と Player など代表例の設計
  • Physics.Ignore〜 系APIや layer切替 による動的除外
  • チーム開発での運用規約テスト観点

結論(設計の指針)

  • 「そもそも当てないべき組み合わせ」は Project Settings > Physics / Physics 2D の衝突マトリクスで OFF→ 物理段階で除外され、イベント自体が発生しない(最速・最安)
  • 「状態に応じて切り替えたい」「例外的に一部だけ」は、LayerMask や コンポーネント型判定でイベント内部をフィルタ、必要に応じてPhysics.IgnoreLayerCollision / Physics.IgnoreCollision や layer切替を併用。
  • タグは識別用。衝突制御はレイヤーで統一するのが安全・高性能。

Tag判定とLayer(衝突マトリクス)の比較

1) ライフサイクル内の条件分岐(Tag/LayerMask/型判定)

void OnTriggerEnter(Collider other)
{
    // 例: Tagでふるい落とす
    if (!other.CompareTag("Player")) return;

    // 例: 型でふるい落とす(インターフェース推奨)
    if (!other.TryGetComponent<IInteractable>(out var interactable)) return;

    // 処理…
}
  • 長所: きめ細かい・状態依存の切替が容易、局所対応で柔軟
  • 短所イベントは呼ばれるため大量だと負荷に響く、ロジックが散りやすい
  • 注意: タグは1個しか付けられず、表記ゆれがバグ源。CompareTag を必ず使う

2) レイヤー衝突マトリクス(Project全体ルール)

Project Settings > Physics / Physics 2D > Layer Collision Matrix

→ 当てたくない組のチェックを外す

  • 長所最速(イベント発生ゼロ)、ヒューマンエラー予防、全体で一貫
  • 短所: 静的ルール寄り(例外はコード側で補う必要あり)
  • 向く: 「UI ↔ Player」「敵弾 ↔ 敵」など常に当てない関係

Layer と LayerMask の正体と使い方

  • Layer(0〜31) … オブジェクトが1つだけ持つ整数ラベル
  • LayerMask … 32bitの集合(ビットフラグ)。複数レイヤーをまとめて指定できる

よく使うAPI

  • Raycast / Overlap:layerMask で対象を絞り込み
  • 描画/照明:Camera.cullingMask / Light.cullingMask
  • 判定内フィルタ:(mask & (1 << other.layer)) != 0

インスペクタで選んだレイヤーだけ通す王道パターン

[SerializeField] private LayerMask targetLayers;

void OnTriggerEnter(Collider other)
{
    int bit = 1 << other.gameObject.layer;
    if ((targetLayers.value & bit) == 0) return; // 含まれていなければ無視
    // 処理…
}

2D 版

void OnTriggerEnter2D(Collider2D other)
{
    int bit = 1 << other.gameObject.layer;
    if ((targetLayers.value & bit) == 0) return;
    // 処理…
}

落とし穴

・トリガーを発火させるには、どちらか一方を Is Trigger、かつ最低片方に Rigidbody(2D) が必要
・3Dは OnTriggerEnter、2Dは OnTriggerEnter2D を使い分け
・LayerMask.NameToLayer(“Name") が存在しないと -1 を返す(ビットシフト前にチェック)


読みやすさを上げる小さな工夫(拡張メソッド)

public static class LayerMaskExtensions
{
    public static bool Contains(this LayerMask mask, int layer)
        => (mask.value & (1 << layer)) != 0;

    public static bool Excludes(this LayerMask mask, int layer)
        => (mask.value & (1 << layer)) == 0;
}
[SerializeField] private LayerMask targetLayers;

void OnTriggerEnter(Collider other)
{
    if (targetLayers.Excludes(other.gameObject.layer)) return;
    // 処理…
}

単一レイヤーだけを狙うなら、等値比較が最も読みやすい:

[SerializeField] private string targetLayerName = "Player";
private int _targetLayer;
void Awake() => _targetLayer = LayerMask.NameToLayer(targetLayerName);

void OnTriggerEnter(Collider other)
{
    if (other.gameObject.layer != _targetLayer) return;
    // 処理…
}

ケース別設計

1) Player ↔ UI を衝突させない

  • Screen Space – Overlay のCanvasはそもそも物理世界にいない→衝突しない
  • World Space UI / 物理当たり判定付きUIは、Player と UI レイヤーを分け、衝突マトリクスで OFF
int player = LayerMask.NameToLayer("Player");
int ui     = LayerMask.NameToLayer("UI");
Physics.IgnoreLayerCollision(player, ui, true); // 3D(必要ならランタイムでも切替)

2) Hitbox / Hurtbox(自傷防止・多人数戦)

組み合わせ当てる?
Hitbox ↔ HurtboxON
Hitbox ↔ Player(自身)OFF
Hitbox ↔ HitboxOFF
Player ↔ PlayerOFF(設計次第)
  • 基本は衝突マトリクスで組合せを定義
  • 状態によって攻撃受付を切るときは、Hurtboxの layer を一時的に切替 or IgnoreLayerCollision を使用

3) 一時的な無敵・ゴースト化

// 例: 一時的に「Ghost」レイヤーへ
int ghost = LayerMask.NameToLayer("Ghost");
int original = gameObject.layer;

IEnumerator Ghost(float sec)
{
    gameObject.layer = ghost;
    yield return new WaitForSeconds(sec);
    gameObject.layer = original;
}

4) 個別ペアだけ除外したい

// 「この剣の当たり」と「この宝箱」だけを一時的に無視
Physics.IgnoreCollision(swordCollider, chestCollider, true);
// 解除
Physics.IgnoreCollision(swordCollider, chestCollider, false);

パフォーマンスと可読性

  • 高速順の目安衝突マトリクス(イベントゼロ) > LayerMaskビット判定 > コンポーネント型判定 > タグ文字列比較
  • まず衝突マトリクスで広く除外してコールバックを減らし、局所のコードで最終フィルタが実戦的
  • 「何を当てるのか」を表(ルール)で見える化し、コードは意図を名前(IsTarget など)に出す

テストと運用(チーム前提)

  1. レイヤー一覧と衝突表をREADMEに固定化
    • 例:Default / Player / Enemy / Hitbox / Hurtbox / UI / Ghost / Terrain / Projectile
    • 変更時はレビュー必須(回帰リスク大)
  2. 自動テストの工夫
    • マトリクス変更差分の検知(スクリーンショット基準 or 設定のYAML化)
    • 代表プレハブでの当たりパス/ブロックの回帰確認
  3. 物理設定も併せて規約化
    • Queries Hit TriggersRaycasts Hit Triggers などのフラグ
    • 2D/3Dでメソッド取り違えない(Physics vs Physics2D)

よくある不具合チェックリスト

  • トリガーが発火しない
    • どちらか片方が Is Trigger になっているか
    • 少なくとも片方に Rigidbody(2D) が付いているか
    • 衝突マトリクスがOFFになっていないか(当たらない設定にしていないか)
  • Raycastが当たらない
    • layerMask に対象レイヤーが含まれているか
    • Query Trigger Interaction の設定
  • NameToLayer が -1
    • レイヤー名のスペルミス/未定義。Editor で先に定義

チートシート

// 名前からマスク作成(int)
int playerOrEnemy = LayerMask.GetMask("Player", "Enemy");

// 単一ビット
int playerBit = 1 << LayerMask.NameToLayer("Player");

// 結合・除外
int merged  = playerOrEnemy | (1 << LayerMask.NameToLayer("Item"));
int removed = playerOrEnemy & ~(1 << LayerMask.NameToLayer("Enemy"));

// Raycastに適用(3D)
if (Physics.Raycast(origin, dir, out var hit, 100f, playerOrEnemy))
{
    // 命中
}

// 描画制御
camera.cullingMask = playerOrEnemy;

まとめ

  • 衝突マトリクス:全体の安全網(「原則当てない」を定義)。負荷最小・事故最小。
  • LayerMask/型判定:イベント内の最終フィルタ。状態依存・例外処理に最適。
  • Ignore〜 / layer切替:ランタイムでの一時的な除外や無敵化。
  • タグは識別に限定し、衝突制御はレイヤーで統一

訪問数 5 回, 今日の訪問数 1回

Unity

Posted by hidepon