Unityを触り始めたころによくあるパターンとして、

  • Update() にプレイヤー操作
  • 敵AI
  • エフェクト再生
  • 地雷の設置と爆発処理

…と、全部まとめて書いてしまうことがあります。動きはするのですが、

  • ちょっと仕様を変えたいだけで巨大なスクリプトをスクロールしまくる
  • 「この処理どこでやってたっけ?」と探すのに時間がかかる
  • プレイヤー以外(敵やギミック)で同じ処理を再利用しづらい

といった問題が出がちですね。

そこでこの記事では、「地雷を設置する」という機能を 専用コンポーネント に切り出します。
プレイヤーでも敵でも「MineLayer」を付ければ地雷をばらまける、という状態にしておくと、プレハブ管理もレベルデザインもかなり楽になります。

【Unity】足元にポンポン地雷設置!「MineLayer」コンポーネント

ここでは、

  • 足元に「踏むと爆発するエリア(地雷)」を設置する MineLayer
  • 踏まれたら爆発してダメージを与える Mine

の2つのコンポーネントを用意します。どちらもUnity6(2023系以降)標準機能だけで動作します。

前提:2D物理での「踏む」判定

ユーザーからの説明にある「Area2D」は、Godotなどでよくある名前ですが、Unityでは Collider2D + Rigidbody2D の組み合わせで同等のことができます。この記事では、

  • 地雷:CircleCollider2D(IsTrigger = true)
  • 踏む側:Collider2D を持つプレイヤーや敵
  • 「踏んだ」判定:OnTriggerEnter2D

という構成で実装します。


フルコード:MineLayer & Mine


using UnityEngine;

/// <summary>地雷を踏めるオブジェクト(プレイヤーや敵)に付けるマーカー</summary>
public class DamageableMarker2D : MonoBehaviour
{
    // このクラス自体には何も書かなくてOK
    // 「地雷のダメージ対象である」ことを示すタグ代わりのコンポーネント
}


using UnityEngine;

/// <summary>
/// 2D用の地雷コンポーネント。
/// 踏まれると爆発し、一定半径内の DamageableMarker2D を持つオブジェクトにダメージを与える。
/// </summary>
[RequireComponent(typeof(CircleCollider2D))]
public class Mine : MonoBehaviour
{
    [Header("爆発設定")]
    [SerializeField] private float damage = 50f;          // 与えるダメージ量(ここではログ出力のみ)
    [SerializeField] private float explosionRadius = 1.5f; // 爆発半径(OverlapCircle用)
    [SerializeField] private LayerMask targetLayer;        // ダメージ対象のLayer(プレイヤーや敵など)

    [Header("見た目・演出")]
    [SerializeField] private GameObject explosionEffectPrefab; // 爆発エフェクト(任意)
    [SerializeField] private float lifeTimeAfterSpawn = 10f;   // 誰にも踏まれなかったときの自動消滅時間

    // すでに爆発したかどうか(多重爆発防止)
    private bool hasExploded = false;

    private void Reset()
    {
        // コンポーネント追加時に、地雷らしい設定にしておく
        var col = GetComponent<CircleCollider2D>();
        col.isTrigger = true;
        col.radius = 0.3f;
    }

    private void Start()
    {
        // 一定時間後に自動的に破壊(安全のため)
        if (lifeTimeAfterSpawn > 0f)
        {
            Destroy(gameObject, lifeTimeAfterSpawn);
        }
    }

    /// <summary>
    /// 誰かが地雷のトリガーに入ったときに呼ばれる
    /// </summary>
    /// <param name="other">侵入してきたCollider2D</param>
    private void OnTriggerEnter2D(Collider2D other)
    {
        // すでに爆発していたら何もしない
        if (hasExploded) return;

        // DamageableMarker2D を持つオブジェクトだけを対象にする
        var damageable = other.GetComponentInParent<DamageableMarker2D>();
        if (damageable == null)
        {
            // 踏めないオブジェクト(壁や弾など)は無視
            return;
        }

        Explode();
    }

    /// <summary>
    /// 爆発処理本体
    /// </summary>
    private void Explode()
    {
        if (hasExploded) return;
        hasExploded = true;

        // 爆発位置
        Vector2 center = transform.position;

        // 爆発エフェクトを生成(任意)
        if (explosionEffectPrefab != null)
        {
            Instantiate(explosionEffectPrefab, center, Quaternion.identity);
        }

        // OverlapCircle を使って爆発半径内のターゲットを取得
        Collider2D[] hits = Physics2D.OverlapCircleAll(center, explosionRadius, targetLayer);

        foreach (var hit in hits)
        {
            var damageable = hit.GetComponentInParent<DamageableMarker2D>();
            if (damageable == null) continue;

            // 実際にはここでHPコンポーネントなどにダメージを与える
            // 今回はサンプルとしてログ出力に留める
            Debug.Log($"Mine: {hit.name} に {damage} ダメージ!(仮)", hit);
        }

        // 爆発したら自分自身を破壊
        Destroy(gameObject);
    }

