Instantiateされた時にprefabの各GameObject内のAwakeとかStartのタイミングを知っておきたかったので調べた。
- 親子関係によるAwakeやStartの流れが不明だったので簡単なスクリプトを作成した。
- スクリプトの主旨としては、出現時にどの状態で出現するのかを理解できるものに、ということで作成した。
プレファブの構造は次の様にした。
- Root - ChildA - GrandChildA - ChildB - GrandChildA - GrandChildB
この構造の各階層のゲームオブジェクトに次のコンポーネントを貼り付けた。
InstantiateSctructLogger.cs
<br />using UnityEngine; using System.Collections; public class InstantiateTestScript : MonoBehaviour { void Awake(){ DebugLoggingHierarchy("Awake"); } // Use this for initialization void Start () { DebugLoggingHierarchy("Start"); } // Update is called once per frame void Update () { InsideUpdate(); } int updateCount = 0; void InsideUpdate(){ if( updateCount < 2){ DebugLoggingHierarchy( "Update" + updateCount); updateCount++; } } void DebugLoggingHierarchy( string _eventName){ Debug.Log( _eventName + "t" + ( ( this.transform.parent != null ) ? this.transform.parent.gameObject.name : "noParent") + ":" + this.gameObject.name ); } }
各イベント時に親の名前と自身の名前を記述するようにしています。
Debug.Log でログ出してみる
上記のスクリプトを付けて、シーンに既にある”RootRoot”という名前をつけた GameObject に ドラッグアンドドロップした時のログはこちら。
Awake ChildA:GrandChildA Awake noParent:Root Awake Root:ChildA Awake ChildB:GrandChildA Awake Root:ChildB Awake ChildB:GrandChildB Start ChildA:GrandChildA Start RootRoot:Root Start Root:ChildA Start ChildB:GrandChildA Start Root:ChildB Start ChildB:GrandChildB Update0 ChildA:GrandChildA Update0 RootRoot:Root Update0 Root:ChildA Update0 ChildB:GrandChildA Update0 Root:ChildB Update0 ChildB:GrandChildB Update1 ChildA:GrandChildA Update1 RootRoot:Root Update1 Root:ChildA Update1 ChildB:GrandChildA Update1 Root:ChildB Update1 ChildB:GrandChildB
Prefabの構造のおける親子関係は最初から構築されているものの、Prefab自身のRootであるRoot
自体に親のGameObjectは見当たらない判定をしています。
また、Instantiateしたからといって親側から順にGameObjectが生成されるわけではないということも興味深い動きです。
一方、StartになるとRootの親にあたる(ドラッグアンドドロップした対象である)RootRoot
が親であるという認識ができています。
続いて、InstantiateをScriptから行い、次の行で処理をした場合にどういう振る舞いに鳴るのかみていきます。
次のコンポーネントを GameObject “RootRoot” にアタッチすることで動作を見ます。
RootRoot.cs
using UnityEngine; using System.Collections; public class InstantiateTestScript1 : MonoBehaviour { void Awake(){ Debug.Log("at Awake Instantiate."); var g = Instantiate( Resources.Load("Prefabs/UIParts/Root") ) as GameObject; g.name = "Awake"; Debug.Log("at Awake Instantiate Done."); g.transform.parent = this.transform; Debug.Log("at Awake child at this."); } void Start(){ Debug.Log("at Start Instantiate."); var g = Instantiate( Resources.Load("Prefabs/UIParts/Root") ) as GameObject; g.name = "Start"; Debug.Log("at Start Instantiate Done."); g.transform.parent = this.transform; Debug.Log("at Start child at this."); } void Update () { InsideUpdate(); } int updateCount = 0; void InsideUpdate(){ if( updateCount < 2){ Debug.Log( "at Update" + updateCount + " Instantiate." ); var g = Instantiate( Resources.Load("Prefabs/UIParts/Root") ) as GameObject; g.name = "Update" + updateCount; Debug.Log( "at Update" + updateCount + " Instantiate Done." ); g.transform.parent = this.transform; Debug.Log( "at Update" + updateCount + " child at this." ); updateCount++; } } }
Instantiateを行った次の行で生成されたゲームオブジェクトの名前を変更しています。
生成された側のGameObjectは先の Prefab
なので、Awake時から自身の名前をDebug.Logで表示しようとします。
この名前がいつ変わるかを見ていきます。
at Awake Instantiate. Awake noParent:Root(Clone) // <- Instantiateされた直後に名前の書き換えが反映されてない。Root(Clone)のまま。親子関係も処理前。 Awake Root(Clone):ChildA Awake ChildA:GrandChildA Awake Root(Clone):ChildB Awake ChildB:GrandChildA Awake ChildB:GrandChildB at Awake Instantiate Done. at Awake child at this. at Start Instantiate. Awake noParent:Root(Clone) Awake Root(Clone):ChildA Awake ChildA:GrandChildA Awake Root(Clone):ChildB Awake ChildB:GrandChildA Awake ChildB:GrandChildB at Start Instantiate Done. at Start child at this. Start RootRoot:Awake // <- Awake内で Instantiate したGameObject "Awake" の名前が反映された。親子関係も処理されている。 Start Awake:ChildA Start ChildA:GrandChildA Start Awake:ChildB Start ChildB:GrandChildA Start ChildB:GrandChildB Start RootRoot:Start // Start時に生成された "Start" もInstantiateされた直後にそのままStartを呼び出されている。 Start Start:ChildA Start ChildA:GrandChildA Start Start:ChildB Start ChildB:GrandChildA Start ChildB:GrandChildB at Update0 Instantiate. Awake noParent:Root(Clone) Awake Root(Clone):ChildA Awake ChildA:GrandChildA Awake Root(Clone):ChildB Awake ChildB:GrandChildA Awake ChildB:GrandChildB at Update0 Instantiate Done. at Update0 child at this. Update0 RootRoot:Awake Update0 Awake:ChildA Update0 ChildA:GrandChildA Update0 Awake:ChildB Update0 ChildB:GrandChildA Update0 ChildB:GrandChildB Update0 RootRoot:Start Update0 Start:ChildA Update0 ChildA:GrandChildA Update0 Start:ChildB Update0 ChildB:GrandChildA Update0 ChildB:GrandChildB Start RootRoot:Update0 Start Update0:ChildA Start ChildA:GrandChildA Start Update0:ChildB Start ChildB:GrandChildA Start ChildB:GrandChildB 1 at Update1 Instantiate. Awake noParent:Root(Clone) Awake Root(Clone):ChildA Awake ChildA:GrandChildA Awake Root(Clone):ChildB Awake ChildB:GrandChildA Awake ChildB:GrandChildB at Update1 Instantiate Done. at Update1 child at this. Update1 RootRoot:Awake Update1 Awake:ChildA Update1 ChildA:GrandChildA Update1 Awake:ChildB Update1 ChildB:GrandChildA Update1 ChildB:GrandChildB Update1 RootRoot:Start Update1 Start:ChildA Update1 ChildA:GrandChildA Update1 Start:ChildB Update1 ChildB:GrandChildA Update1 ChildB:GrandChildB Update0 RootRoot:Update0 Update0 Update0:ChildA Update0 ChildA:GrandChildA Update0 Update0:ChildB Update0 ChildB:GrandChildA Update0 ChildB:GrandChildB Start RootRoot:Update1 Start Update1:ChildA Start ChildA:GrandChildA Start Update1:ChildB Start ChildB:GrandChildA Start ChildB:GrandChildB 2 Update1 RootRoot:Update0 Update1 Update0:ChildA Update1 ChildA:GrandChildA Update1 Update0:ChildB Update1 ChildB:GrandChildA Update1 ChildB:GrandChildB Update0 RootRoot:Update1 Update0 Update1:ChildA Update0 ChildA:GrandChildA Update0 Update1:ChildB Update0 ChildB:GrandChildA Update0 ChildB:GrandChildB 3 Update1 RootRoot:Update1 Update1 Update1:ChildA Update1 ChildA:GrandChildA Update1 Update1:ChildB Update1 ChildB:GrandChildA Update1 ChildB:GrandChildB 4
Awake
プレファブ内の親子関係は保持された状態でAwakeが走ります。
逆に、RootはAwake時には指定の子にはなっていません。
スクリプトで言うところの以下の様になってます。
var go = Instantiate( Resources.Load("Prefabs/TestObject") ) as GameObject; // ここでAwakeが走る。 go.transform.parent = parentObj;
Awake時にはClone
と命名されたデフォルトの状態であり、名前が変更されたのを確認できるのはStart以降になります。
つまり、AwakeはInstantiate後、且つ次の処理(行)以前で走っていることがわかります。
Start
ログを見るとStartはAwake後のいつのタイミングで処理が走っているのかも読み取れます。
Instantiateの呼び出しもとである “RootRoot” のAwakeで生成したPrefab “Awake” のAwakeはInstantiate行内で処理されてるのは前項の通りでした。
しかしStartで生成されたPrefab “Start” のAwakeは “RootRoot” の Start直後に全て流れているので、MonoBehaviourのStart直後に子オブジェクトに対してBroadCastをしているようです。
“RootRoot” のStart終了後、 “Awake” と “Start” の両方のStartが流れています。
実際にUpdate回数が2階目の時だけInstantiateするようにし、LateUpdate内ではUpdate回数を数える事で、1フレーム内にどこまで処理がなされているのかを探ってみます。
... void Update () { InsideUpdate(); } int updateCount = 0; void InsideUpdate(){ if( updateCount == 2){ Debug.Log( "at Update" + updateCount + " Instantiate." ); var g = Instantiate( Resources.Load("Prefabs/UIParts/Root") ) as GameObject; ...
2フレーム目のDebug.Log表示から3フレーム目のDebug.Log表示の間にInstantiateされたAwakeとStartが連続して呼ばれている事が確認できた。
よって、前述の “Start” のStart関数が呼ばれたタイミングは、”Awake” のStartが呼ばれたタイミングと同一フレームだったということ。
Update
UpdateはフレームはStart自体が終わった次のフレームで動作が行われる。
これは先のLateUpdateで確認をとった通り。
Startまでしか同じフレームではない。
まとめ
- AwakeはInstantiate直後
- AwakeはPrefabにして無い限りは親子関係の構築がなされてない
- Startは自分の親がStartし終わったら
- Updateは次のフレーム
- 再帰的にAwakeを行う
- 再帰的にStartを行う
- 子や孫全部がAwakeし終わったら、もっともRootに近いところからStartが走る