スポンサーリンク
エラー・トラブルシューティング

UnityのMissingReferenceExceptionの原因と対処法|Destroy後の参照エラーを解決

エラー・トラブルシューティング

Unityでゲーム制作をしていると、ある日こんなエラーに遭遇することがあります。

MissingReferenceException:
The object of type 'Enemy' has been destroyed but you are still trying to access it.

最初に見ると、「え、nullじゃないの?」「Destroyしただけなのに?」とかなり混乱しやすいんですよね。

しかもこのエラー、厄介なのが“最初は普通に動いていた”ケースが多いことです。

  • 敵を倒した瞬間だけエラーが出る
  • シーン切り替え後に突然壊れる
  • 数秒後にCoroutineで落ちる
  • Listをループしていたら急にMissingになる

こういう症状が出ると、「とりあえずnullチェック」で乗り切りたくなる気持ちも分かります🙂

ただ、MissingReferenceExceptionは“参照の寿命管理”が原因になっていることが多く、表面的な修正だけだと再発しやすいです。

特にUnityは、普通のC#とは少し違う「Destroy済みオブジェクト」の扱いをするので、初心者ほど混乱しやすいポイントでもあります。

私も最初の頃、EnemyをDestroyしたあとに追尾処理だけ残っていて、「なんでnullじゃないのに壊れるの…?」とかなり悩みました。

このエラーは、仕組みを一度理解してしまうと原因を切り分けやすくなります。

この記事では、

  • MissingReferenceExceptionの意味
  • NullReferenceExceptionとの違い
  • Destroy後に参照が残る理由
  • Coroutine・List・イベントで再発しやすいケース
  • 再発防止の考え方

を、実際によくある失敗パターンと一緒に整理していきます。


  1. MissingReferenceExceptionとは?
    1. NullReferenceExceptionとの違い
    2. Destroyした直後でも変数が残る理由
  2. MissingReferenceExceptionが発生しやすい原因4選
    1. Destroy後に別スクリプトから参照している
    2. Coroutineが破棄後も動いている
    3. Listや配列にDestroy済みオブジェクトが残っている
    4. イベント解除漏れで破棄済みオブジェクトが呼ばれる
  3. MissingReferenceExceptionの対処法
    1. まずはエラー行を確認する
    2. オブジェクト使用前にnullチェックする
    3. TryGetComponentを使って安全に取得する
    4. List内のMissing参照を定期的に掃除する
    5. Destroyする責任を整理する
  4. Destroyのタイミングを理解すると再発しにくい
    1. Destroyはその場で即削除されるわけではない
    2. 「今は動く」が次フレームで壊れる
    3. DestroyImmediateは基本的に通常ゲーム処理では使わない
    4. シーン遷移でもMissingReferenceExceptionは起こる
  5. MissingReferenceException診断チェックリスト
    1. まず確認するべき6項目
    2. 「最初は動く」のかを確認する
    3. Hierarchyに「Missing」が残っていないか確認する
    4. Debug.Logで「いつ壊れたか」を追う
  6. MissingReferenceExceptionでよくある誤解
    1. 「nullチェックすれば全部解決」は半分正しい
    2. SetActive(false)はDestroyとは別物
    3. GetComponent失敗が直接MissingReferenceExceptionになるわけではない
    4. 「動いているから安全」は危険
  7. まとめ|MissingReferenceExceptionは「参照寿命」を意識すると解決しやすい
  8. よくある質問(FAQ)
    1. 関連記事:

MissingReferenceExceptionとは?

まず最初に押さえておきたいのが、MissingReferenceExceptionは「オブジェクトが存在しなくなったのに、まだ使おうとしている」時に発生するエラーだということです。

特によくあるのが、Destroy()したオブジェクトへの参照が残っているケースです。

例えば、こんなコードがあります。

public class Enemy : MonoBehaviour
{
    void Update()
    {
        transform.Translate(Vector3.forward * Time.deltaTime);
    }
}

