【Unity】継承したクラスに空の Start() を残してはいけない理由

広告

はじめに

Unityでゲームを作っていると、こんな経験はありませんか?

  • 親クラスに書いた初期化処理が、なぜか動かない
  • HPが0のままになっている
  • エラーは出ていないのに、動きがおかしい

実はこれ、子クラスに残っている「空の Start()」が原因かもしれません。この記事では、その理由と対処法を解説します。

今回のコード

敵やプレイヤーに共通する「ステータス」を、親クラス MobStatus にまとめたとします。

using UnityEngine;

public class MobStatus : MonoBehaviour
{
    protected int hp;

    void Start()
    {
        hp = 100;   // 共通の初期化
        Debug.Log("HPを初期化しました");
    }
}

そして、プレイヤー用の子クラスを作ります。Unityで新しいスクリプトを作ると、こうなっているはずです。

using UnityEngine;

public class PlayerStatus : MobStatus
{
    void Start()
    {
    }

    void Update()
    {
    }
}

この状態で実行すると、「HPを初期化しました」は表示されません。hp も 0 のままです。

原因:Unityは「一番下の子クラス」の Start() だけを呼ぶ

Unityは、Start() や Update() などのメッセージ関数を、実行時に探して呼び出しています。このとき呼ばれるのは、一番子供にあたるクラス(今回は PlayerStatus)で定義された Start() だけです。

PlayerStatus に Start() がある
  → PlayerStatus.Start() が呼ばれる(中身は空)
  → MobStatus.Start() は呼ばれない

つまり、子クラスの空の Start() が、親の Start() の出番を奪ってしまうのです。

例えると…

会社の新人研修で、「全社共通の研修(親)」と「配属先ごとの研修(子)」があるとします。配属先が「うちは研修なしでいいです」と言った結果、全社共通の研修まで丸ごと飛ばされてしまった。これと同じことが起きています。

なぜ気づきにくいのか

  • エラーも警告も出ない(文法的には正しいコードなので、コンパイルは通ります)
  • 書いた本人は「中身が空だから何も起きないはず」と思っている
  • Unityが自動で作ってくれるテンプレートなので、そもそも自分で書いた意識がない

原因が見えにくいため、何時間も悩んでしまう初心者がとても多いポイントです。

解決方法①:使わない Start() は削除する

一番シンプルな対処法です。子クラスで Start() を使わないなら、消してしまいましょう。

using UnityEngine;

public class PlayerStatus : MobStatus
{
    // Start() は書かない → 親の Start() が呼ばれる
}

これで MobStatus.Start() が実行され、HPが正しく初期化されます。Update() も同様に、使わないなら消しておくのがおすすめです。

解決方法②:親の処理も呼びつつ、子の処理も追加する

子クラスでもプレイヤー固有の初期化をしたい場合は、virtual と override を使い、base.Start() で親の処理を呼び出します。

using UnityEngine;

public class MobStatus : MonoBehaviour
{
    protected int hp;

    protected virtual void Start()    // virtual を付ける
    {
        hp = 100;
        Debug.Log("HPを初期化しました");
    }
}

public class PlayerStatus : MobStatus
{
    protected override void Start()   // override を付ける
    {
        base.Start();                 // 親の処理を呼ぶ
        Debug.Log("プレイヤー固有の初期化をしました");
    }
}

ポイントは次の3つです。

  1. 親側は protected virtual void Start()
  2. 子側は protected override void Start()
  3. 子の中で base.Start(); を呼ぶ

base.Start() を忘れると、最初の例と同じく親の処理が動かないので注意しましょう。

Start() 以外でも同じ

この現象は Start() だけの話ではありません。Awake()、Update()、OnEnable()、OnDestroy() など、Unityのメッセージ関数すべてで同じことが起こります。

まとめ

  • Unityのメッセージ関数は、一番下の子クラスで定義されたものだけが呼ばれる
  • 子クラスに空の Start() があると、親の Start() が実行されない
  • エラーは出ないので、気づきにくい
  • 対処法は2つ
    • 使わないなら削除する
    • 使うなら virtual / override + base.Start()

継承を使うときは、「新しいスクリプトを作ったら、使わない Start() と Update() をまず消す」。この習慣をつけておくと、こうしたトラブルを防げます。

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

広告

C#,Unity,クラス

Posted by hidepon