WL 2022/01/09 21:41

1/9更新分

あけましておめでとうございます。お正月はいかがでしたでしょうか。私は痔になりました。
皆さんの2022年が実り多い物になることをお祈りしています。

年貢

おい新年早々年貢を納めねえ奴がいるらしいぞ?


モデリングが面白くて絵をサボってました。次回は納めたいね...年貢...

髪留めを雰囲気で描いていたのが致命的な形で露見している様子です。

お気づきの方もいらっしゃるかと思いますが、Hシーンは2Dにするつもりなので3Dモデルの使いみちが今の所特にないんですよね。まあなんかで使うやろ。

実装

UnityのAwake()の実行順について

備忘録です。

TL;DR

Awake()関数はエディタであとから作ったオブジェクトから順番に実行されます(多分)。

動機

Unityにはイベント関数というものがあります。その中のAwake()関数は
Awake: この関数は常に Start 関数の前およびプレハブのインスタンス化直後に呼び出されます。
とあり、ゲームオブジェクトが作られた直後に呼ばれるらしいです。ではゲームオブジェクトが複数ある時、それらの間のAwake()の実行順はどうなるでしょうか?

実験1

  • Main
  • Test1
  • Test2
  • Test3

の4つの空のゲームオブジェクトを作ります。なお、オブジェクトを作成する際はMainを作り、Test1, Test2, Test3の順にコピーして作りました。これは後で重要になってきます。それぞれのオブジェクトに以下のようなスクリプトをつけて調べてみます。

Main.cs

using System;
using UnityEngine;

public class Main : MonoBehaviour
{
    void Awake()
    {
        Debug.Log("main awake: " + DateTime.Now);
    }
    void Start()
    {
        Debug.Log("main start");
    }
}

Test1.cs

using System.Threading;
using System;
using UnityEngine;

public class Test1 : MonoBehaviour
{
    void Awake()
    {
        Thread.Sleep(1000);
        Debug.Log("test1 : " + DateTime.Now);
    }
}

Test2.cs

using System.Threading;
using System;
using UnityEngine;

public class Test2 : MonoBehaviour
{
    void Awake()
    {
        Thread.Sleep(2000);
        Debug.Log("test2 : " + DateTime.Now);
    }
}

Test3.cs

using System.Threading;
using System;
using UnityEngine;

public class Test3 : MonoBehaviour
{
    void Awake()
    {
        Thread.Sleep(3000);
        Debug.Log("test3 : " + DateTime.Now);
    }
}

それぞれのコード中のDebug.Logが出力される順番を見ることで、各オブジェクトのAwake()が実行される順番を知ることができるはずです。では結果を見てみましょう。

test3 : 2022/01/09 20:56:58
UnityEngine.Debug:Log (object)
Test3:Awake () (at Assets/Scripts/Common/Junkyard/Test3.cs:10)

test2 : 2022/01/09 20:57:00
UnityEngine.Debug:Log (object)
Test2:Awake () (at Assets/Scripts/Common/Junkyard/Test2.cs:10)

test1 : 2022/01/09 20:57:01
UnityEngine.Debug:Log (object)
Test1:Awake () (at Assets/Scripts/Common/Junkyard/Test1.cs:10)

main awake: 2022/01/09 20:57:01
UnityEngine.Debug:Log (object)
Main:Awake () (at Assets/Scripts/Common/Junkyard/Main.cs:9)

main start
UnityEngine.Debug:Log (object)
Main:Start () (at Assets/Scripts/Common/Junkyard/Main.cs:13)

???????

私の予想では main awake -> test 1 -> test 2 -> test 3 -> main startの順になるかと思ったんですが、Awake()の順番は逆でした。

実験2

次にTest3のSleepを1000ms, Test1のSleepを3000msにしてみます。これでAwake()の実行時間が順序に関係するかどうかが分かります。では結果を見てみましょう。

test3 : 2022/01/09 21:02:06
UnityEngine.Debug:Log (object)
Test3:Awake () (at Assets/Scripts/Common/Junkyard/Test3.cs:10)

test2 : 2022/01/09 21:02:08
UnityEngine.Debug:Log (object)
Test2:Awake () (at Assets/Scripts/Common/Junkyard/Test2.cs:10)

test1 : 2022/01/09 21:02:11
UnityEngine.Debug:Log (object)
Test1:Awake () (at Assets/Scripts/Common/Junkyard/Test1.cs:10)

main awake: 2022/01/09 21:02:11
UnityEngine.Debug:Log (object)
Main:Awake () (at Assets/Scripts/Common/Junkyard/Main.cs:9)

main start
UnityEngine.Debug:Log (object)
Main:Start () (at Assets/Scripts/Common/Junkyard/Main.cs:13)

????????????????????????

実験1と実行順序は変わりませんでした。つまりAwake()の実行時間は順序に関係なさそうな事がわかります。

実験3

次に、Test1とTest2のヒエラルキー上での順番を入れ替え、実験1と同じ条件で実験します。つまり、今まで

Main
Test1
Test2
Test3

という並びだったのが

Main
Test2
Test1
Test3

となります。この実験でヒエラルキー上での順番が順序に関係あるか調べることが出来ます。では結果を見てみましょう。

test3 : 2022/01/09 21:08:18
UnityEngine.Debug:Log (object)
Test3:Awake () (at Assets/Scripts/Common/Junkyard/Test3.cs:10)

test2 : 2022/01/09 21:08:20
UnityEngine.Debug:Log (object)
Test2:Awake () (at Assets/Scripts/Common/Junkyard/Test2.cs:10)

test1 : 2022/01/09 21:08:21
UnityEngine.Debug:Log (object)
Test1:Awake () (at Assets/Scripts/Common/Junkyard/Test1.cs:10)

