はじめまして、オルトプラスでクライアントエンジニアをしているepagoraと申します。

これまで4年半ほどUnityでの開発に従事してきましたので、今回はUnity C#で見かけるありがちなアンチパターンを3つお届けしようと思います。

たった3つではありますが、知らない人にとっては結構ハマりどころだと思うので、どうぞ読んでやってください。

ケース1

1つ目は個人的にUnityあるあるだと思っているこちらです。

問題

public class Bullet : MonoBehaviour
{
    public void AliveLog()
    {
        Debug.Log("Still alive");
    }
}

まずは生存しているかどうかのログを出す機能しかありませんが、弾のクラスです。

public class Sample : MonoBehaviour
{
    [SerializeField] Bullet prefab;

    Bullet bullet = null;
    void Start()
    {
        bullet = Instantiate(prefab);
        StartCoroutine(AutoDestroy());
    }

    IEnumerator AutoDestroy()
    {
        yield return new WaitForSeconds(3.0f);
        Destroy(bullet);
    }

    void Update()
    {
        // 実際はログが出過ぎちゃうからやめてね
        bullet?.AliveLog();
    }
}

そして、その弾をInstantiateし、3秒後にDestroyするという処理です。

Updateではbulletがnullでなければ生存ログを出すようにしています。

しかしこのコード、bulletDestroyしたあとも生存ログが出続けます。

場合によってはNullReferenceExceptionが出ることもあります。

nullチェックは確かにしているはずなのに、なぜでしょうか?

原因

実は、UnityEngine.Objectを継承しているクラスにとってのnullは、C#本来のnullとは違うものなのです。

UnityEngine.ObjectDestroyされることでnullになりますが、それはC#本来のnullになるのではなく、Unity的にnull扱いという状態になります。