    private void OnDrawGizmosSelected()
    {
        // シーンビューで爆発半径を可視化
        Gizmos.color = new Color(1f, 0.5f, 0f, 0.4f);
        Gizmos.DrawWireSphere(transform.position, explosionRadius);
    }
}


using UnityEngine;
using UnityEngine.InputSystem;

/// <summary>
/// 足元に地雷を設置するコンポーネント。
/// プレイヤーでも敵でも、このコンポーネントと InputAction(またはAI)から呼び出せば地雷をばらまける。
/// </summary>
public class MineLayer : MonoBehaviour
{
    [Header("地雷プレハブ")]
    [SerializeField] private Mine minePrefab;          // 生成する地雷
    [SerializeField] private Transform spawnPoint;     // 地雷を出す位置(未指定なら自身の足元)

    [Header("設置制御")]
    [SerializeField] private float placeCooldown = 0.5f; // 連続設置のクールダウン
    [SerializeField] private int maxMines = 5;           // 同時に存在できる最大地雷数(0以下なら無制限)

    [Header("足元オフセット")]
    [SerializeField] private Vector2 footOffset = new Vector2(0f, -0.5f); // 足元の相対位置

    // 内部状態
    private float lastPlaceTime = -999f;
    private int currentMineCount = 0;

    /// <summary>
    /// Mine が破壊されたときに呼んでもらうためのイベント用デリゲート
    /// </summary>
    public System.Action OnMineDestroyed;

    private void Awake()
    {
        // Mine の破壊数カウント用にイベントを登録
        OnMineDestroyed += HandleMineDestroyed;
    }

    private void OnDestroy()
    {
        OnMineDestroyed -= HandleMineDestroyed;
    }

    /// <summary>
    /// 実際に地雷を設置する処理。
    /// 新InputSystemの InputAction から呼んだり、AIから呼んだりできるよう public にしている。
    /// </summary>
    public void PlaceMine()
    {
        if (minePrefab == null)
        {
            Debug.LogWarning("MineLayer: minePrefab が設定されていません。", this);
            return;
        }

        // クールダウンチェック
        if (Time.time - lastPlaceTime < placeCooldown)
        {
            return;
        }

        // 上限チェック(0以下なら無制限)
        if (maxMines > 0 && currentMineCount >= maxMines)
        {
            return;
        }

        // 生成位置を決定
        Vector3 spawnPos;
        if (spawnPoint != null)
        {
            spawnPos = spawnPoint.position;
        }
        else
        {
            spawnPos = transform.position + (Vector3)footOffset;
        }

        // 地雷を生成
        Mine mineInstance = Instantiate(minePrefab, spawnPos, Quaternion.identity);

        // Mine が破壊されたらカウントを減らすために、破壊監視用コンポーネントを付与
        var tracker = mineInstance.gameObject.AddComponent<MineLifetimeTracker>();
        tracker.Initialize(this);

        currentMineCount++;
        lastPlaceTime = Time.time;
    }

    /// <summary>
    /// Input System(新InputSystem)用のコールバックサンプル。
    /// InputAction の "performed" にこのメソッドを登録しておくと、ボタンで地雷を設置できる。
    /// </summary>
    /// <param name="context">InputAction.CallbackContext</param>
    public void OnPlaceMineInput(InputAction.CallbackContext context)
    {
        // ボタンが押された瞬間だけ反応する
        if (context.performed)
        {
            PlaceMine();
        }
    }

    /// <summary>
    /// MineLifetimeTracker から呼ばれるコールバック。
    /// </summary>
    private void HandleMineDestroyed()
    {
        currentMineCount = Mathf.Max(0, currentMineCount - 1);
    }

    /// <summary>
    /// Mineの破壊を検知するための内部クラス。
    /// </summary>
    private class MineLifetimeTracker : MonoBehaviour
    {
        private MineLayer owner;

        public void Initialize(MineLayer mineLayer)
        {
            owner = mineLayer;
        }

        private void OnDestroy()
        {
            // Mine が破壊されたときに MineLayer に通知
            if (owner != null)
            {
                owner.OnMineDestroyed?.Invoke();
            }
        }
    }
}


使い方の手順

ここからは、具体的なセットアップ手順を説明します。例として「プレイヤーが足元に地雷を設置し、敵が踏むと爆発する」ケースを想定します。

① プレイヤーと敵の基本セットアップ

  • 2Dプロジェクトでシーンを開く。
  • Player GameObject を用意して、少なくとも以下を付けます:
    • Rigidbody2D(Dynamic)
    • Collider2D(例:CapsuleCollider2D)
    • DamageableMarker2D(上記コードのコンポーネント)
  • Enemy GameObject も同様に作成し、
    • Rigidbody2D(Dynamic)
    • Collider2D
    • DamageableMarker2D

    を付けておきます。これでプレイヤーも敵も「地雷のダメージ対象」になります。

