【Unity】継承、ポリモーフィズムの学習
基本的な学習
プロジェクトを企画では、どのようなゲームオブジェクトが必要か考えます
たとえば、複数のユニークな種類の敵を登場させることを考えてみましょう
その場合、いくつかの属性(パラメータ)は共通のケースがあると思います
そのような時、それぞれの敵にコピーされたスクリプトをアタッチするのではなく、共通部分はまとめて定義するとコードがスッキリします
今回は、車を例にとってどのように構成すれば良いのかみていきましょう
シーンの構成
ゲームをコントロールするマネージャオブジェクト(空のゲームオブジェクト)と、1台のスポーツカーオブジェクトを登場させています
スポーツカーゲームオブジェクト

参考)作り方

ゲームマネージャーゲームオブジェクト

スクリプト
Carクラスは登場する各種車の共通の情報が記述されるものとします
車ごとの違いは、派生クラスの側に記述します
using UnityEngine;
public class Car : MonoBehaviour
{
public virtual void Run()
{
Debug.Log("基本走行で走るよ");
}
}
using UnityEngine;
public class SportCar : Car
{
public override void Run()
{
Debug.Log("スポーツカーが走る");
base.Run();
}
}
マネージャは、車オブジェクトにアタッチされているスクリプト(コンポーネント)を取得して、メソッドを実行します
この際、継承元のクラスで取得するようにします
これをポリモーフィズムと言います
using UnityEngine;
public class Manager : MonoBehaviour
{
[SerializeField]
GameObject car;
void Start()
{
Car carObj = car.GetComponent<Car>();
carObj.Run();
}
}
ポイント
car.GetComponent<Car>();
スポーツカーゲームオブジェクトには、Carスクリプトはあたったされていませんが(SportCarスクリプトですね)、Carを継承しているので、取得できます
発展的な学習(便利に使う)
基本型ができたところで新しい種類の車を作成してみましょう
今回はクラシックカーを追加します
シーンの構成
上記の構成にクラシックカーオブジェクトw追加します
クラシックカーゲームオブジェクト

ゲームマネージャーゲームオブジェクト
アウトレット接続に、クラシックカーを代入します

スクリプト
using UnityEngine;
public class ClassicCar : Car
{
public override void Run()
{
Debug.Log("クラシックカーが走る");
base.Run();
}
}
クラス名が変わっただけになりますね
実行結果
Carクラスで宣言している変数にも関わらず、派生クラスのメソッドが呼ばれているのがわかりますね
また、base.Run()メソッドは、base(基本、基底)のメソッドを呼ぶことを指しています
クラシックカーが走る
基本走行で走るよ

派生クラスにRunメソッドがない場合、困ります
マネージャーからRunメソッドがありきで読んでいるのですが、これまでのコードでは強制力はありません
強制的に存在しなけらばならないようにしましょう
Carクラスを抽象クラスに変更します(abstract修飾子をつける)
まず、これにより、このクラスからインスタンスが作成できなくもしています
スポーツカーやクラシックカーは必要ですが単なる「カー」は登場しなくて良いため(してほしくない)
using UnityEngine;
public abstract class Car : MonoBehaviour
{
public void RunBaseCar()
{
Debug.Log("基本走行で走るよ");
}
public abstract void Run();
}
ポイント
public abstract void Run();
メソッドの形をしていますが、ブロックがありませんね
これは、このクラスを継承したクラス(派生クラス)にこの名前のメソッドを強制的に作成するよう促しています
(派生クラスにRunメソッドが存在しないとビルドできません。つまり実行ができないことになります)
using UnityEngine;
public class SportCar : Car
{
public override void Run()
{
Debug.Log("スポーツカーが走る");
RunBaseCar();
}
}
using UnityEngine;
public class ClassicCar : Car
{
public override void Run()
{
Debug.Log("クラシックカーが走る");
RunBaseCar();
}
}

発展的な学習(複数のゲームオブジェクトに対してまとめて処理して楽をする)
折角のポリモーフィズムですが、便利機能を使いきれていません
まず、マネージャクラスを簡単にしてみましょう
これまでのマネージャクラスはGameObjectクラスの型をドラッグ&ドロップしていましたが、Carクラスを代入するようにしましょう
using UnityEngine;
public class Manager : MonoBehaviour
{
[SerializeField]
Car car;
void Start()
{
car.Run();
}
}
どうでしょう?ずいぶんシンプルになりました
スポーツカーやクラシックカーのいずれでもドラッグ&ドロップできることに変わりはありません
複数のゲームオブジェクトをインスペクターから代入できるようにする
コードを次のように変更しましょう
using System.Collections.Generic;
using UnityEngine;
public class Manager : MonoBehaviour
{
[SerializeField]
List<Car> cars;
void Start()
{
foreach (var car in cars)
{
car.Run();
}
}
}
インスペクターで複数の変数が代入できるようになりましたので、2つをドラッグ&ドロップしてみましょう
これは、Listなので幾つでも入ります

実行結果
スポーツカーが走る
基本走行で走るよ
クラシックカーが走る
基本走行で走るよ
まとめ
継承、ポリモーフィズムを使うことで便利なことは次のようになりますね
- 操作する側(使う側、今回はManagerですね)から見ると相手を意識することがありません
- 操作する側で、同じ基底クラスを継承している派生クラスを持つゲームオブジェクトに対してまとめて操作することができます
- 派生クラスでは、基本クラス以外の情報に絞ったコードを記述するだけで大丈夫です
以上のような特徴を活かせるプロジェクトである場合、積極的に活用してみましょう
おまけ
PlantUMLクラス図
@startuml UnityClassDiagram
!define UNITY_MONOBEHAVIOUR abstract
!define SERIALIZEFIELD <<SerializeField>>
class MonoBehaviour {
-void Start()
}
class Debug {
+Log(message: string)
}
class GameObject {
}
class Car {
+Run()
}
class SportCar {
+Run()
}
class Manager {
+car: GameObject
- {SERIALIZEFIELD} carObj: Car
+Start()
}
MonoBehaviour <|-- Car
MonoBehaviour <|-- Manager
Car <|-- SportCar
@enduml
@startuml UnityClassDiagram
!define UNITY_MONOBEHAVIOUR abstract
!define SERIALIZEFIELD <<SerializeField>>
class MonoBehaviour {
-void Start()
}
class Debug {
+Log(message: string)
}
class GameObject {
}
abstract class Car {
+Run()
}
class SportCar {
+Run()
}
class ClassicCar {
+Run()
}
class Manager {
+car: GameObject
- {SERIALIZEFIELD} carObj: Car
+Start()
}
MonoBehaviour <|-- Car
MonoBehaviour <|-- Manager
Car <|-- SportCar
Car <|-- ClassicCar
@enduml
ディスカッション
コメント一覧
まだ、コメントがありません