【Unity】NullReferenceExceptionの直し方|よくある6つの原因

NullReferenceException の正体は、ひとことで言えば「中身が入っていない変数を使おうとした」です。エラー文は英語で長いのに、原因はいつもこれだけ。難しいのは、どの変数が空っぽなのかを見つける所なんですね。

この記事では、Unity の NullReferenceException について、エラーの意味、Console から原因の行を突き止める手順、よく出る6つの原因と直し方、null チェックの正しい書き方、そしてエディタでは出ないのにビルド後だけ出る時の調べ方をまとめました。コードは Unity 6 のスクリプトリファレンスで API を確認したものだけを載せています。

再生前に止まる赤いエラー(CS1002 など)はコンパイルエラーという別物です。そちらは次の記事で扱っています。

スポンサーリンク

NullReferenceExceptionとは|エラー文の意味

Console に出る文はたいてい次の形をしています。

NullReferenceException: Object reference not set to an instance of an object
PlayerMove.Update () (at Assets/Scripts/PlayerMove.cs:15)

1行目は「オブジェクトの参照が、実体(インスタンス)に設定されていない」という意味です。変数という箱はあるけれど、中に何も入っていない(null)。その状態で rb.AddForce() のように中身のメソッドを呼んだり、target.position のように中身の値を読んだりすると、このエラーで処理が止まります。

2行目がいちばん大事な情報です。PlayerMove.cs の15行目、Update の中で起きたと書いてあります。コンパイルエラーと違って再生自体は始まるので、ゲームは動いているのに途中で特定の処理だけが効かない、という見え方になりがちです。

エラーの場所を見つける手順

1. Console のエラーをダブルクリックする

Window > General > Console を開き、赤いエラーの行をダブルクリックすると、コードエディタ(Visual Studio や Rider)が開いて該当の行にカーソルが移ります。下の欄には呼び出しの流れ(スタックトレース)が出ていて、上の行ほどエラーに近い場所です。自分の書いたスクリプト名が出てくる一番上の行を見てください。

2. その行で「.」の左にある変数を全部疑う

null の変数そのものを書いただけではエラーになりません。エラーになるのは、null の変数のあとに「.」を付けて中身にアクセスした時です。たとえば次の1行なら、疑う相手は player と player.GetComponent<Animator>() の結果の2つ。

player.GetComponent<Animator>().SetTrigger("Jump");

3. Debug.Log でどれが null か確かめる

候補が複数ある時は、エラーの行の直前で中身を表示させるのが早いです。

Debug.Log("player は null? " + (player == null));
Debug.Log("animator は null? " + (player.GetComponent<Animator>() == null), this);

Debug.Log の2番目の引数に this を渡すと、Console のログをクリックした時にそのスクリプトが付いたオブジェクトがヒエラルキーで選ばれます。同じスクリプトを何十個も置いている時、どのオブジェクトで起きたかが分かるので助かります。

よくある原因と直し方6つ

原因1:インスペクターで参照を入れ忘れている

いちばん多いのがこれです。public や [SerializeField] の変数をインスペクターに出したのに、オブジェクトをドラッグしていない。インスペクターの欄に「None (Transform)」のように出ていたら、それが空っぽの証拠です。

プレハブから置いたオブジェクトや、別のシーンにコピーしたオブジェクトでは、参照だけが外れていることがあります。最初はここでつまずきやすいです。エラーが出たオブジェクトを選んで、None になっている欄がないか目で確かめてください。

原因2:GetComponent でコンポーネントが見つからない

GetComponent<T>() は、そのオブジェクトに T 型のコンポーネントが付いていなければ null を返します(スクリプトリファレンスに明記)。子オブジェクトに付いているのに、親で GetComponent している、というのもよくある形です。

キャラクターを動かすスクリプトなら、付け忘れを防ぐ書き方が2つあります。

using UnityEngine;

// このスクリプトを付けると CharacterController も自動で付く
[RequireComponent(typeof(CharacterController))]
public class PlayerMove : MonoBehaviour
{
    CharacterController controller;