このEnemyを別スクリプトからDestroyしたあと、どこかでまだ参照していると、MissingReferenceExceptionが発生することがあります。

Destroy(enemy.gameObject);

enemy.transform.position = Vector3.zero;

この場合、Enemyはすでに削除済みです。

でも変数enemy自体は残っているため、「存在しているように見える」のが混乱ポイントなんですよね。

Unityでは、Destroyしたオブジェクトが即座に完全削除されるわけではなく、“Unity側で特殊な状態”になります。

そのため、普通のC#のnullとは少し違う挙動をします。

NullReferenceExceptionとの違い

初心者が特に混同しやすいのが、NullReferenceExceptionとの違いです。

エラー原因
NullReferenceExceptionそもそも参照が入っていない
MissingReferenceExceptionDestroy済みオブジェクトを参照している

つまり、MissingReferenceExceptionは「昔は存在していた」という点が特徴です。

だからこそ、

  • 最初は正常動作する
  • 途中から壊れる
  • 特定タイミングだけ落ちる

という“再現が不安定なエラー”になりやすいんです。

Destroyした直後でも変数が残る理由

Unity初心者が最初につまずきやすいのがここです。

例えば次のコード。

Destroy(gameObject);

Debug.Log(gameObject);

「Destroyしたならnullになるはず」と思いがちですが、Unity内部では少し特殊な管理がされています。

UnityEngine.Objectは独自のnull判定を持っているため、C#だけの知識だと違和感が出やすいんですね。

この仕様のせいで、

  • Coroutine内で後から参照して落ちる
  • Listに残っていて壊れる
  • イベント購読解除し忘れてエラーになる

といった問題が発生しやすくなります。

特にゲーム開発では、「消えたオブジェクトを誰がまだ握っているか」を意識すると、かなり原因を追いやすくなります。

関連するエラーとして、オブジェクト取得そのものに失敗しているケースもあります。




MissingReferenceExceptionが発生しやすい原因4選

MissingReferenceExceptionは、「Destroy後の参照」が根本原因です。

ただ、実際の現場では発生パターンがいくつかあります。

ここを把握しておくと、「また同じエラーだ…」と気づきやすくなりますよ。

Destroy後に別スクリプトから参照している

いちばん多いのがこのケースです。

例えばEnemyを削除したあと、Player側やManager側がまだEnemyを触っているパターンですね。

Destroy(enemy.gameObject);

enemy.Move();

この場合、変数enemyは残っていますが、中身のUnityオブジェクトは破棄済みです。

その結果、MissingReferenceExceptionになります。

特に初心者のうちは、

  • 「Destroyした場所」
  • 「まだ参照している場所」

が別スクリプトに分かれていることに気づきにくいんですよね。

「誰がいつDestroyするのか」を整理すると、かなり防ぎやすくなります。

Coroutineが破棄後も動いている

次によくあるのがCoroutineです。

例えば、数秒待ってから処理するコード。

IEnumerator Attack()
{
    yield return new WaitForSeconds(3f);

    target.transform.position = Vector3.zero;
}

待機中にtargetがDestroyされると、3秒後にMissingReferenceExceptionが発生します。

これ、かなり遭遇率が高いです。

しかも「時間差で落ちる」ので、原因特定が難しくなりやすいんですよね。

もし、

  • 数秒後だけエラーが出る
  • 戦闘終了後に突然壊れる
  • 非同期処理中だけ落ちる

という症状があるなら、Coroutineを疑ってみてください。

関連するCoroutineトラブルは、こちらでも詳しく整理しています。

Listや配列にDestroy済みオブジェクトが残っている

これはEnemy管理やBullet管理で本当によくあります。

例えば、

List<GameObject> enemies = new List<GameObject>();

にEnemyを保存していた場合、DestroyしてもListから自動削除はされません。

つまり、内部には「Missing状態」の参照が残ります。

そのままループすると、あとからエラーが出ます。

foreach(var enemy in enemies)
{
    enemy.transform.position += Vector3.forward;
}

特にスポーン管理系ではかなり発生しやすいので、定期的な掃除が重要です。

