🐙

【8原則】SOLID実践で目指すUnity C#堅牢ゲームアーキテクチャ

2025/03/03に公開

閲覧いただきありがとうございます。はじめまして、ゲーム開発所RYURYUの「りゅうや」と申します。

❏ ゲーム開発ランキング【 1位 】実績多数 (ココナラ)
❏ ココナラ総販売【 220件超 】
❏ GC甲子園2022・東京ゲームショウ2023など出展経験あり

■ まずはこの教材でUnity開発の第一歩を体験してください! 今すぐ購入する
https://zenn.dev/ryuryu_game/books/fd28de9d8e963a/viewer/0570af

■ 記事に関するご質問やご意見は、Discordサーバーまでお寄せください。
https://discord.gg/5FwuKCacNy

【8原則】SOLID実践で目指すUnity C#堅牢ゲームアーキテクチャ

Unityでのゲーム開発において、堅牢でメンテナンス性の高いアーキテクチャを構築することは非常に重要です。本記事では、SOLIDの原則を実践しながら、Unity C#での堅牢なゲームアーキテクチャを目指す方法について詳しく解説します。初心者から中級者まで、具体的なコード例や図解を交えつつ、実践的なアプローチを紹介します。

SOLID原則とは

SOLIDは、オブジェクト指向設計の5つの基本原則を表す頭字語です。これらの原則を遵守することで、コードの可読性、再利用性、拡張性が向上し、バグの発生を減少させることができます。以下にSOLIDの各原則を簡潔に説明します。

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

クラスは単一の責任を持つべきであり、その責任を完全にカプセル化する必要があります。

オープン・クローズドの原則(Open/Closed Principle, OCP)

ソフトウェアのエンティティは拡張に対しては開かれており、修正に対しては閉じているべきです。

リスコフの置換原則(Liskov Substitution Principle, LSP)

サブタイプは親タイプと置換可能であるべきです。

インターフェース分離の原則(Interface Segregation Principle, ISP)

クライアントは使用しないメソッドへの依存を強制されるべきでないです。

依存性逆転の原則(Dependency Inversion Principle, DIP)

抽象に依存すべきであって、具体的な実装に依存してはならないです。

UnityにおけるSOLID原則の実践

UnityでSOLID原則を実践することで、プロジェクトのスケーラビリティと保守性を大幅に向上させることが可能です。以下では、各原則を具体的にどのようにUnity C#で実装するかを説明します。

単一責任の原則(SRP)の実装

単一責任の原則を遵守することで、各クラスが特定の機能に専念し、変更が容易になります。

具体例

PlayerMovement.cs
using UnityEngine;

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

    void Update()
    {
        float moveHorizontal = Input.GetAxis("Horizontal");
        float moveVertical = Input.GetAxis("Vertical");
        Vector3 movement = new Vector3(moveHorizontal, 0.0f, moveVertical);
        transform.Translate(movement * speed * Time.deltaTime);
    }
}
PlayerHealth.cs
using UnityEngine;

public class PlayerHealth : MonoBehaviour
{
    public int maxHealth = 100;
    private int currentHealth;

    void Start()
    {
        currentHealth = maxHealth;
    }

    public void TakeDamage(int amount)
    {
        currentHealth -= amount;
        if(currentHealth <= 0)
        {
            Die();
        }
    }

    void Die()
    {
        // プレイヤーの死亡処理
    }
}

上記の例では、PlayerMovementクラスがプレイヤーの移動に責任を持ち、PlayerHealthクラスがプレイヤーの健康管理に責任を持っています。これにより、各クラスの役割が明確になり、変更や拡張が容易になります。

オープン・クローズドの原則(OCP)の実装

オープン・クローズドの原則を守ることで、新しい機能の追加が既存のコードに影響を与えずに行えます。

具体例

IWeapon.cs
public interface IWeapon
{
    void Attack();
}
Sword.cs
public class Sword : IWeapon
{
    public void Attack()
    {
        Debug.Log("Swinging sword!");
    }
}
Bow.cs
public class Bow : IWeapon
{
    public void Attack()
    {
        Debug.Log("Shooting an arrow!");
    }
}
PlayerAttack.cs
using UnityEngine;

public class PlayerAttack : MonoBehaviour
{
    private IWeapon weapon;

    void Start()
    {
        weapon = new Sword(); // 依存性注入を利用することも可能
    }