② Mine プレハブの作成

  1. Hierarchy で 空の GameObject を作成し、名前を Mine にする。
  2. コンポーネントを追加:
    • CircleCollider2D(Is Trigger にチェック)
    • Mine(上記コード)
    • 見た目用に SpriteRenderer などを付けてもOK
  3. Mine コンポーネントのインスペクターで:
    • Target Layer に、プレイヤー・敵が属する Layer を設定(例:Default)。
    • 必要に応じて Explosion RadiusDamage を調整。
    • 爆発エフェクト用のプレハブがあれば Explosion Effect Prefab に割り当てる。
  4. この Mine オブジェクトを Project ビューにドラッグしてプレハブ化し、シーン上の元オブジェクトは削除してOK。

③ MineLayer をプレイヤーに設定

  1. Player オブジェクトに MineLayer コンポーネントを追加。
  2. インスペクターで:
    • Mine Prefab に、さきほど作った Mine プレハブをドラッグ&ドロップ。
    • Spawn Point は空でもOK(空の場合は footOffset を使って足元に出します)。
    • Place Cooldown(例:0.3秒)、Max Mines(例:10個)などをお好みで設定。
    • Foot Offset は、プレイヤーの足元に合わせて調整(例:Yを -0.5 ~ -1.0 くらい)。

④ Input System でボタンに割り当てる(プレイヤー例)

新InputSystemを使っている前提で、地雷設置ボタンを設定します。

  1. Input Actions アセットを開き、Player アクションマップなどに PlaceMine アクションを追加。
    • Action Type:Button
    • Binding:キーボードの Q やゲームパッドのボタンなど。
  2. Player オブジェクトに PlayerInput コンポーネントを付け、
    • Actions に Input Actions アセットを設定。
    • Behavior を Send Messages または Invoke Unity Events にする。
  3. Send Messages の場合:
    • アクション名 PlaceMine に対して、自動的に OnPlaceMineInput が呼ばれます。
    • MineLayer に用意した OnPlaceMineInput がそのまま使えます。
  4. ゲームを再生し、プレイヤーを動かしながら PlaceMine ボタンを押すと、足元に地雷が設置され、敵やプレイヤーが踏むと爆発してログが出るはずです。

同じように、敵側に MineLayer を付けて、AIスクリプトから PlaceMine() を呼ぶようにすれば、「地雷をばらまく敵」も簡単に作れます。


メリットと応用

このように「MineLayer」「Mine」「DamageableMarker2D」を小さなコンポーネントに分けておくと、

  • プレイヤーの移動ロジックと地雷ロジックが分離されるので、バグ調査や仕様変更がしやすい。
  • 地雷を使いたいオブジェクトに MineLayer をポン付けするだけでOK。プレハブの再利用性が上がる。
  • 「踏める対象」を DamageableMarker2D でマークしているので、敵だけ踏める地雷プレイヤーだけ踏める地雷なども簡単に作れる。
  • レベルデザイナーは「Mine プレハブをステージに直接置く」こともできるし、「MineLayer を持つ敵を配置して、動的に地雷をばらまかせる」こともできる。

特にレベルデザイン面では、

  • 「このステージは地雷の最大数を増やして弾幕っぽくしたい」→ MineLayer.maxMines をプレハブ単位で変えるだけ。
  • 「このエリアだけ爆発半径を広げたい」→ そのエリア用の Mine プレハブを1つ複製して explosionRadius を変更。

といった調整が、コードを触らずにインスペクターだけで完結します。

改造案:プレイヤーだけ踏める地雷にする

例えば「プレイヤーだけに反応する地雷」を作りたい場合、Mine に以下のような簡単なフィルタ処理を追加できます。


[SerializeField] private bool onlyPlayer = false;
[SerializeField] private string playerTag = "Player";

private bool IsValidTarget(Collider2D other)
{
    // DamageableMarker2D を持っていなければ対象外
    var damageable = other.GetComponentInParent<DamageableMarker2D>();
    if (damageable == null) return false;

    // プレイヤー限定モードなら Tag で絞り込む
    if (onlyPlayer)
    {
        if (!other.CompareTag(playerTag) &&
            (other.attachedRigidbody == null || !other.attachedRigidbody.CompareTag(playerTag)))
        {
            return false;
        }
    }

    return true;
}

private void OnTriggerEnter2D(Collider2D other)
{
    if (hasExploded) return;
    if (!IsValidTarget(other)) return;

    Explode();
}

このように、小さなコンポーネントに分けておくと、仕様変更や派生ギミックの追加がしやすくなります。
「MineLayer」は「地雷を置く」という1つの責務に集中させておき、爆発の挙動やダメージの仕様は「Mine」側で拡張していく、という分担を意識して設計していきましょう。