enemies.RemoveAll(item => item == null);

このコードは実務でもかなりよく使います。

List管理に不慣れな場合は、こちらもかなり役立ちます。

イベント解除漏れで破棄済みオブジェクトが呼ばれる

中級者以上でもハマりやすいのがイベント解除漏れです。

例えば、Managerがイベントを発行しているとします。

manager.OnDamage += OnDamage;

この購読を解除しないままオブジェクトをDestroyすると、イベント発火時にMissingReferenceExceptionが出ることがあります。

特にSingletonやGameManagerを使っていると起きやすいですね。

対策としては、OnDisable()OnDestroy()で解除するのが基本です。

private void OnDisable()
{
    manager.OnDamage -= OnDamage;
}

イベント系は「動いている時は快適だけど、壊れた時の追跡が難しい」ので、早めに習慣化しておくとかなり楽になります。




MissingReferenceExceptionの対処法

MissingReferenceExceptionは、「Destroy済みオブジェクトをまだ触っている」状態を解消すると直ります。

ただ、やみくもにnullチェックを追加するだけだと、あとで別の場所から再発しやすいです。

ここでは、実際によく使う対処法を「優先順位順」で整理していきます。

まずはエラー行を確認する

最初にやるべきなのは、Unity Consoleのスタックトレース確認です。

Consoleをダブルクリックすると、エラー発生行へジャンプできます。

例えばこんなエラー。

MissingReferenceException:
The object of type 'Enemy' has been destroyed but you are still trying to access it.
EnemyController.Update()

この場合、EnemyController.Update()の中でDestroy済みオブジェクトを触っています。

ここで重要なのは、「どの変数がMissingなのか」を特定することです。

初心者のうちは、

  • Destroyした側
  • エラーが出た側

を別々に考えてしまいがちなんですが、実際は“参照が残っている側”が原因になっていることが多いです。

オブジェクト使用前にnullチェックする

もっとも基本的な対策です。

if(enemy != null)
{
    enemy.Move();
}

UnityではDestroy済みオブジェクトも== nullで判定できるため、多くのケースで有効です。

特に、

  • Coroutine
  • Update
  • イベントコールバック

など、「あとから呼ばれる処理」ではかなり重要になります。

ただし、ここで注意したいのが、“nullチェックは応急処置になりやすい”ことです。

例えば、Enemyを削除したあともPlayerがEnemyを追跡し続けている設計だと、毎回nullチェックを書くことになります。

こういう時は、

  • ターゲット変更時に参照を切る
  • Destroy時に通知する
  • 管理クラスで寿命を統一する

など、設計側を見直したほうが再発しにくいです。

TryGetComponentを使って安全に取得する

GetComponent()周りの取得ミスも、あとからMissingReferenceExceptionにつながることがあります。

最近は、TryGetComponent()を使うほうが安全です。

if(TryGetComponent(out Rigidbody rb))
{
    rb.AddForce(Vector3.up);
}

これなら、「取得失敗時にnullのまま進行する」事故を減らしやすくなります。

特にPrefab差し替えやコンポーネント構成変更が多いプロジェクトでは、かなり便利です。

List内のMissing参照を定期的に掃除する

Enemy管理やBullet管理では、Destroy済み参照がListに残りやすいです。

なので、定期的に掃除する処理を入れると安定しやすくなります。

enemyList.RemoveAll(item => item == null);

これはかなり定番の書き方ですね。

特に大量スポーン系では、Missing参照が蓄積するとデバッグが急に難しくなります。

Inspector上で参照状態を可視化したい場合は、Odin Inspectorのような拡張アセットも便利です。

Odin Inspector
✅アセットストアでチェックする

Destroyする責任を整理する

中規模以上のプロジェクトになると、「誰がDestroyするのか」が曖昧になりやすいです。

例えば、

  • Enemy自身が自分をDestroyする
  • GameManagerが削除する
  • Player側が消す

が混在すると、参照管理がかなり壊れやすくなります。

