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)Collider2DDamageableMarker2D
を付けておきます。これでプレイヤーも敵も「地雷のダメージ対象」になります。
② Mine プレハブの作成
- Hierarchy で 空の GameObject を作成し、名前を
Mineにする。 - コンポーネントを追加:
CircleCollider2D(Is Trigger にチェック)Mine(上記コード)- 見た目用に
SpriteRendererなどを付けてもOK
Mineコンポーネントのインスペクターで:- Target Layer に、プレイヤー・敵が属する Layer を設定(例:Default)。
- 必要に応じて Explosion Radius や Damage を調整。
- 爆発エフェクト用のプレハブがあれば Explosion Effect Prefab に割り当てる。
- この
Mineオブジェクトを Project ビューにドラッグしてプレハブ化し、シーン上の元オブジェクトは削除してOK。
③ MineLayer をプレイヤーに設定
- Player オブジェクトに
MineLayerコンポーネントを追加。 - インスペクターで:
- Mine Prefab に、さきほど作った Mine プレハブをドラッグ&ドロップ。
- Spawn Point は空でもOK(空の場合は
footOffsetを使って足元に出します)。 - Place Cooldown(例:0.3秒)、Max Mines(例:10個)などをお好みで設定。
- Foot Offset は、プレイヤーの足元に合わせて調整(例:Yを -0.5 ~ -1.0 くらい)。
④ Input System でボタンに割り当てる(プレイヤー例)
新InputSystemを使っている前提で、地雷設置ボタンを設定します。
- Input Actions アセットを開き、Player アクションマップなどに PlaceMine アクションを追加。
- Action Type:
Button - Binding:キーボードの
Qやゲームパッドのボタンなど。
- Action Type:
- Player オブジェクトに
PlayerInputコンポーネントを付け、- Actions に Input Actions アセットを設定。
- Behavior を
Send MessagesまたはInvoke Unity Eventsにする。
Send Messagesの場合:- アクション名
PlaceMineに対して、自動的にOnPlaceMineInputが呼ばれます。 MineLayerに用意したOnPlaceMineInputがそのまま使えます。
- アクション名
- ゲームを再生し、プレイヤーを動かしながら 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」側で拡張していく、という分担を意識して設計していきましょう。
