[Unity3D]Instantiate と Awake とか Startが流れるタイミングを把握する

Instantiateされた時にprefabの各GameObject内のAwakeとかStartのタイミングを知っておきたかったので調べた。

  • 親子関係によるAwakeやStartの流れが不明だったので簡単なスクリプトを作成した。
  • スクリプトの主旨としては、出現時にどの状態で出現するのかを理解できるものに、ということで作成した。

プレファブの構造は次の様にした。

Ad
    - 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が走る

AWAKE

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です