【Unity】StartメソッドよりOnTriggerイベントが先に実行されるケース

一般的には、Startメソッドが先に実行されますが、特定のケースにおいてはOnTrigger等のイベントが先に発生(実行される)ケースがありますので注意が必要です

シーン構成

3Dプロジェクトで、Cube(デフォルト)とSpawner(空のゲームオブジェクトを作成して名前を変更)を追加します
Spawnerには、Spawnerという名前のスクリプトがアタッチされています

Cube

当たり判定の相手側になります

Spawner

空のゲームオブジェクトを作成して、名前を"Spawner"に変更します
Spawnerスクリプトをアタッチします

using UnityEngine;

public class Spawner : MonoBehaviour
{
    [SerializeField]
    GameObject spawnPrefab; // スポーンさせるPrefabの参照を保持する変数

    void Start()
    {
        Instantiate(spawnPrefab); // ゲーム開始時にPrefabをインスタンス化(生成)する
    }
}

[SerializeField]属性を使用することで、Unityエディター上で直接、生成したいプレハブをspawnPrefab変数に割り当てることができます。Startメソッドはゲームオブジェクトが有効になったときに一度だけ呼び出され、その中でInstantiate関数を使ってプレハブを実際にシーンに生成します。

インスペクターのSpawnerPrefabには、次の作成するプレファブをアウトレット接続します

Sphereプレファブ

最初は、シーンに登場していません
SpawnerスクリプトでInstantiateされます

作成手順

  1. まず、シーン上でSphereを作成します
  2. 次にコライダーの直径を大きくします(下のキャプチャー参考)
    ちょうど、Instantiateで生成された時にCubeを覆うようにします
    これで、このプレファブが生成された時に、トリガーが検出されるようにします
  3. LifeCycleMethodスクリプトをアタッチします
    どのメソッドが先に実行されるか確認するためのテスト用のスクリプトになります
  4. Projectビューにドラッグ&ドロップしてPrefabを作成します
  5. 最後にシーンからSphereを削除します

トリガーイベント検出用のスクリプト

using UnityEngine;

public class LifeCycleMethod : MonoBehaviour
{
    // Awakeメソッドは、ゲームオブジェクトがロードされた時に一度だけ呼ばれます。
    void Awake()
    {
        Debug.Log("Awake");
    }

    // Startメソッドは、Awakeメソッドの直後に一度だけ呼ばれますが、
    // オブジェクトがアクティブになった最初のフレームの前に呼ばれます。
    void Start()
    {
        Debug.Log("Start");
    }

    // OnTriggerEnterメソッドは、このゲームオブジェクトがトリガーとして設定されているColliderに
    // 他のColliderが入ったときに呼ばれます。
    void OnTriggerEnter(Collider other)
    {
        // 接触したオブジェクトの名前をログに出力します。
        Debug.Log(other.name + "と接触");
    }
}

実行結果

Awake
Cubeと接触
Start

実際の開発で起こり得ること

Startメソッドには初期化を記述しておけば、他のメソッドで初期化後の値が使えると思いますよね

ケース

期待値として、Life=10とコンソールに表示されることですが、実際はLife=0となりますね

using UnityEngine;

public class LifeCycleMethod : MonoBehaviour
{
    int life;

    // Startメソッドは、オブジェクトが初めて有効になった時に1回だけ呼ばれます。
    // ここでは、オブジェクトのライフ(生命力)を初期化しています。
    void Start()
    {
        life = 10; // ライフを10に設定
    }

    // OnTriggerEnterメソッドは、このオブジェクトがColliderを持っていて、
    // トリガーとして設定されている場合に、他のColliderがこのオブジェクトのトリガー領域に入った時に呼ばれます。
    // ここでは、トリガー領域に他のオブジェクトが入った時にライフの値をログに出力しています。
    void OnTriggerEnter(Collider other)
    {
        Debug.Log("Life=" + life); // ライフの現在値をログに出力
    }
}

対処

確実な初期化を行うためにStartメソッドではなく、Awakeメソッドに初期化を記述します

using UnityEngine;

public class LifeCycleMethod : MonoBehaviour
{
    int life;

    // Startメソッドは、オブジェクトが初めて有効になった時に1回だけ呼ばれます。
    // ここでは、オブジェクトのライフ(生命力)を初期化しています。
    void Awake()
    {
        life = 10; // ライフを10に設定
    }

    // OnTriggerEnterメソッドは、このオブジェクトがColliderを持っていて、
    // トリガーとして設定されている場合に、他のColliderがこのオブジェクトのトリガー領域に入った時に呼ばれます。
    // ここでは、トリガー領域に他のオブジェクトが入った時にライフの値をログに出力しています。
    void OnTriggerEnter(Collider other)
    {
        Debug.Log("Life=" + life); // ライフの現在値をログに出力
    }
}

Startメソッドに初期化を記述してもうまくいくケース

  • 最初からゲームオブジェクトをシーンに配置しておく
  • Instantiateで生成直後には、トリガーが検出されない場合
    コライダーが小さければトリガー検出されないですね

解説

Instantiate関数を使用してゲームオブジェクトを動的に作成した場合、そのゲームオブジェクトにアタッチされているスクリプトも同様にUnityのライフサイクルイベントに従います。Instantiateで作成されたオブジェクトは、作成された瞬間にアクティブになる前提で話を進めます(Instantiateされたオブジェクトがデフォルトでアクティブである場合)。この場合、ライフサイクルイベントは以下の順序で発生します:

  1. Awake: このメソッドは、ゲームオブジェクトがインスタンス化された直後に呼び出されます。まだフレームの更新は行われていません。この段階で、初期化処理を行うことが一般的です。
  2. OnEnable: オブジェクトがアクティブになった場合に呼び出されます。Instantiate直後にオブジェクトがアクティブであれば、Awakeの直後にこのメソッドが実行されます。
  3. Start: オブジェクトが初めてアクティブになったフレームの、最初のUpdateメソッドが呼び出される前に一度だけ実行されます。AwakeOnEnableと異なり、Startはそのオブジェクトの最初のアクティブなフレームにのみ呼び出されることに注意してください。
  4. Update, FixedUpdate, LateUpdate: これらのメソッドは、オブジェクトがアクティブである限り、各フレーム(Update, LateUpdate)または物理更新のたび(FixedUpdate)に呼び出されます。

Instantiateで作成されたオブジェクトにアタッチされたスクリプトがOnTriggerEnterやその他のコリジョンイベントを含む場合、それらのイベントは物理演算の結果に基づいて発生します。つまり、オブジェクトがアクティブになり、物理演算が実行された後に、これらのイベントが発生する可能性があります。これは、Instantiateによって作成されたゲームオブジェクトが他のオブジェクトと接触している場合に、Startメソッドよりも先にOnTriggerEnterが呼び出される状況を生み出すことがあります。

Unity

Posted by hidepon