読者です 読者をやめる 読者になる 読者になる

テラシュールブログ

旧テラシュールウェアブログUnity記事。主にUnityのTipsやAR・VR、ニコニコ動画についてのメモを残します。

【Unity】エディタ拡張でstaticなフィールドに置いたインスタンスが、コンパイル/再生時に初期化される問題の対策について

長ったらしいタイトルですが、その通り「エディタ拡張で、staticなフィールドに置いたインスタンスコンパイル/再生時に初期化される問題の対策について」です。

初期化されるパラメータ

Unityのエディタ拡張は、ホットリロードの要領でソースコードを書き換えると即座にコンパイル・修正したコードを反映させます。

正確には、「シリアライズ」→「コンパイル」→「シリアライズしたデータ戻し」としているみたいです。

f:id:tsubaki_t1:20170131225147j:plain

これよく見ると、シリアライズしたデータを一時退避し書き出したデータを再設定する事で、コンパイル前に設定したパラメータを保持しています。
逆を言えば、シリアライズ出来なかったパラメータが破棄されます。

例:シリアライズしないデータがある場合

 例えば下のようなコードを用意します。

gist.github.com

ウィンドウを表示後、Pushボタンを押すと#が増えるという簡単な物ですが、ソースコードコンパイルやゲームの再生を行うとパラメータが破棄されてしまいます。

f:id:tsubaki_t1:20170131230141g:plain

解決方法

この問題は、要するに「シリアライズ出来ないパラメータがある」事が問題となります。なので、シリアライズ出来る項目で固めておけば、良いのです。

シリアライズ出来る項目

シリアライズ出来る項目は、以下の通り。

  • static ではないこと
  • const ではないこと
  • readonly ではないこと
  • [Serializable]の属性を持つクラスまたは構造体
  • UnityEngine.Objectから派生したクラス
    (MonobehaviourやScriptableObject)
  • プリミティブな型(int, float, double, bool, string, 等々)
  • シリアライズ出来る型の配列もしくはList

この項目から外れている物は、シリアライズ出来ないのでホットリロード時に破棄されます。

上のサンプルコードでは、stringはシリアライズ出来ますがstaticに格納していたためシリアライズ出来ず、リロード時に値が破棄されていた訳です。

 

つまり、破棄しないように、もしくは破棄しても流し込むようにすれば、値を保持し続けてくれる訳です。

f:id:tsubaki_t1:20170131235250g:plain

解決法1:シリアライズ出来る場所に移動

手っ取り早い解決方法は、シリアライズ出来る場所にフィールドを持っていく事です。上のコードの場合、staticではなくフィールドに配置すればシリアライズ出来ます。

gist.github.com

解決法2:ScriptableSingletonを使用する

どうしてもstatic値が欲しい場合があります。例えば一つしかウィンドウを出したくないといった場合、staticに自分のインスタンスを登録して有無をチェックするコードが使用されますが、staticがデシリアライズ時に破棄されるため十分ではありません。

そんなときはScriptableSingletonを使用します。

ScriptableSingletonはシングルトンのような動作をしつつ、シリアライズ可能な値をシリアライズして保持してくれるので、staticのように全体で共有して扱う値も保持してくれます。

エディタの情報やアセット、GameObjectのインスタンスを保持する場合、これが何かと便利です。

gist.github.com

但し、シングルトンのような挙動をおこなうせいで、初期化コードを書いた方が良いケースが多々あります。
(これのランタイム版があれば色々と楽なのですが…)

解決法3:シリアライズするタイミングで、IOに書き出してしまう

微妙な方法ではありますが、シリアライズするタイミングのコールバックを取得して、IOに書き込んでしまうというアプローチです。

ISerializationCallbackReceiverを実装している場合、シリアライズ前とデシリアライズ後にコールバックが呼ばれるので、何らかの処理を挟む事が出来ます。

この方法なら、ディクショナリだろうが何だろうが、自分でシリアライズしてデシリアライズ出来るなら、値を保持出来ます。
なお、このコールバック中にUnityAPIは呼べません。つまりEditorPrefsは使えません。まぁ、UnityのAPIが必要な物は大体シリアライズ出来るので大した問題では。

gist.github.com

関連

monkey coders' - 実行中にスクリプトを編集したときのNullReferenceExceptionを回避する

docs.unity3d.com

tsubakit1.hateblo.jp

kanonji.info