私も昔、Enemy削除処理が複数箇所に分散していて、「たまにだけMissingReferenceExceptionが出る」という沼にハマりました…。

Destroy責任を1か所に寄せるだけでも、かなり追跡しやすくなります。

スクリプト責務の分離については、こちらもかなり参考になります。




Destroyのタイミングを理解すると再発しにくい

MissingReferenceExceptionを理解するうえで、かなり重要なのが「Destroyは即時削除ではない」という点です。

ここを誤解していると、「さっきまで動いていたのに次のフレームで壊れた…」という現象が起きやすくなります。

Destroyはその場で即削除されるわけではない

例えば次のコード。

Destroy(gameObject);

Debug.Log("まだ動く?");

これを見ると、「Destroyした瞬間に完全削除される」と思いやすいですよね。

でも実際には、Destroy()は“削除予約”に近い動きをします。

Unityでは通常、現在のUpdate処理が終わったあとに破棄されます。

そのため、同じフレーム中では一部アクセスできてしまうことがあります。

これが、MissingReferenceExceptionをややこしくしている原因のひとつです。

「今は動く」が次フレームで壊れる

例えばこんなコード。

Destroy(enemy.gameObject);

Debug.Log(enemy.transform.position);

このコード、状況によってはエラーが出ないことがあります。

でも次フレームで同じことをすると、MissingReferenceExceptionになるケースがあります。

つまり、「今アクセスできたから安全」とは限らないんですね。

特に危険なのが、

  • Coroutine
  • Invoke
  • 遅延処理
  • イベント

など、“後から実行される処理”です。

Destroy後に参照が残っていると、時間差で突然壊れます。

「一定時間後だけ落ちる」場合は、かなり疑わしいポイントです。

DestroyImmediateは基本的に通常ゲーム処理では使わない

UnityにはDestroyImmediate()もあります。

DestroyImmediate(gameObject);

こちらは即時削除です。

ただし、Runtime中に多用すると予期しない挙動につながりやすいため、通常のゲーム処理ではあまり使いません。

主にEditor拡張や特殊用途向けですね。

初心者のうちは、「DestroyImmediateなら安全そう」と感じやすいんですが、むしろ事故が増えることもあります。

まずは通常のDestroy()のライフサイクルを理解するほうが大切です。

Destroy系の挙動は、Prefabやシーン管理とも密接に関係します。

シーン遷移でもMissingReferenceExceptionは起こる

Destroyを直接呼んでいなくても、シーン切り替え時にオブジェクトが消えるケースがあります。

例えば、

  • Scene A のEnemyを保持
  • Scene B に移動
  • Enemy参照だけ残る

という流れですね。

この場合も、あとからアクセスするとMissingReferenceExceptionになります。

特に、

  • Singleton
  • DontDestroyOnLoad
  • Manager系

を使い始めると発生しやすいです。

もし「シーン切り替え後だけ壊れる」なら、参照寿命を疑ってみてください。

DontDestroyOnLoad周りの落とし穴は、こちらでも詳しく整理しています。




MissingReferenceException診断チェックリスト

MissingReferenceExceptionは、「原因候補を順番に潰す」とかなり早く解決できます。

逆に、思いつきで修正し始めると、別の場所で再発しやすいです。

私も昔は、nullチェックを増やしまくって「一応動くけど原因不明」という状態によくなっていました…。

ここでは、実際によく使う確認順をまとめます。

まず確認するべき6項目

  • Consoleのエラー行を確認したか
  • Destroy後に参照していないか
  • Coroutineが残っていないか
  • Listや配列にMissing参照が残っていないか
  • イベント解除漏れがないか
  • シーン遷移後だけ発生していないか

特に重要なのは、「Destroyした場所」ではなく「まだ触っている場所」を探すことです。

MissingReferenceExceptionは、“消した側”より“参照が残っている側”が原因になっているケースが多いんですよね。

「最初は動く」のかを確認する

これはかなり強い判断材料です。