    void Awake()
    {
        // 見つかったら true、なければ false が返る
        if (!TryGetComponent(out controller))
        {
            Debug.LogError("CharacterController が付いていません", this);
        }
    }
}

TryGetComponent は、見つからなかった時にエディタでもメモリ確保をしない、と公式ドキュメントにあります。戻り値が bool なので、見つからなかった時の処理も素直に書けますね。CharacterController の基本的な使い方はこちらにまとめています。

原因3:GameObject.Find や Camera.main が null を返す

GameObject.Find("Player") は名前で探すメソッドですが、非アクティブのオブジェクトは見つけません。リファレンスにも「アクティブな GameObject だけを返す」と書かれています。名前の打ち間違い、全角と半角の違い、ヒエラルキーでチェックを外したままのオブジェクト。どれも null になります。

Camera.main も同じ仲間です。これは「MainCamera」タグの付いた、有効な Camera を返すプロパティなので、カメラを作り直してタグが Untagged のままだと null になります。Cinemachine を入れた時やシーンをまたいだ時に起きやすいので、カメラのタグは一度確認しておくと安心です。

なお Find はシーン全体を線形に探すので、Update の中で毎フレーム呼ばないよう公式も注意しています。Start で一度だけ探して変数に入れておく形にすれば、null の確認も1か所で済みます。

原因4:Awake と Start の順番を当てにしている

スクリプト A の Awake で作ったものを、スクリプト B の Awake で使う。これはたまたま動くことがあっても、Unity は別々のオブジェクトの Awake の順番を保証していません(リファレンスでは「決まっていない」とされています)。昨日まで動いていたのに、オブジェクトを1つ増やしたら急に null になる、という厄介な形で出てきます。

公式の考え方は「Awake では自分の中の準備と参照の取得、ほかのオブジェクトとのやり取りは Start で」です。Start は全部の Awake が終わってから呼ばれるので、相手の準備が済んでいることを前提にできます。

原因5:List や配列を new していない

List<string> items;          // 宣言しただけ。中身は null

void Start()
{
    items.Add("ポーション");   // ここで NullReferenceException
}

private で宣言しただけの List は null のままです。List<string> items = new List<string>(); のように、宣言と同時に作っておけば防げます。

ややこしいのは、public や [SerializeField] の List は、インスペクターに表示された時点で Unity が空のリストを用意してくれる点です。インスペクターに出している変数では起きないのに、private に変えた途端にエラーになる。この差を知らないと混乱しやすい所だと思います。

原因6:Destroy したオブジェクトやシーン移動後の参照を使っている

敵を Destroy() したあとも、別のスクリプトがその敵を変数に持ち続けていることがあります。破棄されたオブジェクトを触ると、多くの場合は MissingReferenceException(「破棄されたのにまだアクセスしている」)が出ますが、書き方によっては NullReferenceException になります。どちらにしても、破棄した時に参照している側の変数も片付けるか、使う前に null チェックを入れるのが対処です。シーンを読み込み直した時に、static 変数や DontDestroyOnLoad のオブジェクトが古いシーンのものを握ったまま、というパターンもあります。

nullチェックの書き方と「?.」の落とし穴

一番シンプルなのは if で確かめる形です。

if (target != null)
{
    transform.LookAt(target);
}

C# に慣れている人ほど target?.position や a ?? b と書きたくなりますが、Unity のオブジェクト(GameObject やコンポーネント)には ?. と ?? を使わないほうが安全です。

理由は Unity の作りにあります。Destroy されたオブジェクトは、C# の側では変数にまだ何かが残っていて、本当の意味で null ではありません。Unity は == と != を独自に書き換えて「破棄済みなら null と同じ扱い」にしていますが、?. と ?? は書き換えられないため、破棄済みのオブジェクトを素通りしてしまいます。Object クラスのスクリプトリファレンスにも、この2つは破棄された状態にならないと保証できる時だけ使ってよい、と書かれています。

List や string のような普通の C# の型なら ?. を使っても問題ありません。「Unity のものは ==、それ以外は ?. もあり」と覚えておくと迷わないと思います。

