Unity 2D:旗に触れたとき「ゴール」が2回出る原因と対処

TL;DR

  • 原因:同一 Rigidbody2D 配下の**複数 Collider(例:Box+Circle)**が同じ旗 Trigger に当たり、各コライダー分だけ OnTriggerEnter2D が呼ばれる。
  • 解決(どれか一つ/併用可)
    1. CompositeCollider2D で統合(Used by Composite=Merge) → 発火1回
    2. レイヤーで片方を無視(片方を子に分離し、Collision Matrix で無効化)
    3. コードでワンショット制御(goalHit で一度だけ処理)
    4. CapsuleCollider2D を1枚に統一(Box+Circle を廃止して根本的に1ヒット化)

現象

  • 旗(Goal, IsTrigger=ON)に猫(Player)が触れると、「ゴール」ログが2回出る。
  • 猫の落下は発生していない

なぜ2回出るのか

Unity 2D の Trigger は、同じ Rigidbody2D にぶら下がる複数 Collider が同じ Trigger と重なれば、各コライダー分だけコールバックが届く仕様。

つまり Box と Circle の両方が旗に触れていると 2回呼ばれます。


対処法

1) CompositeCollider2D で統合(推奨)

効果:統合形状でイベントが1回だけ発生。設計もシンプル。

手順

  1. Player に Rigidbody2D と CompositeCollider2D を追加。
  2. 統合したい Box / Circle の Composite Operation を Merge(= Used by Composite) に。
    • 子コライダー側の IsTrigger は無視され、Composite 側の設定が使われます。
  3. CompositeCollider2D 推奨設定
    • Geometry: Outlines
    • Vertex Distance: 0.002〜0.005(細かすぎると頂点過多で重くなる)
    • Edge Radius: 0.01〜0.03(角の“噛み”緩和)

注意

  • どれか1枚でも Merge が外れていると、その1枚が独立に当たり二重再発
  • 別オブジェクトのコライダーが触れていないかも確認。

2) レイヤーで片方のコライダーを無視(設計分離)

効果:複数コライダーを残したまま、旗と接触するのは片方だけにできる。

ポイント

  • レイヤーは GameObject 単位。同一オブジェクト内でコライダーごとに分けられないため、無視したいコライダーを子オブジェクトへ分離する前準備が必要。

手順(概要)

  1. Add Layer… で例:PlayerMain(反応させる側)、PlayerSub(無視側)、Goal(旗)。
  2. Player の子に空オブジェクト SubCollider を作り、無視したい Collider を移設(Rigidbody2D は親だけ)。
  3. レイヤー割当:親=PlayerMain、子=PlayerSub、旗=Goal。
  4. Edit > Project Settings > Physics 2D > Layer Collision Matrix で PlayerSub × Goal を無効化。→ 旗に触れるのは PlayerMain だけ → 発火は1回

3) コードでワンショット制御(最小変更の保険)

設計をいじらず重複ログ/重複処理を防ぐ。将来の変更にも強い。

bool goalHit;

void OnTriggerEnter2D(Collider2D other)
{
    if (goalHit) return;
    if (!other.CompareTag("Goal")) return;

    goalHit = true; // 二重発火の保険
    Debug.Log("ゴール");
    UnityEngine.SceneManagement.SceneManager.LoadScene("ClearScene");
}

4) CapsuleCollider2D を1枚に統一(シンプル&軽量)

効果:発火源が1つになるため二重発火が原理的になくなる。設定も軽く保守性が高い。

手順

  1. Player の BoxCollider2D / CircleCollider2D を削除
  2. CapsuleCollider2D を1枚追加(Direction=Vertical, IsTrigger=OFF)。
  3. サイズ調整:幅は胴体に合わせ少し細め(スプライト幅の70〜85%目安)、高さは足元〜頭頂を覆う。
  4. 任意で PhysicsMaterial2D(Friction=0, Combine=Minimum)を付与。
  5. Rigidbody2D.constraints = FreezeRotation(姿勢安定)。
  6. 旗は IsTrigger=ON + Tag=“Goal”。OnTriggerEnter2D で判定。

