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})");
        }
    }
}

使い方の手順

  1. アイテム用プレハブを用意する
    例として「コイン」をドロップする場合:
    • シーン上にコイン用のGameObjectを作成(SpriteRenderer / MeshRenderer などで見た目をつける)
    • 必要なら RigidbodyCollider、「拾える処理」のスクリプトをアタッチ
    • Projectビューにドラッグ&ドロップしてプレハブ化し、シーン上の元オブジェクトは削除
  2. 敵や壊れるオブジェクトに LootDropper をアタッチ
    例: 敵キャラクターに設定する場合:
    • 敵用のプレハブ(Enemy)を開く
    • Add Component から LootDropper を追加
    • LootPrefab に手順①で作ったコインプレハブをドラッグ&ドロップ
    • Drop Chance を 0.3(30%)など好みの値に設定
    • Min Drop CountMax Drop Count で個数レンジを設定
      • 例: 最小1 / 最大3 → 1〜3個のコインをランダムでドロップ
      • 例: 最小3 / 最大3 → 必ず3個ドロップ
    • 「その場にきっちり1個だけ落としてほしい」なら Use Random Offset をオフにする
  3. 親オブジェクトを 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);
        }
    }
    
  4. プレイして挙動を確認する
    敵を攻撃して倒したときに、指定した確率・個数でコインが出現すれば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 のようなコンポーネントに切り出してみてください。