はじめに
前回の記事*1で?.演算子と??=演算子の紹介をしました。これらを使うときにUnityのnull判定について少し注意が必要になるので、今回はそれについて紹介します。UnityのComponetnクラスを継承したクラスやGameObjectクラスを使うときに気をつける必要があります。
nullだけどnullじゃないんです
UnityでゲームオブジェクトをDestroyすると、そのゲームオブジェクトを参照していた変数はどうなるでしょうか。
以下のコードを見てみます。①ではm_obj == null
で比較するとtrueと判定されます。どうやらDestoryされるとnullになるっぽい?感じがしますよね。
ところが、②を見ているとnameには一見nullが入りそうですが、var name = m_obj?.name
の部分でMissingReferenceExceptionとなってしまいます。あれ?var name = m_obj?.name
は、m_objがnullならnullと評価されてnameにnullが入るのでは?③も同様にm_obj.name ??= "default";
の部分でMissingReferenceExceptionとなりエラーになります。聞いてた話と違うじゃないか!!
// m_objは最初は何かしらのゲームオブジェクトを参照している状態 void Start() { DestroyImmediate(m_obj); // ①trueになる Debug.LogError(m_obj == null); // ②?.の場合、MissingReferenceException var name = m_obj?.name; Debug.LogError(name == null); // ③??=の場合、MissingReferenceException m_obj.name ??= "default"; Debug.LogError(m_obj.name == null); }
原因の正体
実は、UnityのGameObjectなどの「==」や「!=」はこっそりオーバーロードされています。オーバーロードに関しては、過去の記事*2を参考にどうぞ。ComponentやGameObjectの親クラスであるUnityEngine.Objectにも、「=」演算子がオーバーロードされ、独自の挙動が定義されています。その挙動とは、「対象のObjectが生存しているならば、「==」でnullと比較した場合、trueを返すようにする」という感じです。(本当の中身は、ブラックボックスになっていて見ることができません。Unityの偉い人に聞くしかない)
言い換えると、Destroyされたオブジェクトは実態はnullではなく、参照がなくなっている状態になるみたいですね。
なんでこうなっているかと言われると、多分Destroyしたものがnullとして判定されてくれたほうが嬉しい場面がいっぱいあるからだと思います。自分もこれを知るまでは無意識にDestroyされたものはnullだと思って実装してましたし。
対応策
とりあえずnullかどうか不安だけどnullであって欲しいものには、明示的にnullを代入するとかでしょうか。
Destroy(m_obj); m_obj = null;