    void Update()
    {
        if(Input.GetButtonDown("Fire1"))
        {
            weapon.Attack();
        }
    }

    public void SetWeapon(IWeapon newWeapon)
    {
        weapon = newWeapon;
    }
}

新しい武器を追加する場合、IWeaponインターフェースを実装した新しいクラスを作成するだけで、PlayerAttackクラスを変更する必要がありません。

リスコフの置換原則(LSP)の実装

リスコフの置換原則を遵守することで、サブクラスが親クラスと同等に振る舞い、予期しない動作を避けることができます。

具体例

Enemy.cs
public abstract class Enemy
{
    public abstract void Move();
}
FlyingEnemy.cs
public class FlyingEnemy : Enemy
{
    public override void Move()
    {
        Debug.Log("Flying enemy is moving.");
    }
}
GroundEnemy.cs
public class GroundEnemy : Enemy
{
    public override void Move()
    {
        Debug.Log("Ground enemy is moving.");
    }
}

Enemyクラスを継承したFlyingEnemyGroundEnemyは、Moveメソッドを適切に実装しており、親クラスEnemyの代わりに使用しても問題ありません。

インターフェース分離の原則(ISP)の実装

インターフェース分離の原則を守ることで、クラスが不要なメソッドの実装を強制されることを防ぎます。

具体例

IMovable.cs
public interface IMovable
{
    void Move();
}
IAttackable.cs
public interface IAttackable
{
    void Attack();
}
Player.cs
public class Player : MonoBehaviour, IMovable, IAttackable
{
    public void Move()
    {
        // プレイヤーの移動処理
    }

    public void Attack()
    {
        // プレイヤーの攻撃処理
    }
}
Mine.cs
public class Mine : MonoBehaviour, IMovable
{
    public void Move()
    {
        // 地雷の移動処理
    }
}

地雷は攻撃機能を持たないため、IAttackableインターフェースを実装する必要がありません。これにより、クラスが不要なメソッドを実装することを避けられます。

依存性逆転の原則(DIP)の実装

依存性逆転の原則を遵守することで、コードの柔軟性とテスト容易性が向上します。具体的には、高レベルモジュールが低レベルモジュールに依存せず、両者が抽象に依存します。

具体例

ILogger.cs
public interface ILogger
{
    void Log(string message);
}
ConsoleLogger.cs
public class ConsoleLogger : ILogger
{
    public void Log(string message)
    {
        Debug.Log(message);
    }
}
FileLogger.cs
public class FileLogger : ILogger
{
    public void Log(string message)
    {
        // ファイルにログを記録する処理
    }
}
GameManager.cs
using UnityEngine;

public class GameManager : MonoBehaviour
{
    private ILogger logger;

    void Start()
    {
        logger = new ConsoleLogger(); // 依存性注入を利用することも可能
        logger.Log("Game Started");
    }
}

GameManagerは具体的なロガーに依存せず、ILoggerという抽象に依存しています。これにより、ロギングの方法を簡単に変更することができます。

:::alert
依存性逆転の原則を実践する際には、**依存性注入(Dependency Injection)**を用いるとより効果的です。
:::

SOLID原則の8つ目の原則:DRYとYAGNIの追加

SOLIDは本来5つの原則ですが、さらに開発効率と品質を高めるために**DRY(Don't Repeat Yourself)YAGNI(You Aren't Gonna Need It)**の原則を追加で考慮します。

DRY(Don't Repeat Yourself)

同じコードやロジックを繰り返さないようにすることで、バグの発生を防ぎ、コードのメンテナンス性を向上させます。

具体例

共通の機能を持つクラスやメソッドを抽象化し、再利用可能なコンポーネントとして実装します。

Singleton.cs
public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
    private static T instance;

    public static T Instance
    {
        get
        {
            if(instance == null)
            {
                instance = FindObjectOfType<T>();
                if(instance == null)
                {
                    GameObject obj = new GameObject();
                    obj.name = typeof(T).Name;
                    instance = obj.AddComponent<T>();
                }
            }
            return instance;
        }
    }
}
GameManager.cs
public class GameManager : Singleton<GameManager>
{
    public int score;

    void Start()
    {
        score = 0;
    }
}

