【Unity超初心者】NullReferenceExceptionというエラーについて知る【初見殺し】


はじめに

amazonで高評価だったのとタイトルが気になり「Unityで神になる本」という本を結構前に買いました。
書籍内に大砲みたいなのをつくれるチャプターがあるのですが、
内容に沿って進めてうまくいかなかったので
どこがおかしかったのかわかるような気づきの記事になるように残したいと思います。

できあがったもの

できあがってうれしかったのでYoutube動画でうpしました
こんなかんじで動きます
https://www.youtube.com/watch?v=mhkqT4d-S40

この書籍の注意点

2015/6/26に発売した本です。載ってる情報が若干古いです。
(紹介されているアセットストアの無料アセットが有料になってたりしています)

つまずいたスクリプト

「Destruction.cs」というスクリプトを作成しました。
「Destruction」は直訳すると「破壊」です。
大砲の玉をとばして車を破壊する処理についてのスクリプトなのでこのような命名になってます。

コメントは勉強不足な自分が読み返して何をしているのかわかるようにつけているので、慣れている人がソースを見れば処理の内容がわかるものについても記述されている状態だと思います。

・エラーメッセージ
NullReferenceException: Object reference not set to an instance of an object
Destruction.Start () (at Assets/Destruction.cs:35)

Destruction.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

//P.228 玉がボログルマに着弾するとボログルマがだんだん壊れていくスクリプト

public class Destruction : MonoBehaviour
{
    // 玉がぶつかった回数を記憶する int hitCount
    int hitCount = 0;

    // Start後にオブジェクト自身のRigidbodyを参照して情報を格納するためのrb
    public Rigidbody rb;

    // GameObject型のDamageLevels、GameObjectのもの(ボログルマ3形態)のみ配列に入れる
    public GameObject[] DamageLevels;

    // 煙エフェクト Unity側でSmokeをドロップする
    public GameObject SmokePt;

    void Start()
    {
        // 自分のRigidbodyを格納して参照する
        rb = GetComponent<Rigidbody>();
        // 3段階で壊れるボログルマを配列で用意
        DamageLevels = new GameObject[3];
        // ダメージレベルが上がったら各ダメージレベルのオブジェクトを呼ぶくりかえし
        for(var n = 0; n < 3; n++)
        {
            // 1段階壊れたオブジェクトから後+1ずつ呼び出す、3段階目まで
            // 非アクティブ(ディアクティベート)状態の子オブジェクトも取得できるtransform.Find
            /*(子オブジェクトの参照先がみつからない、原因分からず)NullReferenceException: Object reference not set to an 
            instance of an object
            Destruction.Start () (at Assets/Destruction.cs:35)*/
            DamageLevels[n] = transform.Find("damage_level1" + (n + 1)).gameObject;
        }
    }

    // BulletBehaviour.csから送られてきたら反応するAdd_Damageメソッド
    void Add_Damage()
    {
        // 玉がぶつかるたびに煙のsmをinstantiate(インスタンス化する、シーン中に表示させる)
        GameObject sm = Instantiate(SmokePt, transform.position, transform.rotation);
        // smoke出現後はボログルマを親とする
        sm.transform.parent = transform;
        // hitcount2以上はreturn、玉からAdd_Damageが呼ばれるとメソッドの処理は終わり
        if (hitCount > 2)
            return;
        // ボログルマのダメージが0の状態はSmokeを非表示
        DamageLevels[hitCount].SetActive(false);

        // インクリメント hitCountという整数の値に1を足すこと
        hitCount++;

        // ボログルマのダメージ1以上の状態はSmokeを表示
        DamageLevels[hitCount].SetActive(true);
    }

}

ログに表示

Debug.Log(DamageLevels);

画像のログを確認したところ3つ分のボログルマオブジェクトを認識しているっぽい

Unity側のヒエラルキー内

赤枠が対象の1段階壊れたボログルマです
最初はdamage_level1をシーン上に表示してある状態で、
それ以降の状態のオブジェクトはディアクティベート(シーン上から非表示状態)にしています。
つまったとき、ここのオブジェクト名称とスクリプト内35行目の「"damage_level1"」を何度も見返して
名前が間違ってないか見てました。

本をよく読み返したら

for文の部分は同じような構文を繰り返す場合に、ソースコードの見通しがよくなるという意味での参考でした、
短縮しないでひとつずつ配列を用意しても同様のエラーが出るか下記のように修正して試したら
なぜかちゃんと動いちゃいました。

Destruction.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

//P.228 玉がボログルマに着弾するとボログルマがだんだん壊れていくスクリプト

