MonoBehaviourライフサイクルイベントとは?

MonoBehaviourを継承したクラスでは、AwakeStartUpdateといった特定のタイミングでUnityエンジンから自動的に呼ばれるメソッドを定義できます。

これらは「ライフサイクルイベント」と呼ばれ、シーンの開始からオブジェクトの破棄までの一連の流れの中で、決まった順序で呼び出されます。

本記事では、その中でもよく使う「初期化2兄弟(Awake / Start)」と「更新3兄弟(Update / FixedUpdate / LateUpdate)」を取り上げ、それぞれの違いと使い分けを整理します。

主要なライフサイクルイベント

主要なライフサイクルイベントの実行順序は、ざっくり以下のとおりです。

順序メソッド呼ばれる回数主な用途
1Awake1回自身の初期化
2OnEnable有効化ごと購読・登録
3Start1回他オブジェクトとの連携
4FixedUpdate固定間隔物理演算
5Update毎フレーム入力・一般ロジック
6LateUpdate毎フレームカメラ追従など
7OnDisable無効化ごと購読解除
8OnDestroy1回クリーンアップ

なお、MonoBehaviourのコールバック全般やUnityEventとの連携については別の記事で解説しています。

初期化2兄弟:Awake と Start

AwakeStartはどちらも初期化のためのメソッドで、それぞれ1回だけ呼ばれます。

似ているように見えますが、呼び出されるタイミングと用途はけっこう違います。

Awake:自身の初期化

Awakeはスクリプトのインスタンスが生成された直後に呼ばれます。

シーンの読み込み時にすべてのGameObjectに対してAwakeが先に呼び出され、その後でStartに進みます。

この時点では他のGameObjectが初期化済みとは限りません。

そのためAwakeは、自身のコンポーネント取得や変数の初期化など、外部に依存しない処理を書く場所として使うのが基本です。

using UnityEngine;

public class Player : MonoBehaviour
{
    private Rigidbody _rigidbody;

    private void Awake()
    {
        // 自身にアタッチされたコンポーネントの取得
        _rigidbody = GetComponent<Rigidbody>();
    }
}

また、AwakeGameObjectが非アクティブな状態でも、Instantiateされた直後に1回呼ばれます。

Start:他オブジェクトとの連携

Startは、すべてのAwakeOnEnableの処理が終わってから、最初のフレーム更新が走る直前に呼ばれます。

この時点では、シーン内のすべてのオブジェクトのAwakeが完了しています。

他のGameObjectの参照取得や初期状態のやり取りは、Startに書くのが安全です。

using UnityEngine;

public class CameraFollower : MonoBehaviour
{
    private Transform _target;

    private void Start()
    {
        // 他オブジェクトの参照取得は Start で行う
        var player = GameObject.FindWithTag("Player");
        _target = player.transform;
    }
}

なお、StartGameObjectがアクティブにならない限り呼ばれません。非アクティブのままシーンに置かれているオブジェクトでは、Awakeだけ呼ばれてStartは呼ばれない、というケースもあります。

Awake と Start の比較

両者の違いを表にまとめておきます。

項目AwakeStart
呼び出しタイミングインスタンス生成直後最初のフレーム更新直前
呼ばれる回数1回1回
非アクティブでも呼ばれるかInstantiate時は呼ばれる呼ばれない
他オブジェクトへのアクセス推奨されない推奨される
主な用途自身の初期化、GetComponent他オブジェクトの参照取得

迷ったら「コンポーネント取得はAwake、参照解決はStart」と覚えておけばだいたい事故りません。

更新3兄弟:Update / FixedUpdate / LateUpdate

ゲーム実行中に呼び出される更新系メソッドには、UpdateFixedUpdateLateUpdateの3種類があります。

名前は似ていても、呼び出されるタイミングと役割はそれぞれ別物です。

Update:入力取得と一般的なロジック

Updateは毎フレーム呼ばれる、もっとも基本的な更新メソッドです。

呼ばれる間隔はフレームレートに依存するので、フレーム間の経過時間はTime.deltaTimeから取得します。

入力の取得(Input.GetKeyDownなど)も、基本的にはここで行います。FixedUpdateは1フレームに複数回または0回呼ばれることがあり、GetKeyDownのような単発イベントを取りこぼす可能性があるためです。

