Unityでスクリプトを作成していると、実行中にスクリプトを編集して、Unityエディターに戻ったときに、NullReferenceExceptionが発生してプログラムを続行出来なくなることがあります。これを回避するにはどうすればよいでしょうか。
スクリプトの編集でNullReferenceExceptionが発生する状況を見てみると、クラスの持っている変数(static変数)に対して発生していることがわかります。
// IndicatorElement.js
#pragma strict
var index = 0;
private var meter = 0.0;
public static var analyzer : AudioAnalyzer;
function Update() {
meter = Mathf.Max(meter * Mathf.Exp(-10 * Time.deltaTime), analyzer.bandLevels[index]);
transform.localScale.y = meter * 40.0;
}
このコードを含むプロジェクトで、プロジェクト内のどこかのスクリプト(このスクリプトに限らない)を更新すると、以下のようなエラーが大量に出て、プログラムを再実行しないと続行出来なくなります。

この例では、スクリプトを編集して戻ってくると、値が設定されていたはずのanalyzerがnullになってしまっています。
なぜこうなるかを理解するには、Unityがプロジェクト実行中にスクリプトを書き換える機能をどう実現しているかを(半分くらい推測ですが)考えてみたいと思います。おそらく、中でこんなことをしています。

Typeインスタンスの生成は実際には最後のデシリアライズをする過程で同時に生成されていくのだとは思いますが、ここでは単純化のために分けて書いています(どっちにしろ想像の範囲内なので・・)。
シーン上(Hierarchy上)のインスタンスはすべて捕捉されているので、すべて保存&デシリアライズが行われます。しかし、スクリプトは全て再コンパイルされていて、新しいアセンブリを使ってインスタンスが復元されるので、Typeインスタンスは新しいものが利用されます。
わかりにくいかもしれないので図にしてみます。
まず、スクリプト編集前の状態はこんな感じです。

このシーンには、複数のIndicatorElementのインスタンスと一つのAudioAnalyzerインスタンスがあるとします。IndicatorElementもAudioAnalyzerもMonoBehaviorを継承している、なにがしかのGameObjectに張り付いているコンポーネントだと思って下さい。
ここで、AudioAnalyzerかIndicatorElementのスクリプトを編集して、Unityエディタに戻ってくると、
- 全てのスクリプトがコンパイルされて新しいアセンブリが生成され、
- シーン上の全てのオブジェクトがシリアライズされてシーンのインスタンス情報とそのプロパティが保存され、
- 1で作った新しいアセンブリを使って、2の情報を元に全てのインスタンスを生成し、
- 2で作ったシリアライズ情報を元に、保存されているすべてのプロパティを復元(デシリアライズ)
図にすると

こうなって、

こうなる感じです。
つまり、アセンブリが変わってしまっている以上static変数を持っているTypeインスタンス*1も新しいものになってしまっているのですが、この部分はシーンデータから復元されないので、nullのままになっていて、結果analyzerにアクセスするとNullReferenceExceptionが発生する訳です。もしかすると古いアセンブリはアンロードされてしまっていて、情報は残ってないかもしれません。そうだとすると、static変数を復帰させようにもやりようがないというのも分かる話です。
さて、ではNullReferenceExceptionを回避するには、どうすればいいのでしょうか。
- static変数の先のオブジェクトも、シーンの管理対象にする(ScriptableObjectやMonoBehaviourの継承クラスにしておく)
- static変数がnullになったら、GameObject.FindObjectOfType()を使ってシーンから探してくる
この二つを行うようにすれば、NullReferenceExceptionを回避することができます。乱暴な例ですが、下記のように実装することで実現出来ます。
// IndicatorElement.js
var index = 0;
private var meter = 0.0;
public static var analyzer : AudioAnalyzer;
static function GetAnalyzer() {
if(analyzer == null) {
analyzer = GameObject.FindObjectOfType(AudioAnalyzer);
}
return analyzer;
}
function Update() {
meter = Mathf.Max(meter * Mathf.Exp(-10 * Time.deltaTime), GetAnalyzer().bandLevels[index]);
transform.localScale.y = meter * 40.0;
}
実行中にスクリプトが編集出来るとやはり便利なので、できなくなったという人はすこし手を加えてみてはどうでしょうか。
*1: ここでは、分かりやすくするためにTypeインスタンスが持っていることにして説明していますが、実際にはstatic変数への参照をどのように管理しているかはVMの実装依存の領域だそうです。
参考情報:
@3ヶ月前・リアクション1件
JSON Pretty Print formatting in BBEdit
http://crisp.tumblr.com/post/2574967567/json-pretty-print-formatting-in-bbedit
BBEdit 10以降ではブログ先のスクリプトをコピペしてPrettyJSON.pyを作成し、 “~/Library/Application Support/BBEdit/Text Filters” に入れる。すると、Edit>Text Filters にPretyJSON という選択肢が出てくるので、これを適用。
バッチリ綺麗になりました!Thx!
@6ヶ月前・リアクション2件