チュートリアル:Unityで親子クラスのライフサイクルを正しく扱う

では今回の記事内容をベースに、チュートリアル形式に整理します。

読者が実際に手を動かして「親クラスの Start が呼ばれない問題」を体験しながら学べる流れにしました。


このチュートリアルでは、基底クラスと派生クラスを使った継承を題材に、Unity のライフサイクルメソッド(Awake / Start / Update)の挙動を理解します。

最後には、正しい設計パターンを習得することが目標です。


ステップ1:親クラスを作る

まずは BaseMover というクラスを作り、Rigidbody を取得して毎フレーム前進する処理を書きます。

using UnityEngine;

public class BaseMover : MonoBehaviour
{
    protected Rigidbody _rb;

    protected void Start()
    {
        Debug.Log("BaseMover.Start 呼ばれた");
        _rb = GetComponent<Rigidbody>();
    }

    protected void Update()
    {
        _rb.AddForce(Vector3.forward);
    }
}

実行

  1. Cube を作成
  2. Rigidbody をアタッチ
  3. BaseMover をアタッチ
  4. 再生

→ 前に進むのを確認できます。


ステップ2:派生クラスを作る

次に PlayerMover クラスを作成し、BaseMover を継承させます。

using UnityEngine;

public class PlayerMover : BaseMover
{
    protected void Start()
    {
        Debug.Log("PlayerMover.Start 呼ばれた");
        // 親を呼んでいない!
    }
}

Cube に PlayerMover をアタッチ(BaseMover は削除)して再生してみましょう。

結果

  • ログには 「PlayerMover.Start 呼ばれた」 だけが表示される
  • 親の Start は呼ばれず、_rb が null のまま
  • Update で NullReferenceException が発生

ステップ3:解決策を試す

方法A:base.Start() を呼ぶ

using UnityEngine;

public class BaseMover : MonoBehaviour
{
    protected Rigidbody _rb;

    protected virtual void Start() // ← 仮想化して拡張を許容
    {
        Debug.Log("BaseMover.Start 呼ばれた");
        _rb = GetComponent<Rigidbody>();
    }

    protected void Update()
    {
        _rb.AddForce(Vector3.forward);
    }
}
public class PlayerMover : BaseMover
{
    protected override void Start()
    {
        base.Start(); // ← 親の初期化を必ず通す
        // 子の初期化…
        Debug.Log("PlayerMover.Start 呼ばれた");
    }
}

再生すると、今度は Rigidbody が初期化され、前進する動作が復活します。


方法B:Awake に寄せる(おすすめ)

[RequireComponent(typeof(Rigidbody))]
public class BaseMover : MonoBehaviour
{
    protected Rigidbody _rb;

    protected virtual void Awake()
    {
        _rb = GetComponent<Rigidbody>();
    }
}

これなら、子に Start を書いても親の初期化が抜け落ちることはありません。


方法C:子に Start を書かない

もし子で初期化が不要なら、単純に 子の Start を削除すれば親の Start がそのまま呼ばれます。


ステップ4:図解で理解

ライフサイクルの呼ばれ方を図で整理してみましょう。

親と子に Start がある(親は呼ばれない)

子に Start がない(親が呼ばれる)

子で base.Start() を呼ぶ(両方呼ばれる)


よくあるハマりポイント

症状原因解決策
NullReferenceException が出る子に Start を書いて親が呼ばれないbase.Start() を呼ぶ or 子の Start を削除
Rigidbody を付け忘れインスペクタで未設定[RequireComponent(typeof(Rigidbody))] を親に付ける
Awake / Start のタイミングが混乱する初期化を Start に書いている依存コンポーネントの取得は Awake に寄せる

チュートリアルまとめ

  • Unity のライフサイクルは 親を自動で呼ばない
  • 子に Start を書いたら 必ず base.Start() を呼ぶ
  • 依存コンポーネントは Awake に寄せると安全
  • 子に初期化が不要なら Start を書かないのが最もシンプル

これで「親の Start が呼ばれず null になる」問題を再現・解決できました。


訪問数 2 回, 今日の訪問数 2回

C#,Unity,継承

Posted by hidepon