Unityを始めたばかりの頃、敵の死亡処理やアイテムドロップの処理を、ついなんでもかんでも Update() に書いてしまいがちですよね。
「HPが0になったらここでDestroyして…そのときアイテムを落として…スコアも増やして…」と1つのスクリプトに全部詰め込むと、あっという間に数百行のGodクラスが出来上がってしまいます。
こうなると、
- 敵の種類が増えるたびに巨大スクリプトを編集する必要がある
- どこで何が起きているのか追いにくく、バグ修正がつらい
- 「この敵はアイテム落とさないでほしい」といった調整が面倒
といった問題が出てきます。
そこでこの記事では、「敵が消える直前に、確率でアイテムをドロップする」という機能だけを切り出した、小さなコンポーネント 「LootDropper」 を作っていきます。
敵の死亡処理は敵のスクリプトに任せて、「消えるときに何かを落とすかどうか」は LootDropper に任せる、という役割分担ですね。
【Unity】消える瞬間だけ仕事する!「LootDropper」コンポーネント
ここでは「親が破棄される直前(OnDestroy)」にフックして、指定した確率でアイテムプレハブをその場に生成する LootDropper を作ります。
「queue_free」はGodotの用語ですが、Unityでは Destroy(gameObject) がそれに相当します。
LootDropper フルコード
using UnityEngine;
/// <summary>
/// 親オブジェクトがDestroyされる直前に、確率でアイテムをその場に生成するコンポーネント。
/// - 敵、壊れるオブジェクト、宝箱などにアタッチして使う。
/// - 「何を」「どれくらいの確率で」「いくつ」落とすかをインスペクターから設定可能。
/// </summary>
public class LootDropper : MonoBehaviour
{
[Header("ドロップ対象プレハブ")]
[Tooltip("Destroy直前に生成したいアイテムのプレハブ。\n" +
"例: コイン、回復アイテム、武器など")]
[SerializeField] private GameObject lootPrefab;
[Header("ドロップ確率・個数設定")]
[Tooltip("アイテムをドロップする確率(0〜1)。\n" +
"例: 0.25 = 25% の確率でドロップ")]
[Range(0f, 1f)]
[SerializeField] private float dropChance = 0.5f;
[Tooltip("ドロップするアイテムの最小数。\n" +
"最大数と同じにすれば固定個数ドロップになります。")]
[Min(0)]
[SerializeField] private int minDropCount = 1;
[Tooltip("ドロップするアイテムの最大数。\n" +
"最小数より小さくしないでください。")]
[Min(0)]
[SerializeField] private int maxDropCount = 1;
[Header("位置・ランダムオフセット")]
[Tooltip("true の場合、親オブジェクトの位置を基準に、\n" +
"ランダムなオフセットを加えてドロップします。")]
[SerializeField] private bool useRandomOffset = true;
[Tooltip("ランダムオフセットの範囲。\n" +
"X, Y, Z それぞれ -value〜+value の範囲でランダムにずらします。")]
[SerializeField] private Vector3 randomOffsetRange = new Vector3(0.5f, 0f, 0.5f);
[Header("向き設定")]
[Tooltip("true の場合、lootPrefab の回転をそのまま使います。\n" +
"false の場合、親オブジェクトの回転を引き継ぎます。")]
[SerializeField] private bool keepPrefabRotation = true;
[Header("デバッグ & 制御")]
[Tooltip("Destroy時にログを出すかどうか。")]
[SerializeField] private bool enableDebugLog = false;
[Tooltip("ゲーム中に一時的にドロップを無効化したい場合に使います。")]
[SerializeField] private bool isEnabled = true;
// Destroyの原因を区別したい場合に使うフラグ
// (例: シーン遷移時のDestroyではドロップさせない など)
private bool _skipDropOnce = false;
/// <summary>
/// 外部から一時的にドロップをスキップしたいときに呼ぶ関数。
/// 例: シーン遷移直前に呼んで、不要なドロップを防ぐ。
/// </summary>
public void SkipNextDrop()
{
_skipDropOnce = true;
}
/// <summary>
/// Unity標準のライフサイクルイベント。
/// このコンポーネント、またはアタッチされたGameObjectがDestroyされる直前に呼ばれます。
/// </summary>
private void OnDestroy()
{
// アプリケーション終了時はドロップしない(不要な生成を防ぐ)
if (Application.isPlaying == false)
{
return;
}
// 無効化されている場合や、一度だけスキップ指定がある場合は何もしない
if (!isEnabled || _skipDropOnce)
{
if (enableDebugLog)
{
Debug.Log($"[LootDropper] ドロップはスキップされました ({gameObject.name})");
}
return;
}
// プレハブが設定されていない場合は警告を出して終了
if (lootPrefab == null)
{
if (enableDebugLog)
{
Debug.LogWarning($"[LootDropper] lootPrefab が設定されていません ({gameObject.name})");
}
return;
}
// 確率判定
float roll = Random.value; // 0〜1のランダム
if (roll > dropChance)
{
if (enableDebugLog)
{
Debug.Log($"[LootDropper] ドロップ失敗 roll={roll:F2}, chance={dropChance:F2} ({gameObject.name})");
}
return;
}
// 最小・最大値の補正(編集ミス対策)
int min = Mathf.Max(0, minDropCount);
int max = Mathf.Max(min, maxDropCount);
// ドロップ個数を決定
int dropCount = Random.Range(min, max + 1); // maxを含むよう +1
if (dropCount <= 0)
{
if (enableDebugLog)
{
Debug.Log($"[LootDropper] ドロップ個数が0のため何も生成しません ({gameObject.name})");
}
return;
}
// 実際にアイテム生成
for (int i = 0; i < dropCount; i++)
{
SpawnLoot(i);
}
if (enableDebugLog)
{
Debug.Log($"[LootDropper] アイテムを {dropCount} 個ドロップしました ({gameObject.name})");
}
}
/// <summary>
/// 実際にアイテムを1つ生成する処理。
/// </summary>
/// <param name="index">何個目のドロップか(デバッグ用)</param>
private void SpawnLoot(int index)
{
// 基準位置は親オブジェクトの位置
Vector3 spawnPosition = transform.position;
// ランダムオフセットを使う場合は加算
if (useRandomOffset)
{
Vector3 offset = new Vector3(
Random.Range(-randomOffsetRange.x, randomOffsetRange.x),
Random.Range(-randomOffsetRange.y, randomOffsetRange.y),
Random.Range(-randomOffsetRange.z, randomOffsetRange.z)
);
spawnPosition += offset;
}
// 回転の決定
Quaternion rotation = keepPrefabRotation
? lootPrefab.transform.rotation // プレハブの向きをそのまま使う
: transform.rotation; // 親オブジェクトの向きを引き継ぐ
// 実際に生成
GameObject lootInstance = Instantiate(lootPrefab, spawnPosition, rotation);
if (enableDebugLog)
{
Debug.Log($"[LootDropper] アイテム生成 index={index}, position={spawnPosition} ({lootInstance.name})");
}
}
}
使い方の手順
-
アイテム用プレハブを用意する
例として「コイン」をドロップする場合:- シーン上にコイン用のGameObjectを作成(SpriteRenderer / MeshRenderer などで見た目をつける)
- 必要なら
RigidbodyやCollider、「拾える処理」のスクリプトをアタッチ - Projectビューにドラッグ&ドロップしてプレハブ化し、シーン上の元オブジェクトは削除
-
敵や壊れるオブジェクトに LootDropper をアタッチ
例: 敵キャラクターに設定する場合:- 敵用のプレハブ(Enemy)を開く
Add ComponentからLootDropperを追加LootPrefabに手順①で作ったコインプレハブをドラッグ&ドロップDrop Chanceを 0.3(30%)など好みの値に設定Min Drop CountとMax Drop Countで個数レンジを設定- 例: 最小1 / 最大3 → 1〜3個のコインをランダムでドロップ
- 例: 最小3 / 最大3 → 必ず3個ドロップ
- 「その場にきっちり1個だけ落としてほしい」なら
Use Random Offsetをオフにする
-
親オブジェクトを Destroy する処理を書く
敵のHPが0になったときにDestroy(gameObject)すれば、そのタイミングでLootDropperが自動的に発動します。
例: とてもシンプルな敵のスクリプト:using UnityEngine; public class SimpleEnemy : MonoBehaviour { [SerializeField] private int maxHp = 3; private int _currentHp; private void Awake() { _currentHp = maxHp; } // 外部からダメージを受ける想定の関数 public void TakeDamage(int damage) { _currentHp -= damage; if (_currentHp <= 0) { Die(); } } private void Die() { // ここで敵を消すだけ。LootDropper が付いていれば // OnDestroy で自動的にドロップ判定が走ります。 Destroy(gameObject); } } -
プレイして挙動を確認する
敵を攻撃して倒したときに、指定した確率・個数でコインが出現すればOKです。
挙動を詳しく見たいときはEnable Debug Logをオンにして、Consoleに出るログを確認しましょう。
同じ手順で、
- 壊れる木箱に
LootDropperを付けて「ランダムで回復アイテム」 - ボスに
LootDropperを付けて「必ずレア武器を1つドロップ」 - 動く床やギミックオブジェクトに付けて「消えるときに足場アイテムを残す」
といった使い方もできます。
メリットと応用
LootDropper をコンポーネントとして分離しておくと、プレハブ管理やレベルデザインがかなり楽になります。
- 敵のスクリプトがシンプルになる
敵は「いつ死ぬか」だけを管理し、「死んだときに何を落とすか」はLootDropperに丸投げできます。
「この敵だけドロップ率を上げたい」「この敵は何も落とさないでほしい」といった調整も、インスペクターの値を変えるだけで完結します。 - プレハブ単位でドロップ設定を変えられる
同じ敵スクリプトを使っていても、
「通常敵プレハブ」にはコインを30%でドロップ、
「レア敵プレハブ」にはコインを80%+回復アイテムを20%でドロップ、
といった構成も、プレハブを複製してLootDropperの設定を変えるだけで実現できます。 - レベルデザイナーがコードを書かずに調整できる
企画・レベルデザイナーがUnityエディタ上でプレハブを配置し、LootDropperのパラメータを触るだけで、ドロップバランスをガンガン調整できます。
プログラマは「ドロップの仕組み」を作るだけで済み、「この敵のドロップ率を5%上げてください」といった微調整対応から解放されます。
さらに、応用として以下のような改造も簡単です。
- ドロップテーブル(複数種類のアイテムからランダム選択)を持たせる
- 敵のレベルやプレイヤーの状態によってドロップ率を変える
- シーン遷移時やリスタート時にはドロップさせない制御を入れる
例えば、「プレイヤーの所持している『幸運値』によってドロップ率を動的に変える」改造案はこんな感じで追加できます。
/// <summary>
/// 外部から「幸運値」を渡してドロップ率を一時的に補正する例。
/// 例: 幸運値0〜100で、最大+20%までドロップ率アップ。
/// </summary>
public void ApplyLuckBonus(int luckValue)
{
// 0〜1に正規化
float t = Mathf.Clamp01(luckValue / 100f);
// 最大+0.2 (20%) までボーナス
float bonus = 0.2f * t;
float newChance = Mathf.Clamp01(dropChance + bonus);
if (enableDebugLog)
{
Debug.Log($"[LootDropper] Luck補正: base={dropChance:F2}, bonus={bonus:F2}, result={newChance:F2}");
}
dropChance = newChance;
}
このように、小さな責務に分割されたコンポーネントを積み重ねていくと、プロジェクト全体が見通しやすくなり、後からの拡張やバランス調整もしやすくなります。
敵の死亡処理にドロップ処理をベタ書きしていたら、ぜひ LootDropper のようなコンポーネントに切り出してみてください。