症状疑うポイント
最初から動かないNullReferenceException系
途中から壊れるMissingReferenceException系
数秒後だけ落ちるCoroutine・Invoke
シーン切替後だけ壊れる参照寿命・DontDestroyOnLoad

特に「最初は正常なのに途中で壊れる」は、Destroy後参照の典型パターンです。

この特徴を覚えておくだけでも、原因特定がかなり速くなります。

Hierarchyに「Missing」が残っていないか確認する

InspectorやHierarchyに「Missing」と表示されている場合、かなり分かりやすいサインです。

例えば、

  • SerializedField
  • UnityEvent
  • List
  • Prefab参照

などにMissingが残っていることがあります。

特にPrefab運用では、古い参照が残り続けるケースがよくあります。

「何もしてないのに突然壊れた」ように見えても、実はInspector側にMissingが潜んでいることはかなり多いです。

Debug.Logで「いつ壊れたか」を追う

原因が見つからない時は、参照の寿命を追跡すると分かりやすいです。

Debug.Log(enemy);

Destroy(enemy.gameObject);

Debug.Log("Destroy後");
Debug.Log(enemy);

このようにログを入れると、

  • いつDestroyされたか
  • どこでまだ参照されているか
  • どのタイミングでMissingになるか

を追いやすくなります。

特に中規模以上のプロジェクトでは、「参照の流れを見える化する」だけでかなりデバッグが楽になります。

Debug系の可視化は、こちらの記事もかなり相性が良いです。




MissingReferenceExceptionでよくある誤解

MissingReferenceExceptionは、Unity特有の挙動が絡むので誤解されやすいです。

特に初心者のうちは、「null関連エラー」とひとまとめに考えてしまいがちなんですよね。

ここでは、実際によくある勘違いを整理しておきます。

「nullチェックすれば全部解決」は半分正しい

確かに、

if(enemy != null)
{
    enemy.Move();
}

のようなnullチェックは有効です。

UnityではDestroy済みオブジェクトも== nullで判定できるため、多くのケースでエラー回避できます。

ただし、これは“応急処置”になりやすいです。

例えば、

  • Enemy削除後もPlayerが追尾し続けている
  • イベント解除漏れがある
  • Managerが古い参照を保持している

といった状態では、毎回nullチェックを書くことになります。

本当に重要なのは、「誰が参照を持つべきか」を整理することです。

中規模以上のプロジェクトでは、ここを意識するだけでかなり安定します。

設計面を改善したい場合は、こちらもかなり役立ちます。

SetActive(false)はDestroyとは別物

かなり混同されやすいポイントです。

SetActive(false)は「非表示・停止」であり、Destroyではありません。

gameObject.SetActive(false);

この場合、オブジェクト自体は存在しています。

なので通常はMissingReferenceExceptionにはなりません。

一方、Destroyはオブジェクトそのものを破棄します。

Destroy(gameObject);

つまり、

処理オブジェクト状態
SetActive(false)存在している
Destroy()破棄される

という違いがあります。

「一時的に消したいだけ」なら、DestroyではなくSetActive(false)のほうが安全なケースもあります。

GetComponent失敗が直接MissingReferenceExceptionになるわけではない

これもかなり誤解されやすいです。

例えば、

Rigidbody rb = GetComponent<Rigidbody>();
rb.AddForce(Vector3.up);

この時、Rigidbodyが存在しなければ通常はNullReferenceException寄りになります。

MissingReferenceExceptionになるケースは、「取得後にDestroyされた」場合です。

つまり、

  • 最初から取得失敗 → NullReferenceException
  • 途中で破棄された → MissingReferenceException

というイメージですね。

この違いを理解すると、原因の切り分けがかなり速くなります。

「動いているから安全」は危険

Unityでは、Destroy直後でも一瞬アクセスできるケースがあります。

そのため、

  • 今は動いている
  • たまにしか落ちない
  • 再現率が低い

という状態になりやすいです。

特にCoroutineやイベントは、時間差で問題が表面化します。

なので、「たまたま動いた」を信用しすぎないことが大切です。