main awake: 2022/01/09 21:08:21
UnityEngine.Debug:Log (object)
Main:Awake () (at Assets/Scripts/Common/Junkyard/Main.cs:9)

main start
UnityEngine.Debug:Log (object)
Main:Start () (at Assets/Scripts/Common/Junkyard/Main.cs:13)

お、そうだな

実験1と実行順序は変わりませんでした。つまりヒエラルキー上での順番は順序に関係なさそうな事がわかります。

実験4

次にTest1を削除し、同じものを再び作ります。これでオブジェクトの作成順がAwake()の実行順序に関係あるかを知ることが出来ます。では結果を見てみましょう。

test1 : 2022/01/09 21:12:18
UnityEngine.Debug:Log (object)
Test1:Awake () (at Assets/Scripts/Common/Junkyard/Test1.cs:10)

test3 : 2022/01/09 21:12:21
UnityEngine.Debug:Log (object)
Test3:Awake () (at Assets/Scripts/Common/Junkyard/Test3.cs:10)

test2 : 2022/01/09 21:12:23
UnityEngine.Debug:Log (object)
Test2:Awake () (at Assets/Scripts/Common/Junkyard/Test2.cs:10)

main awake: 2022/01/09 21:12:23
UnityEngine.Debug:Log (object)
Main:Awake () (at Assets/Scripts/Common/Junkyard/Main.cs:9)

main start
UnityEngine.Debug:Log (object)
Main:Start () (at Assets/Scripts/Common/Junkyard/Main.cs:13)

^_^

test1の実行が最初になりました。
この結果から、エディタであとから作ったオブジェクトから順番にAwake()が実行されるっぽいことが分かります。

実験Ex

最後に、このシーンを別のシーンからLoadSceneAsync()関数で読み込んでみます。
この実験の目的はどのタイミングでシーンのロードが完了したとみなされるかを知ることです。

読み込む側のシーンのオブジェクトのコードはこんな感じです。

using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;

public class MainMenuUIController : MonoBehaviour
{
    AsyncOperation asy;

    public void Continue()
    {
        asy = SceneManager.LoadSceneAsync("Playground", LoadSceneMode.Additive);
        asy.completed += _ => { Debug.Log("complete!"); };
    }

    void Update()
    {
        if (asy != null)
        {
            Debug.Log(asy.progress);
        }
    }
}

Test1とかのログがロード完了のログ(「complete!」)の前に出れば、シーンのロードが完了したとはオブジェクトのAwake()関数を呼び終わったあとの状態を意味することになります。逆もまた然り。途中で出たときは知らん。
では結果を見てみましょう。

0
UnityEngine.Debug:Log (object)
MainMenuUIController:Update () (at Assets/Scripts/MainMenu/MainMenuUIController.cs:44)

0.9
UnityEngine.Debug:Log (object)
MainMenuUIController:Update () (at Assets/Scripts/MainMenu/MainMenuUIController.cs:44)

test3 : 2022/01/09 21:30:29
UnityEngine.Debug:Log (object)
Test3:Awake () (at Assets/Scripts/Common/Junkyard/Test3.cs:10)

test2 : 2022/01/09 21:30:31
UnityEngine.Debug:Log (object)
Test2:Awake () (at Assets/Scripts/Common/Junkyard/Test2.cs:10)

test1 : 2022/01/09 21:30:32
UnityEngine.Debug:Log (object)
Test1:Awake () (at Assets/Scripts/Common/Junkyard/Test1.cs:10)

main awake: 2022/01/09 21:30:32
UnityEngine.Debug:Log (object)
Main:Awake () (at Assets/Scripts/Common/Junkyard/Main.cs:9)

complete!
UnityEngine.Debug:Log (object)
MainMenuUIController/<>c:<Continue>b__3_0 (UnityEngine.AsyncOperation) (at Assets/Scripts/MainMenu/MainMenuUIController.cs:21)
UnityEngine.AsyncOperation:InvokeCompletionEvent ()

main start
UnityEngine.Debug:Log (object)
Main:Start () (at Assets/Scripts/Common/Junkyard/Main.cs:13)

1
UnityEngine.Debug:Log (object)
MainMenuUIController:Update () (at Assets/Scripts/MainMenu/MainMenuUIController.cs:44)

1
UnityEngine.Debug:Log (object)
MainMenuUIController:Update () (at Assets/Scripts/MainMenu/MainMenuUIController.cs:44)

...

わかったことをあげてみましょう。

  • 1になる直前にAwake()が呼ばれているように見える
  • complete!が出るのはAwake()が全部終わったあと、かつStart()が呼ばれる前
  • Continue()したとき6秒くらい止まったが、その間ログは止まっていた(Update()は呼ばれていない)

実験の設定が悪くてよくわかりませんが疲れてきたしだるいんで大体こんな感じで考察をやっつけたいと思います。

  • 多分Awake()が終わったときにロード完了
  • でもロード割合はあまり信用できない(Awake()で重い処理をすると90%で止まる)
  • 別のシーンでも同一スレッドで実行される

以上、Awake()関数の実行されるタイミングについて調べてみました。いかがでしたか?
こういうことを公式ドキュメントに書いてほしいんですが、とっくの昔に非推奨になってるOnLevelWasLoadedへの言及があるあたり公式ドキュメントは信用すべきではなさそうであることが分かります。


今週は以上です。言い忘れてたんですがゲームシステムはブロック崩しになりそうです。
次回は年貢を納めていきたいと思いますのでよろしくお願いします。

では次回もよろしくお願いします。お元気で。

最新の記事

記事のタグから探す

月別アーカイブ

限定特典から探す

記事を検索