テラシュールブログ

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

【Unity】コンポーネントのイベント実行順についてのTips

GameObjectにアタッチしたスクリプトの実行順番は、特に何もしなければ割とランダムです。今回はその辺りについて少し整理します。

スクリプトの実行順番

UnityはコンポーネントをGameObjectに設定すればコンポーネントに記述したイベントを呼び出してくれます。自動的に呼ばれる代表格はAwakeやOnEnable、StartやUpdate等です。

f:id:tsubaki_t1:20170204224959j:plain

若干面倒くさい事に、このコンポーネントの処理実行順は特に設定しなければ不透明…というか、どの順番で呼ばれるかは分かりません。
たとえばマネージャークラスのような物よりも先に末端のコンポーネントが先に呼ばれるといったケースも有りえます。

スクリプトの実行順を制御する

一応、スクリプトの実行順番は、Script Execution Orderのキーワードで設定する事が出来ます。

Script Execution OrderをGUIで設定

一番GUI的なのはScript Execution OrderのUIで書き換える事です。

  1. Edit > Project Settings > Script Execution Orderを開く
  2. +を押して登録したいスクリプトを選択
  3. スクリプトの実行順を設定

実行順は、上が優先度が高く下が低いです。
正確には、数値が低い方が優先されます。標準のコンポーネントは実行順は0です。

f:id:tsubaki_t1:20170204225757j:plain

f:id:tsubaki_t1:20170204225802j:plain

なお、一覧表示中にコンポーネント頭文字のキーを押すと、該当のスクリプトまで選択がジャンプします。一つしか無い場合はその瞬間に選択が確定します。

f:id:tsubaki_t1:20170204230454g:plain

メタデータに直接書き込む

スクリプトの量が多くなってくると、Script Execution Orderでは設定が面倒くさくなってきます。そんな時の力技は.metaを直接書き換えてしまうアプローチがあります。

Script Execution Orderの情報は各スクリプトメタデータに有ります。

metaを開いて、  executionOrderの指定する数値を書き換えます。無ければ追加します。

スクリプト単位で書き込めるので、手軽といえば手軽に設定できます。

f:id:tsubaki_t1:20170204231106j:plain

f:id:tsubaki_t1:20170204231147j:plain

DefaultExecutionOrderを使用

一方、スクリプト側から設定する方法も(多分)5.5辺りから追加されてるっぽいです。*1

コンポーネントのAttributeとして設定すると、指定のコンポーネントの実行順を制御します。またScript Execution Orderと同様、数値が低い方が優先され、標準コンポーネントの実行順は0です。

なおScript Execution Orderが優先されます。

f:id:tsubaki_t1:20170204231704j:plain

イベントの実行順

イベントの実行順ですが、Unity - マニュアル: イベント関数の実行順に絵付きで分かりやすく書いてあります。

要するに、

  1. Awake
  2. OnEnable
  3. Start
  4. Update

の順で呼ばれます。但し、StartとUpdateはインスタンス化直後ではなく、スタートのタイミングで呼ばれます。

 

  • Awake
    (シーンに配置orAddComponent直後
     GameObjectがActive時)
  • OnEnable
    (シーンに配置orAddComponent直後
     ComponentがEnable時)
  • Start
    (他のStartと同時)
  • Update
    (他のUpdateと同時)

つまりコンポーネントやオブジェクトを生成したタイミングで即座にAwake→OnEnableが呼ばれ、他の全てのAwakeとOnEnableが呼ばれた後にStartが呼ばれ始めます。

OnEnableとAwakeの違い

一見一気に呼ばれるAwakeとOnEnableですが、よく見ると若干挙動が異なります。

Awake: この関数は常に Start 関数の前およびプレハブのインスタンス化直後に呼び出されます。(もしゲームオブジェクトがスタートアップ時に無効である場合、有効化にされるまで Awake は呼び出されません。)
OnEnable: (オブジェクトがアクティブな場合にのみ呼び出されます): この関数は、オブジェクトを有効にした直後に呼び出されます。例えば、MonoBehaviour インスタンスが作成されたとき、レベルロードあるいはスクリプトコンポーネントのアタッチされたゲームオブジェクトがインスタンス化されたときに実行します。

要するに、

Awake:

OnEnable:

つまりGameObjectのインスタンス化後、コンポーネントがdisableでもAwakeは呼ばれ、enable時にOnEnableが呼ばれるという事です。

f:id:tsubaki_t1:20170205220200g:plain

これを上手く使えば、階層の有るPrefabのような場合、GameObjectのアクティブ/非アクティブやコンポーネントのenable/disableを上手く使うと、先に全てのAwakeを実行してからOnEnableを実行するといった事も出来なくは無いかもしれません。

ScriptableObjectのOnEnableは、大抵Awakeより先に呼ばれる

ScriptableObjectをシリアライズしている場合、参照先のScriptableObjectのOnEnableが呼ばれるタイミングは、他のAwakeよりも早いです。

  • シーンに参照があれば、まず呼ばれる
  • シーンが参照するPrefabから参照されていたら呼ばれる
  • Instantiateで生成する前に参照を辿って呼ばれる
  • PreloadAssetsに登録されていればシーン読み込む前に呼ばれる