メリット

  • 設計が単純・最も軽量・設定漏れが少ない
  • 丸端形状で段差に噛みにくい

デメリット

  • 胴体基準で当たりがやや太めに感じる場面あり(幅を細めに/補助 Trigger を追加)

目的

  • 物理の安定用“太め”コライダー(Capsule 1枚など)はそのまま残し、判定だけは“細く・局所的”に行うための専用センサーを持たせるためです。
  • これにより、
    • 旗やアイテムの**誤判定(早すぎ・遅すぎ・当たりすぎ)**を減らす
    • 地面用/頭上用/前方用など用途別に、検出対象を限定できる(Layer で分離)
    • 物理安定(段差・坂・押し出し)と精密判定トレードオフせず両立できる

旗判定での実装パターン

「メイン当たり=物理(太め)」「補助=判定(細め)」の二枚看板にします。

  1. 子オブジェクトに補助トリガーを追加
    • 子 GoalSensor を作成
    • BoxCollider2D(小さめ)を付与し Is Trigger = ON
    • 位置はプレイヤー前方に少しオフセット(例:offset.x = +0.15〜0.25)
  2. レイヤーを分ける(重要:重複発火を避ける)
    • 例:
      • メイン本体(Capsule)= PlayerBody
      • 補助トリガー(子)= PlayerSensor
      • 旗(Goal)= Goal
    • Physics 2D > Layer Collision Matrix
      • PlayerBody × Goal = OFF(無効)
      • PlayerSensor × Goal = ON(有効)こうすると、旗と接触するのは補助トリガーだけになり、発火は1回に統一されます。
  3. コード(親から処理する例)
// 子オブジェクト側(GoalSensor.cs)
using UnityEngine;

public class GoalSensor : MonoBehaviour
{
    PlayerController owner;

    void Awake()
    {
        owner = GetComponentInParent<PlayerController>();
    }

    void OnTriggerEnter2D(Collider2D other)
    {
        if (other.CompareTag("Goal"))
        {
            owner.OnGoalTriggered(); // 親の処理を呼ぶ
        }
    }
}

// 親(PlayerController.cs)
using UnityEngine;
using UnityEngine.SceneManagement;

public class PlayerController : MonoBehaviour
{
    bool goalHit;

    public void OnGoalTriggered()
    {
        if (goalHit) return;
        goalHit = true;
        Debug.Log("ゴール");
        SceneManager.LoadScene("ClearScene");
    }
}

ポイント

  • メイン Capsule は物理専用(Is Trigger=OFF)。
  • 旗との接触は補助トリガーだけが担当(行列で厳密化)。
  • これで「太めで安定な当たり」を維持しつつ、ゴール判定は細く正確にできます。

補助トリガーの典型パターン(応用)

  • 足元センサー:接地判定(IsGrounded)、坂・段差での安定
  • 頭上センサー:天井ヒット/ジャンプ抑止
  • 前方センサー:ゴール・スイッチ・会話開始・梯子/壁登り切替
  • 取得センサー:コイン/アイテムだけを拾う(敵や地形は無視)

どれも 対象レイヤーを限定(Matrix)し、不要なものとは衝突しないようにするのがコツです。


チェックリスト

  • 旗の Collider:IsTrigger=ONTag=“Goal”
  • (方式1)Composite:すべての統合対象で Merge=ON
  • (方式2)レイヤー:PlayerSub × Goal を無効化(子分離済み)
  • (方式3)goalHit でワンショット制御
  • (方式4)CapsuleCollider2D 1枚に統一(IsTrigger=OFF)
  • 余計なコライダーの混在なし(別子や別オブジェクトが触れていない)

まとめ

  • 二重発火は「複数コライダーが同じ Trigger に当たる」のが原因。
  • Composite 統合レイヤー無効化ワンショット制御Capsule 1枚化のいずれかで1ヒット化すれば解消。
  • プロジェクト方針や保守性に合わせて選び、必要なら複数を併用してください。
訪問数 14 回, 今日の訪問数 14回

Unity

Posted by hidepon