Godotのリストに戻る

【Godot】Collision LayersとMasksによる衝突判定の整理

作成: 2025-06-20更新: 2025-06-20移動・物理ブログ記事を読む

概要

Godotの衝突判定システムは、Layers(レイヤー)とMasks(マスク)を使って、どのオブジェクト同士が衝突するかを制御します。適切に設定することで、パフォーマンス向上と意図しない衝突の防止が可能です。

最初はこのシステムが全く理解できず、「なぜプレイヤーの攻撃が自分に当たるのか」「敵同士が押し合ってバグる」といった問題に悩まされました。レイヤーとマスクの仕組みを理解した今では、ゲームの衝突判定を綺麗に整理できるようになりました。

Collision Layer設定画面

基本概念

Collision Layer(レイヤー)

  • 意味: このオブジェクトが「どの層に存在するか」
  • 設定: 複数選択可能(ビットマスク)

Collision Mask(マスク)

  • 意味: このオブジェクトが「どの層と衝突判定を行うか」
  • 設定: 複数選択可能(ビットマスク)

実装例:ゲームでの設定

レイヤー構成例

Layer 1: Player        - プレイヤーキャラクター
Layer 2: Enemies       - 敵キャラクター
Layer 3: PlayerWeapon  - プレイヤーの攻撃判定
Layer 4: EnemyWeapon   - 敵の攻撃判定
Layer 5: Environment   - 壁、床、障害物
Layer 6: Pickups       - アイテム、コイン

各オブジェクトの設定

プレイヤー

  • Layer: 1 (Player)
  • Mask: 2, 4, 5, 6 (Enemies, EnemyWeapon, Environment, Pickups)
  • 結果: 敵、敵の攻撃、環境、アイテムと衝突

  • Layer: 2 (Enemies)
  • Mask: 1, 3, 5 (Player, PlayerWeapon, Environment)
  • 結果: プレイヤー、プレイヤーの攻撃、環境と衝突(敵同士はすり抜ける)

プレイヤーの武器

  • Layer: 3 (PlayerWeapon)
  • Mask: 2 (Enemies)
  • 結果: 敵だけに当たる(プレイヤー自身には当たらない)

プロジェクト設定でレイヤー名を付ける

  1. プロジェクト設定Layer Names2D Physics
  2. 各レイヤーに分かりやすい名前を設定
Collision Layer名前設定
Layer 1: Player
Layer 2: Enemies
Layer 3: PlayerWeapon
Layer 4: EnemyWeapon
Layer 5: Environment
Layer 6: Pickups
Layer 7: Triggers
Layer 8: Platforms

コードでの制御

レイヤーとマスクの設定

func _ready():
    # レイヤーの設定(ビット演算)
    collision_layer = 1  # Layer 1のみ
    collision_layer = 5  # Layer 1と3(1 + 4 = 5)
    
    # マスクの設定
    collision_mask = 2   # Layer 2のみ
    collision_mask = 14  # Layer 2,3,4(2 + 4 + 8 = 14)
    
    # 個別のビット操作
    set_collision_layer_bit(0, true)   # Layer 1を有効
    set_collision_mask_bit(1, false)   # Layer 2との衝突を無効

動的な衝突制御

# 無敵時間の実装
func become_invincible():
    # 敵の攻撃レイヤーとの衝突を無効化
    set_collision_mask_bit(3, false)  # Layer 4 (EnemyWeapon)を無効
    
    await get_tree().create_timer(invincibility_duration).timeout
    
    # 衝突を再度有効化
    set_collision_mask_bit(3, true)

実践的な活用例

片道プラットフォーム

# OneWayPlatform.gd
extends StaticBody2D

func _ready():
    # 下からは通り抜け、上からは乗れる
    one_way_collision = true
    
func allow_pass_through():
    # 一時的に衝突を無効化
    set_collision_layer_bit(7, false)  # Platformsレイヤーを無効
    await get_tree().create_timer(0.5).timeout
    set_collision_layer_bit(7, true)

フェーズシフト敵

# PhasingEnemy.gd
extends CharacterBody2D

func phase_shift():
    # 物理世界から一時的に消える
    collision_layer = 0  # 全レイヤーから除外
    collision_mask = 0   # 全ての衝突を無視
    modulate.a = 0.5     # 半透明に
    
    await get_tree().create_timer(2.0).timeout
    
    # 元に戻る
    collision_layer = 2  # Enemiesレイヤー
    collision_mask = 5   # Player + Environment
    modulate.a = 1.0

トラブルシューティング

よくある問題と解決法

問題:プレイヤーの攻撃が自分に当たる

# 解決:武器のマスクからプレイヤーレイヤーを除外
weapon_area.collision_mask = 2  # Enemiesのみ

問題:敵同士が押し合う

# 解決:敵のマスクから敵レイヤーを除外
enemy.collision_mask = 5  # Player + Environment(Enemiesを含まない)

パフォーマンス最適化

  1. 不要な判定を避ける: 相互作用しないオブジェクトは別レイヤーに
  2. レイヤー数を最小限に: 32レイヤーまで使用可能だが、必要最小限に
  3. 動的な切り替えは慎重に: 頻繁な変更は処理負荷になる

ベストプラクティス

  • レイヤー構成をドキュメント化
  • 定数でレイヤー番号を管理
  • デバッグ表示で衝突を可視化
# Constants.gd
class_name GameConstants

const LAYER_PLAYER = 0
const LAYER_ENEMIES = 1
const LAYER_PLAYER_WEAPON = 2
const LAYER_ENEMY_WEAPON = 3
const LAYER_ENVIRONMENT = 4

実装して学んだこと

レイヤーとマスクの概念は最初は難しく感じましたが、「レイヤー=自分がどこに属しているか」「マスク=何と衝突したいか」というシンプルな考え方で理解できるようになりました。

特に便利だと感じたのは、無敵時間の実装です。以前はフラグ管理で複雑になっていましたが、マスクのビットを切り替えるだけで実現できるようになりました。

また、プロジェクト設定でレイヤーに名前を付けられるのも大きな発見でした。数字ではなく意味のある名前で管理できるので、チーム開発でも理解しやすくなります。