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)を選択 - Tag を
Playerに設定(存在しなければ「Add Tag…」から追加) Rigidbody(3D)またはRigidbody2D(2D)をアタッチCapsuleCollider/BoxCollider2Dなど、適切なコライダーをアタッチ
※ プレイヤーには IsTrigger = false の通常コライダーでOKです。
② エリア用オブジェクト(Area2D)を作る
例として「ボス部屋の入口エリア」を作る場合:
- シーン上で Empty Object を作成し、名前を
Area2D_BossEntranceなどにする - 位置とスケールを、プレイヤーが通過するエリアに合わせて調整
-
3Dの場合:
BoxColliderをアタッチ- Is Trigger にチェックを入れる
-
2Dの場合:
BoxCollider2Dなどをアタッチ- Is Trigger にチェックを入れる
このエリアオブジェクトが、コード上でいう「親(Area2D)」というイメージです。
③ AreaTrigger コンポーネントを追加してイベントを設定
- エリアオブジェクト(
Area2D_BossEntrance)を選択 - Add Component >
AreaTriggerを追加 -
インスペクターで以下を設定:
- Player Tag:プレイヤーに付けたタグ(デフォルトで
Player) - Destroy After Triggered:1回きりで良ければチェックON
- Destroy Delay:演出のために少し残したければ 0.5 などを指定
- Player Tag:プレイヤーに付けたタグ(デフォルトで
-
On Player Enter のイベント欄で、発火させたい処理を登録:
- 「+」ボタンを押す
- シーン上の任意の GameObject をドラッグ&ドロップ
- ドロップダウンから呼び出したいメソッドを選択(例:
BossManager.StartBossBattle)
④ 具体的な使用例
例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_BossEntrance の On Player Enter に BossManager.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 にすべての進行を書き込むのではなく、
こういった小さなトリガーを組み合わせてゲームを構成していくと、
保守性の高いプロジェクトになっていきますね。
