【Unity】コンポーネントのイベント実行順についてのTips
GameObjectにアタッチしたスクリプトの実行順番は、特に何もしなければ割とランダムです。今回はその辺りについて少し整理します。
スクリプトの実行順番
UnityはコンポーネントをGameObjectに設定すればコンポーネントに記述したイベントを呼び出してくれます。自動的に呼ばれる代表格はAwakeやOnEnable、StartやUpdate等です。
若干面倒くさい事に、このコンポーネントの処理実行順は特に設定しなければ不透明…というか、どの順番で呼ばれるかは分かりません。
たとえばマネージャークラスのような物よりも先に末端のコンポーネントが先に呼ばれるといったケースも有りえます。
スクリプトの実行順を制御する
一応、スクリプトの実行順番は、Script Execution Orderのキーワードで設定する事が出来ます。
Script Execution OrderをGUIで設定
一番GUI的なのはScript Execution OrderのUIで書き換える事です。
実行順は、上が優先度が高く下が低いです。
正確には、数値が低い方が優先されます。標準のコンポーネントは実行順は0です。
なお、一覧表示中にコンポーネントの頭文字のキーを押すと、該当のスクリプトまで選択がジャンプします。一つしか無い場合はその瞬間に選択が確定します。
メタデータに直接書き込む
スクリプトの量が多くなってくると、Script Execution Orderでは設定が面倒くさくなってきます。そんな時の力技は.metaを直接書き換えてしまうアプローチがあります。
Script Execution Orderの情報は各スクリプトのメタデータに有ります。
metaを開いて、 executionOrderの指定する数値を書き換えます。無ければ追加します。
スクリプト単位で書き込めるので、手軽といえば手軽に設定できます。
DefaultExecutionOrderを使用
一方、スクリプト側から設定する方法も(多分)5.5辺りから追加されてるっぽいです。*1
コンポーネントのAttributeとして設定すると、指定のコンポーネントの実行順を制御します。またScript Execution Orderと同様、数値が低い方が優先され、標準コンポーネントの実行順は0です。
なおScript Execution Orderが優先されます。
イベントの実行順
イベントの実行順ですが、Unity - マニュアル: イベント関数の実行順に絵付きで分かりやすく書いてあります。
要するに、
- Awake
- OnEnable
- Start
- 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が呼ばれるという事です。
これを上手く使えば、階層の有るPrefabのような場合、GameObjectのアクティブ/非アクティブやコンポーネントのenable/disableを上手く使うと、先に全てのAwakeを実行してからOnEnableを実行するといった事も出来なくは無いかもしれません。
ScriptableObjectのOnEnableは、大抵Awakeより先に呼ばれる
ScriptableObjectをシリアライズしている場合、参照先のScriptableObjectのOnEnableが呼ばれるタイミングは、他のAwakeよりも早いです。
- シーンに参照があれば、まず呼ばれる
- シーンが参照するPrefabから参照されていたら呼ばれる
- Instantiateで生成する前に参照を辿って呼ばれる
- PreloadAssetsに登録されていればシーン読み込む前に呼ばれる
ただし、エディターで操作中に意図せずOnEnableが呼ばれる事もあります。
Execution Orderが効かないケース
残念ながら、Execution Orderが効かないケースもあります。
と言うより、Execution Orderはどうも「生成時にコンポーネント呼出順を並べる機能」っぽく、逐次生成すると効果を発揮しないみたいです。
なので、下のような現象が発生します。
コンポーネントやオブジェクトを動的に追加した場合は、生成順になる
動的にコンポーネントを生成した場合、コンポーネントの実行順は(StartやUpdate等も)オブジェクトの生成順になります。
例えば下のようなコードが合った場合、Execution Orderを設定してもStartやUpdateの呼出順はScript1が先、Script2が後になります。
同様に、これがGameObjectの生成であった場合も、先に生成されたGameObjectのStart、Updateが優先されます。
Script2がScript1より優先的に実行される状況を作っても、Script1がアタッチされているオブジェクトを先に生成すれば、StartはScript1が先に呼ばれます。
しかし”同時に”生成した場合は、Script Execution Order準拠
この生成した場合のルールですが、複数のコンポーネントがアタッチしているやPrefabの子オブジェクトにアタッチされているといった状況で、一気に複数のオブジェクトを生成出来る場合、Execution Orderが適応されます。
関連
旧記事
*1:何故多分かと言えば、マニュアルに書いて無いからです。自分も5.6の新しいNavmesh機能のサンプル(ランタイムにNavmeshを構築するやつ)に使われてた時に気づきました。