using UnityEngine;

public class PlayerInput : MonoBehaviour
{
    [SerializeField] private float _moveSpeed = 5f;

    private void Update()
    {
        // 入力の取得は Update で行う
        var horizontal = Input.GetAxis("Horizontal");

        // フレームレートに依存しない移動量に変換
        transform.Translate(Vector3.right * horizontal * _moveSpeed * Time.deltaTime);
    }
}

FixedUpdate:物理演算

FixedUpdateは、物理演算用のタイムステップに合わせて固定間隔で呼ばれます。

デフォルトは0.02秒(50回/秒)ごとで、Edit > Project Settings > TimeFixed Timestepから変更できます。

Rigidbodyに力を加える、MovePositionで位置を動かす、といった物理演算が絡む処理はここに書きます。

using UnityEngine;

public class PlayerPhysics : MonoBehaviour
{
    [SerializeField] private float _force = 10f;
    private Rigidbody _rigidbody;
    private float _horizontal;

    private void Awake()
    {
        _rigidbody = GetComponent<Rigidbody>();
    }

    private void Update()
    {
        // 入力の取得は Update で行い、値を保持しておく
        _horizontal = Input.GetAxis("Horizontal");
    }

    private void FixedUpdate()
    {
        // Rigidbody への力の適用は FixedUpdate で行う
        _rigidbody.AddForce(Vector3.right * _horizontal * _force);
    }
}

FixedUpdateは描画フレームと独立して動くので、フレームレートが揺れても物理挙動を安定させやすいのが利点です。

その代わり、フレームレートが低いときは1フレーム中に複数回呼ばれることもあるので、その点には注意してください。

LateUpdate:他オブジェクト更新後の処理

LateUpdateは、シーン内のすべてのUpdateが呼ばれたあとに、毎フレーム実行されます。

「他のオブジェクトの更新結果を踏まえて処理したい」ときに使うメソッドです。

典型的な用途はカメラの追従処理ですね。

プレイヤーの位置をUpdateで動かしているとき、カメラの追従処理もUpdateに書いてしまうと、スクリプトの実行順序によってはプレイヤー移動の前にカメラが更新されてしまい、1フレーム前の位置を追ってしまいます。

これがカメラのカクつきを生む典型パターンです。次のセクションで詳しく見ていきます。

更新3兄弟の比較

項目UpdateFixedUpdateLateUpdate
呼び出し間隔毎フレーム固定間隔(既定0.02秒)毎フレーム
経過時間の取得Time.deltaTimeTime.fixedDeltaTimeTime.deltaTime
主な用途入力、一般ロジック物理演算カメラ追従、他更新後の処理
フレームレート依存ありなしあり

この使い分けがハマりやすいのが、先ほど触れたカメラ追従です。具体的に何が起きて、LateUpdateへの切り替えでどう解決できるのかを次のセクションで見ていきます。

カメラ追従のカクつきとLateUpdateでの解決

更新3兄弟の使い分けがいちばん効いてくるのが、カメラ追従です。

問題:Update でカメラを追従させると

次のように、プレイヤー側のUpdateで位置を動かし、カメラ側のUpdateで追従させていたとします。

using UnityEngine;

// プレイヤー側:Update で移動
public class Player : MonoBehaviour
{
    [SerializeField] private float _speed = 5f;

    private void Update()
    {
        var horizontal = Input.GetAxis("Horizontal");
        transform.Translate(Vector3.right * horizontal * _speed * Time.deltaTime);
    }
}
using UnityEngine;

// カメラ側:Update で追従(NG例)
public class CameraFollow_NG : MonoBehaviour
{
    [SerializeField] private Transform _target;
    [SerializeField] private Vector3 _offset = new Vector3(0f, 2f, -5f);

    private void Update()
    {
        // 実行順序によっては Player の Update より先に呼ばれる
        transform.position = _target.position + _offset;
    }
}

Update同士の実行順序はオブジェクトごとに決まっておらず、Unityからは未定義として扱われます。

もしカメラ側のUpdateがプレイヤー側より先に呼ばれてしまうと、カメラは前フレームのプレイヤー位置を追ってしまいます。

この1フレームのズレが、映像のカクつきやジッターになって現れるわけです。

解決:LateUpdate に移す