私も昔、テストプレイ中は正常だったのに、敵を大量スポーンさせた瞬間だけMissingReferenceException祭りになったことがあります…。

参照寿命系の問題は、“小規模では見えにくい”のが本当に厄介なんですよね。




まとめ|MissingReferenceExceptionは「参照寿命」を意識すると解決しやすい

MissingReferenceExceptionは、Unity初心者がかなり高確率で遭遇するエラーです。

ただ、原因そのものは意外とシンプルで、「Destroy済みオブジェクトをまだ参照している」状態がほとんどです。

特に重要なのは、次の違いですね。

エラー主な原因
NullReferenceException最初から参照が入っていない
MissingReferenceException途中でDestroyされた

この違いを理解するだけでも、原因調査の速度がかなり変わります。

特に、

  • 最初は動くのに途中で壊れる
  • Coroutine後に落ちる
  • シーン切替後だけエラー
  • Listループ中にMissingになる

という症状なら、Destroy後参照をかなり疑ってOKです。

また、実務では「nullチェックを書く」よりも、

  • 誰がDestroyするのか
  • 誰が参照を持つのか
  • いつ参照を解除するのか

を整理するほうが、圧倒的に再発しにくくなります。

私も最初の頃は、「if(obj != null)を増やせば解決」と思っていました。

でも実際は、Enemy管理・イベント・Coroutineの責務整理をした瞬間、一気に安定した経験があります。

Unityはオブジェクト寿命の管理がかなり重要なので、MissingReferenceExceptionは“設計を見直すサイン”として捉えると、むしろレベルアップにつながりやすいです🙂

コード管理や責務分離をさらに改善したい場合は、Clean Code系の考え方もかなり役立ちます。

Clean Code
✅ Amazonでチェックする✅ 楽天でチェックする


よくある質問(FAQ)

Q
MissingReferenceExceptionとNullReferenceExceptionはどちらが危険?
A

どちらが危険というより、「原因の種類」が違います。

NullReferenceExceptionは、最初から参照が存在しないケースが中心です。

一方、MissingReferenceExceptionは「途中でDestroyされた」のが特徴です。

そのため、MissingReferenceExceptionのほうが、

  • 時間差で発生する
  • 再現率が低い
  • 特定条件だけ壊れる

など、デバッグが難しくなりやすい傾向があります。

特にCoroutineやイベントを多用し始めると、原因追跡が複雑になりやすいですね。

Q
Destroyしたあとにif(obj != null)すれば安全?
A

多くのケースでは有効です。

if(enemy != null)
{
    enemy.Move();
}

UnityではDestroy済みオブジェクトも== nullで判定できるため、エラー回避には役立ちます。

ただし、nullチェックを大量に書き始めた場合は、「参照設計そのもの」に問題がある可能性もあります。

例えば、

  • Destroy後も追尾処理が残る
  • Managerが古い参照を持ち続ける
  • イベント解除漏れがある

といった状態ですね。

応急処置としては有効ですが、「なぜ参照が残っているのか」まで見ると再発しにくくなります。

Q
DestroyせずSetActive(false)を使えば安全?
A

ケースによります。

SetActive(false)はオブジェクトを破棄しないため、MissingReferenceExceptionは起きにくいです。

gameObject.SetActive(false);

そのため、

  • 弾のオブジェクトプール
  • UI表示切替
  • 一時非表示

などではかなりよく使われます。

ただし、不要オブジェクトが増え続けると、別の管理問題が発生することもあります。

なので、

  • 完全削除したい → Destroy
  • 再利用したい → SetActive(false)

という使い分けが大切です。

※当サイトはアフィリエイト広告を利用しています。リンクを経由して商品を購入された場合、当サイトに報酬が発生することがあります。

※本記事に記載しているAmazon商品情報(価格、在庫状況、割引、配送条件など)は、執筆時点のAmazon.co.jp上の情報に基づいています。
最新の価格・在庫・配送条件などの詳細は、Amazonの商品ページをご確認ください。

スポンサーリンク