Unityを触り始めたころは、つい何でもかんでも Update() に書いてしまいがちですよね。
「プレイヤーがエリアに入ったらイベントを起動したい」みたいな処理も、毎フレーム Vector3.Distance を測って判定していたり、巨大なプレイヤースクリプトに「当たり判定+UI+サウンド+ゲーム進行」を全部詰め込んでしまったり…。
そうなると、プレイヤーのコードを少し変えるだけでゲーム全体が壊れたり、シーンごとに条件が違ってきてメンテ不能になってしまいます。

そこでこの記事では、「エリアに入ったら何かが起きる」というよくある処理を、小さなコンポーネントとして切り出します。
今回作る AreaTrigger コンポーネントは、親オブジェクト(Area2D)にプレイヤーが入った瞬間にイベントを発火し、自動で消滅するシンプルなトリガーです。

【Unity】エリア侵入でイベント発火&自壊!「AreaTrigger」コンポーネント

ここでは Unity6(C#)で使える、AreaTrigger のフルコードを紹介します。
コンセプトは:

  • プレイヤーがエリアに入った瞬間だけ反応する(1回きり)
  • イベントは UnityEvent でインスペクターから自由に設定
  • 処理が終わったら自分自身(トリガー)を破棄してスッキリ
  • 「親(Area2D)」のコライダーを使う前提で設計

前提ルール

  • プレイヤーには Collider(2Dなら Collider2D)と Rigidbody が付いていること
  • プレイヤーには Player というタグを付ける(タグ名はコードで変更可能)
  • エリア側(Area2D)は トリガー(IsTrigger = true)のコライダーを持つ

AreaTrigger.cs(3D/2D両対応版フルコード)


using UnityEngine;
using UnityEngine.Events;

/// <summary>
/// 親オブジェクト(Area2D など)にプレイヤーが侵入したら
/// UnityEvent を発火し、その後自動で自分自身を破棄するトリガー。
/// 
/// ・2D/3D の両方に対応
/// ・プレイヤー判定はタグで行う(デフォルト "Player")
/// ・イベントはインスペクターから設定可能
/// 
/// このコンポーネント自体にはコライダーは不要。
/// 親側に IsTrigger = true の Collider / Collider2D を付けて使うことを想定。
/// </summary>
[DisallowMultipleComponent]
public class AreaTrigger : MonoBehaviour
{
    [Header("プレイヤー判定")]
    [SerializeField]
    [Tooltip("プレイヤーを識別するタグ名。プレイヤー側に同じタグを設定してください。")]
    private string playerTag = "Player";

    [Header("イベント設定")]
    [SerializeField]
    [Tooltip("プレイヤーがエリアに入った瞬間に呼ばれるイベント(1回きり)。")]
    private UnityEvent onPlayerEnter;

    [Header("動作オプション")]
    [SerializeField]
    [Tooltip("一度発火したらこのトリガーを自動で破棄するかどうか。")]
    private bool destroyAfterTriggered = true;

    [SerializeField]
    [Tooltip("トリガー発火後、破棄までの遅延時間(秒)。0 なら即時破棄。")]
    private float destroyDelay = 0f;

    // すでにトリガーされているかどうかのフラグ
    private bool _hasTriggered = false;

    // --- 2D/3D どちらのエリアにも対応するため、両方のコールバックを実装 ---

    /// <summary>
    /// 3D コライダーのトリガー侵入時に呼ばれる。
    /// 親の Collider (IsTrigger) に Rigidbody を付けた場合はこちらが呼ばれる。
    /// </summary>
    /// <param name="other">侵入してきた Collider</param>
    private void OnTriggerEnter(Collider other)
    {
        TryTrigger(other.gameObject);
    }

    /// <summary>
    /// 2D コライダーのトリガー侵入時に呼ばれる。
    /// 親の Collider2D (IsTrigger) に Rigidbody2D を付けた場合はこちらが呼ばれる。
    /// </summary>
    /// <param name="other">侵入してきた Collider2D</param>
    private void OnTriggerEnter2D(Collider2D other)
    {
        TryTrigger(other.gameObject);
    }

    /// <summary>
    /// 共通のトリガー処理。
    /// 2D/3D どちらの OnTriggerEnter からもここを呼ぶ。
    /// </summary>
    /// <param name="otherGameObject">侵入してきたオブジェクト</param>
    private void TryTrigger(GameObject otherGameObject)
    {
        // すでにトリガー済みなら何もしない(多重発火防止)
        if (_hasTriggered)
        {
            return;
        }

        // タグでプレイヤーかどうかを判定
        if (!otherGameObject.CompareTag(playerTag))
        {
            return;
        }

        // ここまで来たらプレイヤーが侵入したとみなす
        _hasTriggered = true;

        // デバッグログ(必要なければ削除してOK)
        Debug.Log($"[AreaTrigger] Player entered area: {gameObject.name}", this);

        // UnityEvent を発火
        if (onPlayerEnter != null)
        {
            onPlayerEnter.Invoke();
        }

        // 指定されていれば自動で破棄
        if (destroyAfterTriggered)
        {
            // destroyDelay が 0 なら即時、そうでなければ遅延破棄
            if (destroyDelay <= 0f)
            {
                Destroy(gameObject);
            }
            else
            {
                Destroy(gameObject, destroyDelay);
            }
        }
    }

    // --- インスペクターからも呼びやすいテスト用メソッド ---

    /// <summary>
    /// エディタ上で動作確認したいときに便利な、強制トリガーメソッド。
    /// インスペクターのコンテキストメニューから実行できます。
    /// </summary>
    [ContextMenu("Test Trigger (Editor Only)")]
    private void TestTrigger()
    {
        Debug.Log("[AreaTrigger] TestTrigger() called in Editor.", this);

        if (onPlayerEnter != null)
        {
            onPlayerEnter.Invoke();
        }

        if (destroyAfterTriggered)
        {
            if (destroyDelay <= 0f)
            {
                DestroyImmediate(gameObject);
            }
            else
            {
                // DestroyImmediate には遅延が指定できないのでログだけ出しておく
                Debug.LogWarning("[AreaTrigger] destroyDelay は TestTrigger では無視されます。", this);
                DestroyImmediate(gameObject);
            }
        }
    }
}

使い方の手順

① プレイヤーにタグとコライダーを設定する

  • プレイヤーの GameObject(例: Player)を選択
  • TagPlayer に設定(存在しなければ「Add Tag…」から追加)
  • Rigidbody(3D)または Rigidbody2D(2D)をアタッチ
  • CapsuleCollider / BoxCollider2D など、適切なコライダーをアタッチ

※ プレイヤーには IsTrigger = false の通常コライダーでOKです。

② エリア用オブジェクト(Area2D)を作る

例として「ボス部屋の入口エリア」を作る場合:

  1. シーン上で Empty Object を作成し、名前を Area2D_BossEntrance などにする
  2. 位置とスケールを、プレイヤーが通過するエリアに合わせて調整
  3. 3Dの場合:
    • BoxCollider をアタッチ
    • Is Trigger にチェックを入れる
  4. 2Dの場合:
    • BoxCollider2D などをアタッチ
    • Is Trigger にチェックを入れる

このエリアオブジェクトが、コード上でいう「親(Area2D)」というイメージです。

③ AreaTrigger コンポーネントを追加してイベントを設定

  1. エリアオブジェクト(Area2D_BossEntrance)を選択
  2. Add Component > AreaTrigger を追加
  3. インスペクターで以下を設定:
    • Player Tag:プレイヤーに付けたタグ(デフォルトで Player
    • Destroy After Triggered:1回きりで良ければチェックON
    • Destroy Delay:演出のために少し残したければ 0.5 などを指定
  4. On Player Enter のイベント欄で、発火させたい処理を登録:
    • 「+」ボタンを押す
    • シーン上の任意の GameObject をドラッグ&ドロップ
    • ドロップダウンから呼び出したいメソッドを選択(例:BossManager.StartBossBattle

④ 具体的な使用例

例1:プレイヤーが入ったらボス戦開始
  1. BossManager というオブジェクトを用意し、以下のようなスクリプトを付ける:

using UnityEngine;

public class BossManager : MonoBehaviour
{
    [SerializeField] private GameObject bossUI;
    [SerializeField] private GameObject bossEnemy;

    public void StartBossBattle()
    {
        Debug.Log("Boss Battle Started!");

        if (bossUI != null)
        {
            bossUI.SetActive(true);
        }

        if (bossEnemy != null)
        {
            bossEnemy.SetActive(true);
        }
    }
}

そのうえで、Area2D_BossEntranceOn Player EnterBossManager.StartBossBattle を登録します。
プレイヤーがエリアに入った瞬間にボス戦が始まり、AreaTrigger は自動で消えるので、同じエリアに出入りしても二度と起動しません。

例2:動く床の開始トリガー

動く床(MovingPlatform)を制御するスクリプト側に、例えば BeginMove() というメソッドを用意しておき、
プレイヤーがエリアに入ったらその床が動き出すようにする、というのも簡単です。

  • 動く床のスクリプトに public void BeginMove() を用意
  • エリアの On Player Enter にそのメソッドを登録

レベルデザイナーは「この床はこのエリアに入ったら動き出す」という関係を、
コードを書かずにインスペクターだけで組めるようになります。


メリットと応用

メリット1:プレイヤーコードが痩せる

「エリアに入ったら何かする」という処理をすべてプレイヤー側に書いてしまうと、
プレイヤーのスクリプトがあっという間に God クラス化します。

AreaTrigger を使えば、

  • プレイヤーは「普通に動くだけ」
  • エリア側が「入ったら何をするか」を決める

という責務分離ができて、プレイヤーコードがスリムになります。

メリット2:プレハブ化しやすく、レベルデザインが楽

例えば:

  • 「セーブポイントエリア」プレハブ
  • 「チュートリアルポップアップエリア」プレハブ
  • 「敵増援スポーンエリア」プレハブ

といった形で、エリア+AreaTrigger+イベント設定済み のプレハブをいくつか作っておけば、
ステージ上にポンポン配置するだけで、ゲーム進行の組み立てができます。

メリット3:イベント内容をコードに縛られずに変更できる

イベント本体は UnityEvent にしているので、

  • シーンごとに違う演出を差し込みたい
  • 同じエリアでもビルドごとに挙動を変えたい

といった場合でも、コードを一切触らずインスペクターだけで差し替え可能です。

応用・改造案:一度だけではなく、回数制限付きトリガーにする

「1回きり」ではなく、「最大3回まで発火してほしい」というようなケースもありますね。
その場合は、以下のようなメソッドを追加して、_hasTriggered の代わりに回数カウンタを使う形に改造できます。


[SerializeField]
[Tooltip("このトリガーが有効な最大発火回数。0 以下なら無制限。")]
private int maxTriggerCount = 1;

private int _currentTriggerCount = 0;

private bool CanTrigger()
{
    // maxTriggerCount <= 0 の場合は無制限
    if (maxTriggerCount <= 0)
    {
        return true;
    }

    // まだ上限に達していなければ発火可能
    return _currentTriggerCount < maxTriggerCount;
}

private void TryTriggerWithLimit(GameObject otherGameObject)
{
    if (!otherGameObject.CompareTag(playerTag))
    {
        return;
    }

    if (!CanTrigger())
    {
        return;
    }

    _currentTriggerCount++;

    if (onPlayerEnter != null)
    {
        onPlayerEnter.Invoke();
    }

    // 上限に達したタイミングで破棄
    if (destroyAfterTriggered && !CanTrigger())
    {
        Destroy(gameObject);
    }
}

このように、小さなコンポーネントとして分割しておくと、
「1回きり」「回数制限付き」「一定時間だけ有効」などのバリエーションも、
それぞれシンプルな責務を持つコンポーネントとして増やしていけます。

巨大な GameManager にすべての進行を書き込むのではなく、
こういった小さなトリガーを組み合わせてゲームを構成していくと、
保守性の高いプロジェクトになっていきますね。