ただし、エディターで操作中に意図せずOnEnableが呼ばれる事もあります。

tsubakit1.hateblo.jp

 Execution Orderが効かないケース

残念ながら、Execution Orderが効かないケースもあります。

と言うより、Execution Orderはどうも「生成時にコンポーネント呼出順を並べる機能」っぽく、逐次生成すると効果を発揮しないみたいです。
なので、下のような現象が発生します。

コンポーネントやオブジェクトを動的に追加した場合は、生成順になる

動的にコンポーネントを生成した場合、コンポーネントの実行順は(StartやUpdate等も)オブジェクトの生成順になります。

例えば下のようなコードが合った場合、Execution Orderを設定してもStartやUpdateの呼出順はScript1が先、Script2が後になります。

f:id:tsubaki_t1:20170204234348j:plain

同様に、これがGameObjectの生成であった場合も、先に生成されたGameObjectのStart、Updateが優先されます。

Script2がScript1より優先的に実行される状況を作っても、Script1がアタッチされているオブジェクトを先に生成すれば、StartはScript1が先に呼ばれます。

f:id:tsubaki_t1:20170204234944j:plain

f:id:tsubaki_t1:20170204235139j:plain

しかし”同時に”生成した場合は、Script Execution Order準拠

この生成した場合のルールですが、複数のコンポーネントがアタッチしているやPrefabの子オブジェクトにアタッチされているといった状況で、一気に複数のオブジェクトを生成出来る場合、Execution Orderが適応されます。

f:id:tsubaki_t1:20170204235802j:plain

f:id:tsubaki_t1:20170204235811j:plain

f:id:tsubaki_t1:20170205000016j:plain

関連

旧記事

tsubakit1.hateblo.jp

docs.unity3d.com

tsubakit1.hateblo.jp

*1:何故多分かと言えば、マニュアルに書いて無いからです。自分も5.6の新しいNavmesh機能のサンプル(ランタイムにNavmeshを構築するやつ)に使われてた時に気づきました。

【Unity】Statemachinebehaviourの処理の実行順について

f:id:tsubaki_t1:20170202234325j:plain

Statemachinebehaviourをステートに貼り付けた際、Statemachinebehaviourはどのような順番で呼ばれるのか。

確認してみました。

Statemachinebehaviourを呼ばれる順番

ルールは、どうも下の通りっぽいです。

  • 親ステートが子ステートよりも先に呼ばれる
  • 上に配置したStatemachinebehaviourが下のStatemachinebehaviourより先に呼ばれる
  • 上にあるレイヤーが下にあるレイヤーより先に呼ばれる

f:id:tsubaki_t1:20170203002806j:plain

SubStateMachineのStatemachinebehaviour

意外と気づいてない人も居るかもしれませんが、SubStateMachineにもStatemachinebehaviourを設定する事が出来ます。

f:id:tsubaki_t1:20170202235609j:plain

その場合、OnStateMachineEnterが「SubStateMachineに到達した時に呼ばれる処理」で、「OnStateEnter」が子ステートマシンで変化が合った際に呼ばれます。

たとえば下のような図のステートとStatemachinebehaviourが合った場合、OnStateEnterはNewStateとHumanoidWalkが切り替わる度に、OnStateMachineEnterはトリガー「何らかの条件」が呼ばれ、AnyStateからStateMachineに切り替わる度に呼ばれます。

f:id:tsubaki_t1:20170203000910j:plain

f:id:tsubaki_t1:20170203001059j:plain

またSubStateMachineの子が現在のステートの場合、OnStateUpdateは常に呼ばれ続けます。

関連

unity3d.com

tsubakit1.hateblo.jp

【Unity】Display 1 No cameras rendering を消す方法

f:id:tsubaki_t1:20170201005856j:plain

小ネタです。

Display 1 No cameras rendering

画面のこの表示は、要するにCameraコンポーネントで何も描画してないよという意味です。つまりCameraを用意してやれば消えます。

f:id:tsubaki_t1:20170201010804j:plain

Cameraが不要なケース

基本的にUnityでは画面内のオブジェクトをCameraで映す事により、ゲーム画面に表示しています。
しかし、Cameraを用意するのが面倒だったり、不要といったケースも多々あります。

  •  例えばUIのみしか使用しないシーンの場合。
  • 例えばマルチシーンエディティングでCameraが別シーンに格納されている場合。
  • 例えばCameraが実行時に生成されるタイプの場合。

そういったケースでは、もしかしたら、この表示は余計なお世話かもしれません。

f:id:tsubaki_t1:20170201012519j:plain

Display 1 No cameras renderingを消す方法

f:id:tsubaki_t1:20170201012021g:plain

この項目を消したい場合、以下の操作で消せます。

  • Gameビューを右クリックしwarn if no camera renderingを選択

たぶん使えるのはUnity 5.5からです。
Display 1 No cameras renderingは確か Unity 5.4 からだったと思うので、UIが無いならダミーのカメラを置く必要があるかもしれません。

関連

tsubakit1.hateblo.jp

tsubakit1.hateblo.jp

【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