Unityで実践する動物オブジェクトのポリモーフィズムと移動機能の実装

本資料では、Unity を用いて「動物」というテーマのもと、各動物をゲームオブジェクトとして実装しながら、ポリモーフィズムによる共通機能の管理と、地上移動および飛行移動の実装方法を学びます。
特に、地上移動は IMovable インターフェース(水平移動:Vector3.forward)で実装し、鳥は IFlyable インターフェースを利用して、上昇移動(Vector3.up)を行う設計例を紹介します。


1. Animal クラスの基本設計

Animal クラスは、各動物の共通情報(名前、鳴く機能など)を管理する抽象クラスです。
MonoBehaviour を継承しているため、各動物はシーン上のゲームオブジェクトとして存在します。

using UnityEngine;

// Animal はすべての動物が共通に持つ情報を管理する抽象クラス
public abstract class Animal : MonoBehaviour
{
    public string animalName;

    // 共通の鳴き声機能(派生クラスでオーバーライド可能)
    public virtual void MakeSound()
    {
        Debug.Log(animalName + "が鳴いています。");
    }
}

2. 移動機能の実装方法

動物の移動は、各動物の性質に合わせた実装が可能です。
ここでは、地上移動(犬、猫など)と、飛行移動(鳥)の 2 つのアプローチを紹介します。

2-1. 地上移動:IMovable インターフェースを利用

地上移動対象(犬、猫など)は、IMovable インターフェースを実装し、ゲームオブジェクト上で水平移動(XZ平面での移動、ここでは前方向=Vector3.forward)を実現します。

IMovable インターフェースの定義

using UnityEngine;

public interface IMovable
{
    void Move(Vector3 direction, float speed);
}

Dog.cs(犬の場合:IMovable を実装)

using UnityEngine;

public class Dog : Animal, IMovable
{
    public override void MakeSound()
    {
        Debug.Log(animalName + " (犬): ワンワン!");
    }

    public void Move(Vector3 direction, float speed)
    {
        // 地面に沿った水平移動(XZ平面での移動)
        transform.Translate(direction * speed * Time.deltaTime);
        Debug.Log(animalName + " (犬) が地上を移動しました。");
    }
}

Cat.cs(猫の場合:IMovable を実装)

using UnityEngine;

public class Cat : Animal, IMovable
{
    public override void MakeSound()
    {
        Debug.Log(animalName + " (猫): ニャー!");
    }

    public void Move(Vector3 direction, float speed)
    {
        transform.Translate(direction * speed * Time.deltaTime);
        Debug.Log(animalName + " (猫) がすばしっこく移動しました。");
    }
}

2-2. 飛行移動:IFlyable インターフェースを利用

鳥の場合、地上移動とは異なり「飛ぶ」専用の動作が必要となるため、IFlyable インターフェースを用いて上方向(Vector3.up)の移動など特有の挙動を実装します。

IFlyable インターフェースの定義

using UnityEngine;

public interface IFlyable
{
    // 飛行動作を実装するメソッド
    void Fly(Vector3 direction, float speed);
}

Bird.cs(鳥の場合:IFlyable を実装)

using UnityEngine;

public class Bird : Animal, IFlyable
{
    public override void MakeSound()
    {
        Debug.Log(animalName + " (鳥): ピヨピヨ!");
    }

    // 飛行専用の処理:ここでは上昇(Vector3.up)の方向に対応
    public void Fly(Vector3 direction, float speed)
    {
        // 単純な例として Translate を利用(実際には高度制御や物理演算を追加可能)
        transform.Translate(direction * speed * Time.deltaTime);
        Debug.Log(animalName + " (鳥) が飛んで移動しました。");
    }
}

3. AnimalManager による統一的な制御

AnimalManager は、シーン上に配置された各動物のゲームオブジェクト(Animal コンポーネント付き)を Inspector 経由で登録し、統一的な操作を行います。
ここでは、次の操作を実装します。

  • 地上移動:W キーが押されると、Dog や Cat などの地上移動対象は Vector3.forward(前方向=水平移動)に沿って移動します。
  • 飛行移動:W キーが押されたとき、Bird などの飛行移動対象は Vector3.up(上方向=上昇)に沿って移動します。
using UnityEngine;
using System.Collections.Generic;

public class AnimalManager : MonoBehaviour
{
    // Inspector で各動物のゲームオブジェクト(Animal コンポーネント付き)を登録
    public List<Animal> animals = new List<Animal>();

    // 移動速度のパラメータ
    public float groundMoveSpeed = 2f;
    public float flySpeed = 3f;

    // 飛行可能な動物(Bird など)を抽出して管理
    private List<IFlyable> flyingAnimals = new List<IFlyable>();

    void Start()
    {
        // 登録された Animal から IFlyable を実装しているものを抽出
        foreach (Animal animal in animals)
        {
            if (animal is IFlyable flyable)
            {
                flyingAnimals.Add(flyable);
            }
        }

        // 確認用に、各動物の鳴き声を再生
        foreach (Animal animal in animals)
        {
            animal.MakeSound();
        }
    }

    void Update()
    {
        // キー入力例:
        // W キーを押したら、地上移動は Vector3.forward(水平移動)、
        // 飛行移動は Vector3.up(上昇)にそれぞれ実行される
        if (Input.GetKey(KeyCode.W))
        {
            MoveGroundAnimals(Vector3.forward);
            FlyAnimals(Vector3.up);
        }
    }

