【Unity】継承、ポリモーフィズムの学習

2023年10月12日

基本的な学習

プロジェクトを企画では、どのようなゲームオブジェクトが必要か考えます
たとえば、複数のユニークな種類の敵を登場させることを考えてみましょう
その場合、いくつかの属性(パラメータ)は共通のケースがあると思います

そのような時、それぞれの敵にコピーされたスクリプトをアタッチするのではなく、共通部分はまとめて定義するとコードがスッキリします

今回は、車を例にとってどのように構成すれば良いのかみていきましょう

シーンの構成

ゲームをコントロールするマネージャオブジェクト(空のゲームオブジェクト)と、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

学習のためのリンク