public class Destruction : MonoBehaviour
{
    // 玉がぶつかった回数を記憶する int hitCount
    int hitCount = 0;

    // Start後にオブジェクト自身のRigidbodyを参照して情報を格納するためのrb
    public Rigidbody rb;

    // GameObject型のDamageLevels、GameObjectのもののみ配列に入れる
    public GameObject[] DamageLevels;

    // 煙エフェクト Unity側でSmokeをドロップする
    public GameObject SmokePt;

    // ビルトイン配列を定義 配列数を指定して子オブジェクトをtransform.Findで探す、damage_levelのGameObject
    void Start()
    {
        rb = GetComponent<Rigidbody>();
        DamageLevels = new GameObject[3];
        DamageLevels[0] = transform.Find("damage_level1").gameObject;
        DamageLevels[1] = transform.Find("damage_level2").gameObject;
        DamageLevels[2] = transform.Find("damage_level3").gameObject;
    }

    /*
    // For文を使うともう少しすっきりする
    // (子オブジェクトの参照先がみつからない、原因分からず)NullReferenceException: Object reference not set to an instance of an object
       Destruction.Start () (at Assets/Destruction.cs:47)
    void Start()
    {
        // 自分のRigidbodyを格納して参照する
        rb = GetComponent<Rigidbody>();
        // 3段階で壊れるボログルマを配列で用意
        DamageLevels = new GameObject[3];
        // ダメージレベルが上がったら各ダメージレベルのオブジェクトを呼ぶくりかえし
        for(var n = 0; n < 3; n++)
        {
            // 1段階壊れたオブジェクトから後+1ずつ呼び出す、3段階目まで
            // 非アクティブ(ディアクティベート)状態の子オブジェクトも取得できるtransform.Find
            DamageLevels[n] = transform.Find("damage_level1" + (n + 1)).gameObject;
        }
    }
    */

    // BulletBehaviour.csから送られてきたら反応するAdd_Damageメソッド
    void Add_Damage()
    {
        // 玉がぶつかるたびに煙のsmをinstantiate(インスタンス化する、シーン中に表示させる)
        GameObject sm = Instantiate(SmokePt, transform.position, transform.rotation);
        // smoke出現後はボログルマを親とする
        sm.transform.parent = transform;
        // hitcount2以上はreturn、玉からAdd_Damageが呼ばれるとメソッドの処理は終わり
        if (hitCount > 2)
            return;
        // ボログルマのダメージが0の状態はSmokeを非表示
        DamageLevels[hitCount].SetActive(false);

        // インクリメント hitCountという整数の値に1を足すこと
        hitCount++;

        // ボログルマのダメージ1以上の状態はSmokeを表示
        DamageLevels[hitCount].SetActive(true);
    }

}

ググる

過去に同じ書籍を進めて質問していたであろう人の質問とそれに対しての回答や、記事を読みました。

情報をざっくり整理すると
・Transform.Findで取得できるのは、アクティブ・非アクティブに関わらず子オブジェクトのみ

つまり、やろうとしていることは間違ってないことがわかりました。
書籍の参考がおかしいことになってるなと思いStartメソッドをを下記のように改修したら
ちゃんと動くようになりました。

Destruction.cs
    void Start()
    {
        // 自分のRigidbodyを格納して参照する
        rb = GetComponent<Rigidbody>();
        // 3段階で壊れるボログルマを配列で用意
        DamageLevels = new GameObject[3];
        // ダメージレベルが上がったら各ダメージレベルのオブジェクトを呼ぶくりかえし
        for (var n = 0; n < 3; n++)
        {
            //アクティブなオブジェクト"damaged_transporter_01"をまず見つけ出し、その子オブジェクトを探索して"damage_level1"を見つける
            DamageLevels[n] = GameObject.Find("damaged_transporter_01").transform.Find("damage_level1").gameObject;
        }
    }

おわりに

去年の11月に購入した書籍なんですが、今回のように詰まってばかりなため
いつ読み終えることができるか心配なところです。
ただじっくり進めているおかげか今のところ挫折せずに楽しみながら進められています。

この記事を書きながら親オブジェクトを参照し子オブジェクトを探索するというやり方に気づけたので、
気づきの記事にするという目標は達成できました。

これにゲームの要素を取り入れるとしたら玉の段数を10発までにして
90秒の間にボログルマを何台破壊できるかとかでしょうか、

これなら1ダウンロード10円くらいで販売できそうですかね

参考サイト

▼Find関数について
http://kimama-up.net/unity-find/

▼Find系関数で注意すべきこと4つ
http://mediamonster.blog.fc2.com/blog-entry-7.html

▼transform.Findでオブジェクトを取得できない
https://teratail.com/questions/53384