    // 地上移動対象の動物(Dog、Cat など)に対して Move を呼び出す
    void MoveGroundAnimals(Vector3 direction)
    {
        foreach (Animal animal in animals)
        {
            // 飛行する動物は除外
            if (animal is IFlyable)
                continue;

            // IMovable を実装している場合に移動命令を発行
            if (animal is IMovable movable)
            {
                movable.Move(direction, groundMoveSpeed);
            }
        }
    }

    // 飛行可能な動物に対して Fly を呼び出す
    void FlyAnimals(Vector3 direction)
    {
        foreach (IFlyable flyable in flyingAnimals)
        {
            flyable.Fly(direction, flySpeed);
        }
    }
}

以下は、MoveGroundAnimals と FlyAnimals の実装方法に差がある理由と、それぞれの設計方針についての解説です。


1. 別々のリストを使った設計と直接フィルタリングの違い

FlyAnimalsの場合:

  • 飛行する動物(Bird など)は、IFlyable インターフェースを実装しているため、AnimalManager の Start() で専用のリスト(flyingAnimals)に抽出しています。
  • そのため、FlyAnimals ではこのリストを直接ループでき、各要素に対して Fly メソッドを呼び出す単純なブロックコードとなっています。
void FlyAnimals(Vector3 direction)
{
    foreach (IFlyable flyable in flyingAnimals)
    {
        flyable.Fly(direction, flySpeed);
    }
}

MoveGroundAnimalsの場合:

  • 地上移動対象(Dog や Cat など)は、専用のリストとして抽出していません。
  • その代わり、全体の animals リストを走査し、IFlyable を実装している(=飛行する動物)かどうかをフィルタリング(continue)して、残りの IMovable を実装しているオブジェクトに対して Move を呼び出しています。
void MoveGroundAnimals(Vector3 direction)
{
    foreach (Animal animal in animals)
    {
        // IFlyable を実装しているもの(飛行対象)は対象外
        if (animal is IFlyable)
            continue;

        // IMovable を実装している場合に移動命令を発行
        if (animal is IMovable movable)
        {
            movable.Move(direction, groundMoveSpeed);
        }
    }
}

2. この実装方法にした理由

  • 専用のリスト抽出の必要性:
    飛行する動物は、IFlyable インターフェースで明確に区別できるため、専用リストに抽出することで、「飛行」という特殊な挙動をまとめて管理しやすくしています。
  • 地上移動対象は多様性がある:
    一方、地上を移動する動物は Dog、Cat など複数の種類が含まれ、共通して IMovable を実装していますが、これらをわざわざ専用リストにまとめなくても、全体リストから IFlyable を除外することで対応できると考えました。
    これにより、すべての Animal オブジェクトは一つのリストにまとめられ、必要に応じてフィルタリングするというシンプルな設計としています。
  • 設計の意図と柔軟性:
    この方法は、もし後で地上移動対象が非常に多くなってパフォーマンスや管理面で専用リストが必要になった場合、同様に専用の groundMovers リストを用意する設計へ変更する余地があるという点でも柔軟です。
    今回はサンプルとして、飛行対象だけ専用リストにして、その他は全体リストからフィルタリングする形にしています。

3. まとめ

  • FlyAnimals:
    IFlyable のみを集めた専用リスト(flyingAnimals)を使うことで、単純明快に飛行動作の呼び出しを実装しています。
  • MoveGroundAnimals:
    全体リストから IFlyable を除外する方式を採用することで、地上移動対象を動的にフィルタリングしています。
    これにより、すべての Animal を一元管理しながら、地上移動対象と飛行対象に分けることができます。

このような設計は、要件やプロジェクトの規模・複雑性に応じて変更可能ですが、今回のサンプルコードではそれぞれの用途に合わせたシンプルなアプローチとして採用しています。


4. シーンでの利用方法

  1. ゲームオブジェクトの作成
    • 各動物(Dog、Cat、Bird)のスクリプトをそれぞれの GameObject にアタッチします。
    • 例:シーン内に「DogObject」「CatObject」「BirdObject」と名前をつけた GameObject を配置し、各スクリプトを割り当てます。
  2. Inspector への登録
    • AnimalManager を持つ空の GameObject を作成し、Inspector の animals リストに上記各動物の GameObject をドラッグ&ドロップで登録します。
    • 必要に応じて各 GameObject 内の animalName を設定してください。
  3. シーンの実行
    • シーンを再生すると、AnimalManager の Start() 内で各動物が鳴いた後、W キーを押すことで以下の動作が実現されます。
      • 地上移動対象(Dog、Cat)は、前方向(Vector3.forward)に沿って水平移動します。
      • 飛行移動対象(Bird)は、上方向(Vector3.up)に沿って上昇移動します。

5. まとめ

  • ゲームオブジェクトとしての実体:
    各動物は MonoBehaviour を継承しているため、シーン上の GameObject として動作し、個別の位置や挙動を持ちます。
  • 移動機能の責務の分離:
    地上移動は IMovable インターフェース(または Animal クラスの仮想メソッド)により実装し、鳥のような特殊な「飛ぶ」動作は IFlyable インターフェースで明確に分離・実装することで、保守性・拡張性が向上します。
  • AnimalManager による統一制御:
    Inspector に登録した各動物を管理し、W キー入力により地上移動は Vector3.forward(水平移動)、飛行移動は Vector3.up(上昇)で実行することで、直感的かつ明確な操作が可能です。

この資料をもとに、Unity プロジェクトで各動物をゲームオブジェクトとして実装し、実践的な動作やインタラクションの実現にお役立てください。