【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つです。
- 親側は
protected virtual void Start() - 子側は
protected override void Start() - 子の中で
base.Start();を呼ぶ
base.Start() を忘れると、最初の例と同じく親の処理が動かないので注意しましょう。
Start() 以外でも同じ
この現象は Start() だけの話ではありません。Awake()、Update()、OnEnable()、OnDestroy() など、Unityのメッセージ関数すべてで同じことが起こります。
まとめ
- Unityのメッセージ関数は、一番下の子クラスで定義されたものだけが呼ばれる
- 子クラスに空の
Start()があると、親のStart()が実行されない - エラーは出ないので、気づきにくい
- 対処法は2つ
- 使わないなら削除する
- 使うなら
virtual/override+base.Start()
継承を使うときは、「新しいスクリプトを作ったら、使わない Start() と Update() をまず消す」。この習慣をつけておくと、こうしたトラブルを防げます。










ディスカッション
コメント一覧
まだ、コメントがありません