Unityの衝突判定を正しく設計する ― Tag判定/LayerMask/衝突マトリクス徹底解説
Unity 6系前提、2D/3D両対応
本記事は次のテーマを一気に整理します。
- タグ判定 vs レイヤー衝突マトリクスの使い分け
- Layer / LayerMask の正しい理解と実践
- OnTriggerEnter(2D) での軽量フィルタ実装
- Hitbox/Hurtbox, UI と 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 ↔ Hurtbox | ON |
| Hitbox ↔ Player(自身) | OFF |
| Hitbox ↔ Hitbox | OFF |
| Player ↔ Player | OFF(設計次第) |
- 基本は衝突マトリクスで組合せを定義
- 状態によって攻撃受付を切るときは、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 など)に出す
テストと運用(チーム前提)
- レイヤー一覧と衝突表をREADMEに固定化
- 例:Default / Player / Enemy / Hitbox / Hurtbox / UI / Ghost / Terrain / Projectile
- 変更時はレビュー必須(回帰リスク大)
- 自動テストの工夫
- マトリクス変更差分の検知(スクリーンショット基準 or 設定のYAML化)
- 代表プレハブでの当たりパス/ブロックの回帰確認
- 物理設定も併せて規約化
- Queries Hit Triggers, Raycasts 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切替:ランタイムでの一時的な除外や無敵化。
- タグは識別に限定し、衝突制御はレイヤーで統一。
訪問数 4 回, 今日の訪問数 4回




ディスカッション
コメント一覧
まだ、コメントがありません