YAGNI(You Aren't Gonna Need It)

必要になるまで機能を実装しないことで、過剰なコードを避け、開発の効率を高めます。

具体例

機能追加の前に、本当に必要かどうかを検討し、不要な抽象化や機能実装を避けます。

// 不必要な機能を追加しない例
public class Enemy : MonoBehaviour
{
    public void Move()
    {
        // 移動処理
    }

    public void Attack()
    {
        // 攻撃処理
    }

    // 現在は攻撃機能が不要であれば、実装しない
}

テーブルで見るSOLID原則の比較

原則名 説明 メリット
単一責任の原則(SRP) クラスは単一の責任を持つべき 高い可読性と保守性
オープン・クローズドの原則(OCP) エンティティは拡張に対して開かれ、修正に対して閉じられている 柔軟な拡張と安定した既存機能
リスコフの置換原則(LSP) サブクラスは親クラスと置換可能であるべき 安全なポリモーフィズムの実現
インターフェース分離の原則(ISP) クライアントは使用しないメソッドに依存するべきでない 不要な依存の排除とインターフェースの明確化
依存性逆転の原則(DIP) 抽象に依存し、具体に依存しない 柔軟でテスト可能なコードの実現
DRY(Don't Repeat Yourself) コードの重複を避ける バグの減少とコードのメンテナンス性向上
YAGNI(You Aren't Gonna Need It) 必要になるまで機能を実装しない 開発効率の向上と過剰な機能の回避

まとめ

SOLIDの原則をUnity C#で実践することで、堅牢で拡張性の高いゲームアーキテクチャを構築することが可能です。各原則を理解し、具体的なコード例とともに適用することで、開発効率とコード品質を大幅に向上させることができます。ぜひ本記事を参考に、あなたのUnityプロジェクトにSOLID原則を取り入れてみてください。

Unityをもっと極めたい"あなた"へ ― 今すぐスキルアップのチャンス!

1. どこでもUnity教室「無料プラン」

❏ 毎日の質問で即解決|Unityに関する疑問や悩みは、専用Discordでプロの仲間とシェア!

  • 月額0円 で、テキストで気軽に質問・進捗共有が可能
  • 実績多数のコミュニティで、参加するだけで具体的な課題解決のヒントが手に入る

まずは無料で参加して、あなたのUnity学習を加速させましょう! 無料でDiscordに参加する]
https://discord.gg/5FwuKCacNy

2. Unity超入門書【1,000円】

Unityスキルを5日間でマスター|「実践×即戦力」を手に入れる!

  • 130,000文字超の詳細な解説と実例で、初心者でもすぐにUnityの基礎が身につく
  • 実際の成果例:5日間でシンプルな3D FPSゲームを完成
  • 専属講師サポートのオプション付きで、疑問を即解消しながら学習を進められる

まずはこの教材でUnity開発の第一歩を体験してください! 教材を今すぐ購入する
https://zenn.dev/ryuryu_game/books/fd28de9d8e963a/viewer/0570af

3. Unity超入門完全支援プラン

Unityの全てをプロがバックアップ|教材で学んだ内容を実践サポート!

  • 専属講師による24時間テキスト質問サポート(毎日17:00~21:00の回答)
  • 月2回×60分 または 月1回×120分のビデオチャットで、学習進捗やプロジェクトの具体的な課題を徹底サポート
  • 教材と連携し、実践の現場での疑問や課題をそのまま解決!
  • 限定:1度に最大10名様のみ受付!早期申込で安心のサポート体制を

教材で学んだ知識をさらに深め、実践に活かすならこのプランがおすすめです! 今すぐ詳細を確認する
https://ryuryu.memberpay.jp/service/item/yjo1sst

4. Unityプロジェクト完全支援プラン

Unityプロジェクトを本格サポート|個人の趣味からプロの現場まで幅広く対応!

  • 専属講師による24時間テキスト質問サポート(毎日17:00~21:00の回答)
  • 月2回×60分のビデオチャットで、プロジェクトの進行状況を細かくサポート
  • Unity開発の一部を代行するサービスが常に20%割引で利用可能
  • 基本操作からエラー対応、プロジェクト設計のアドバイスまで幅広くサポート
  • 専用Discordサーバーでのサポート体制(ご購入後に招待リンクを送付)

個人プロジェクトを着実に進め、より高い成果を求めるあなたに最適なプランです! 今すぐプロジェクト支援プランを確認する
https://ryuryu.memberpay.jp/plan/item/epeiywt

Discussion