Unityにおける継承と合成(コンポジション)

概要

Unityはコンポーネントベースの設計思想を採用しており、オブジェクトにさまざまな機能を付与するために「継承」ではなく「合成」を使用することが推奨されています。本資料では、継承と合成の違いをサンプルコードを用いて比較し、合成のメリットを解説します。


継承と合成(コンポジション)の違い

継承の特徴

  • 親クラスと子クラスの関係で機能を共有します。
  • 機能を拡張しやすい一方で、階層が深くなると保守性が低下します。
  • クラス同士が強く結びつくため、再利用性が低くなる傾向があります。

合成(コンポジション)の特徴

  • 小さな機能単位を「コンポーネント」として独立して実装し、必要に応じて組み合わせます。
  • 再利用性が高く、機能の追加や変更が容易です。
  • 各コンポーネントが独立しているため、他のスクリプトへの影響が少なくなります。

継承を使った例

以下の例は、プレイヤーキャラクターが「移動」と「ジャンプ」の機能を持つ場合です。

BaseCharacter.cs

using UnityEngine;

public class BaseCharacter : MonoBehaviour
{
    public float speed = 5f;

    // 移動処理
    public virtual void Move()
    {
        float h = Input.GetAxis("Horizontal");
        float v = Input.GetAxis("Vertical");
        transform.Translate(new Vector3(h, 0, v) * speed * Time.deltaTime);
    }

    // Updateは子クラスでオーバーライド可能
    public virtual void Update()
    {
        Move();
    }
}

PlayerCharacter.cs

using UnityEngine;

public class PlayerCharacter : BaseCharacter
{
    public float jumpForce = 5f;

    // 移動処理を拡張し、ジャンプも追加
    public override void Move()
    {
        base.Move(); // 親クラスの移動処理を呼び出す

        if (Input.GetButtonDown("Jump"))
        {
            Jump();
        }
    }

    private void Jump()
    {
        Rigidbody rb = GetComponent<Rigidbody>();
        if (rb != null)
        {
            rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
        }
    }

    // Updateの挙動をカスタマイズ
    public override void Update()
    {
        Move(); // オーバーライドされたMoveを呼び出す
    }
}

問題点

  • PlayerCharacterに特化したジャンプ処理がBaseCharacterに影響しない設計が困難。
  • 他のオブジェクトに「移動」や「ジャンプ」機能を適用しにくい。

合成(コンポジション)を使った例

MoveComponent.cs

using UnityEngine;

public class MoveComponent : MonoBehaviour
{
    public float speed = 5f;

    void Update()
    {
        float h = Input.GetAxis("Horizontal");
        float v = Input.GetAxis("Vertical");
        transform.Translate(new Vector3(h, 0, v) * speed * Time.deltaTime);
    }
}

JumpComponent.cs

using UnityEngine;

[RequireComponent(typeof(Rigidbody))]
public class JumpComponent : MonoBehaviour
{
    public float jumpForce = 5f;

    void Update()
    {
        if (Input.GetButtonDown("Jump"))
        {
            Jump();
        }
    }

    private void Jump()
    {
        GetComponent<Rigidbody>().AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
    }
}

使い方

  1. プレイヤーオブジェクトにMoveComponentJumpComponentをそれぞれアタッチします。
  2. 必要に応じて他のオブジェクトにも追加可能です(例: 敵キャラクターが移動だけを持つなど)。

合成(コンポジション)のメリット

再利用性

各機能が独立しているため、他のゲームオブジェクトにも簡単に使い回しが可能です。

柔軟性

必要な機能だけを組み合わせることで、オブジェクトごとに異なる動作を実現できます。

保守性

コンポーネントが独立しているため、変更が他のコードに影響を与えません。

単一責任の原則(SRP: Single Responsibility Principle)

各コンポーネントが1つの責任に集中しているため、コードがシンプルで読みやすくなります。


比較表

項目継承合成
再利用性低い高い
拡張性階層が深くなるほど難しいコンポーネントを追加するだけで簡単
柔軟性低い高い
保守性変更が他のクラスに影響する可能性あり独立しているため影響が少ない

結論

Unityにおけるゲーム開発では、「継承」よりも「合成」を使用する方が柔軟で再利用性が高い設計を実現できます。特に大規模なプロジェクトや長期的な保守を考慮する場合、合成の設計思想を取り入れることを強く推奨します。