ちなみに、エラーを出さないためだけに null チェックで包むのはおすすめしません。本来入っているべき参照が空なら、それは設定ミスです。黙ってスキップすると「なぜか動かない」になるだけなので、原因2の例のように Debug.LogError で知らせる形にしておくと、あとで自分が助かります。

ビルド後だけ出る時の調べ方(only in build)

エディタでは何も起きないのに、ビルドしたゲームでだけ動かない。この場合は Console が見えないので、まずログを読む所から始めます。

Player.log を開く

Windows のビルドは %USERPROFILE%\AppData\LocalLow\会社名\製品名\Player.log にログを書き出します(会社名・製品名は Project Settings > Player の Company Name と Product Name)。エディタの Console 右上のメニューから「Open Player Log」を選んでも開けます。Android は logcat で見ます。

Development Build で行番号を出す

ふつうのビルドではスタックトレースに行番号が出ず、どこで落ちたのか絞りにくいです。Unity 6 では File > Build Profiles(旧 Build Settings)で Development Build にチェックを入れ、Script Debugging も有効にしてビルドし直すと、デバッグ用の情報が含まれるので場所を追いやすくなります。

ビルドだけで起きやすい原因

  • 読み込むシーンが Build Profiles の Scene List に入っていない
  • Resources.Load のパスや大文字小文字の違い(見つからないと null が返る)
  • Editor という名前のフォルダに置いたスクリプトや、UnityEditor 名前空間の機能を使っている(Editor フォルダの中身はビルドに含まれない)
  • Awake の順番がエディタとビルドで変わり、原因4の問題が表に出た

特に4つ目は「エディタでは動くのに」の代表です。エディタでたまたま A→B の順に動いていても、ビルドで同じとは限りません。ビルドだけで出る時は、まず Awake 同士で参照し合っていないかを見直すのが近道ですね。

参照ミスを減らす作り方|アセットを土台にする手も

NullReferenceException は、慣れた人でも毎週のように見るエラーです。ゼロにはできないので、減らす工夫と、出た時にすぐ場所が分かる書き方をセットで持っておくのが現実的だと思います。

  • インスペクターで入れる参照は [SerializeField] にして、None の欄をなくす
  • 同じオブジェクトに必要なコンポーネントは [RequireComponent] で付け忘れを防ぐ
  • Find や Camera.main は Start で1回だけ取得して変数に入れる
  • ほかのオブジェクトとのやり取りは Start 以降で行う

もう1つ、移動・カメラ・入力のような土台部分を、最初から組み上がったアセットに任せるのも手です。公式の Starter Assets のように、必要なコンポーネントと参照がプレハブの中でつながった状態で届くものなら、自分で配線する箇所が減ります。Starter Assets を入れて「動かない」となった時の対処は次の記事に書きました。

ジャンル別に、評価の高いアセットを集めた記事もあります。

まとめ|NullReferenceExceptionの直し方

NullReferenceException は「空っぽの変数の中身を使った」というエラーで、直し方の流れはいつも同じです。Console のエラーをダブルクリックして行を開き、「.」の左にある変数のどれが null かを Debug.Log で確かめ、なぜ入っていないのかを6つの原因と照らし合わせる。インスペクターの None、GetComponent の見つからない、Find と Camera.main、Awake の順番、new 忘れ、Destroy 後の参照、のどれかに当てはまることがほとんどです。

ビルド後だけ出る時は Player.log と Development Build で場所を特定し、Scene List や Editor フォルダ、Awake の順番を疑ってみてください。

キャラクター周りのアセットを選ぶ時は、キャラクターコントローラーのアセットおすすめ6選で無料と有料を比べています。有料アセットは値下げの時期に買うと出費をかなり抑えられるので、Unity Asset Store のセール情報も合わせて見てみてください。

著者プロフィール
うんくん

IT系に特化した記事がメインです。(Unity多め。)
Udemy講師もやっています。よろしくお願いします。

※Amazonのアソシエイトとして、当メディアは適格販売により収入を得ています。

うんくんをフォローする
unityunity基本操作
スポンサーリンク
シェアする
うんくんをフォローする