カメラ追従の処理をLateUpdateに移すと、すべてのUpdateが完了したあとに呼ばれるので、プレイヤーの最新位置を確実に追えます。

using UnityEngine;

// カメラ側:LateUpdate で追従(OK例)
public class CameraFollow : MonoBehaviour
{
    [SerializeField] private Transform _target;
    [SerializeField] private Vector3 _offset = new Vector3(0f, 2f, -5f);

    private void LateUpdate()
    {
        // すべての Update 完了後に追従
        transform.position = _target.position + _offset;
    }
}

カメラ追従以外にも、IK処理や、複数オブジェクトの位置に追従するエフェクトなど、「他オブジェクトの更新結果を使いたい処理」はLateUpdateに書くのがセオリーです。

実行順序の罠とScript Execution Order

ここまで見てきたように「AwakeStartより先」「UpdateLateUpdateより先」といった種類間の順序は確定しています。一方で、同じ種類のメソッド同士の順序は基本的に未定義です。

例えばシーン内にEnemyAEnemyBという2つのスクリプトがあるとき、どちらのUpdateが先に呼ばれるかは保証されません。

Script Execution Order で順序を指定する

どうしても順序を制御したいときは、Edit > Project Settings > Script Execution Orderからスクリプト単位で実行順序を指定できます。値が小さいほど早く呼ばれ、Default Timeのスクリプトは中央のタイミングで呼ばれます。

DefaultExecutionOrder 属性で指定する

スクリプト側に属性を付けることでも順序を指定できます。

using UnityEngine;

// 通常より早いタイミングで実行する
[DefaultExecutionOrder(-100)]
public class GameInitializer : MonoBehaviour
{
    private void Awake()
    {
        // 他のスクリプトより先に初期化を行いたい処理
    }
}

ただし、実行順序に依存しすぎた設計は後から修正しづらくなるので、できるだけ避けたいところです。

基本は「Awakeで自身を初期化」「Startで参照を解決」を守り、それでも順序が必要なときだけScript Execution Orderを使う、というのがよいでしょう。

補足:OnEnable / OnDisable / OnDestroy

最後に、初期化・更新系とあわせて押さえておきたいライフサイクルメソッドも軽く紹介しておきます。

OnEnable / OnDisable

OnEnableGameObjectまたはコンポーネントが有効になるたび、OnDisableは無効になるたびに呼ばれます。

Awakeの直後に最初のOnEnableが呼ばれ、その後にStartが続く流れです。

イベントの購読登録はOnEnable、解除はOnDisable、と覚えておくのがセオリーです。

using UnityEngine;

public class EventListener : MonoBehaviour
{
    private void OnEnable()
    {
        // 有効化時にイベントを購読
        Application.lowMemory += OnLowMemory;
    }

    private void OnDisable()
    {
        // 無効化時に必ず解除
        Application.lowMemory -= OnLowMemory;
    }

    private void OnLowMemory()
    {
        Debug.Log("メモリ不足の通知を受け取った");
    }
}

OnDestroy

OnDestroyは、オブジェクトが破棄される直前に1回呼ばれます。購読解除やDisposeが必要なリソースの後始末はここで行います。

ただし、Awakeが一度も実行されていないオブジェクト(一度もアクティブになっていないGameObject)ではOnDestroyは呼ばれません。

例えばシーンに非アクティブで置かれたオブジェクトを、一度もアクティブにせず破棄した場合、Awakeが走っていないためOnDestroyもスキップされます。

まとめ

MonoBehaviourのライフサイクルイベントは、Unityのゲーム開発で土台になる仕組みです。

ざっくり整理すると、自身の初期化やコンポーネント取得はAwake、他オブジェクトの参照解決はStart、入力や一般ロジックはUpdate、物理演算はFixedUpdate、カメラ追従のように他オブジェクトの更新後に走らせたい処理はLateUpdate

これが基本の住み分けです。

同じ種類のメソッド同士の実行順序は未定義なので、どうしても順序が必要なときだけScript Execution OrderDefaultExecutionOrder属性で制御すれば十分です。

どのメソッドにどんな処理を書くかを意識するだけで、原因不明のバグやカクつきはかなり減らせます。ご自分のプロジェクトでも、ライフサイクルの使い分けを一度見直してみてください。