Unityで学ぶプログラミングの基本ルール:SOLID原則

はじめに:SOLID原則とは?

ゲームやアプリを作るとき、プログラムがわかりやすくて、直しやすくて、作りやすいととても便利です。そんなときに役立つのが「SOLID原則(ソリッドげんそく)」と呼ばれる5つのルールです。

この資料では、Unityというゲームエンジンを使って、SOLID原則をやさしく学べるように、実際のコード例といっしょに紹介します。

SOLID原則のメリット:

  • プログラムが整理されて、あとで直すのが簡単になります。
  • 新しい機能を追加するとき、前のコードにあまり触らなくてすみます。
  • 同じクラスやスクリプトを、ほかのゲームでも使いやすくなります。
  • テスト(うまく動くか確認すること)がしやすくなります。
  • チームで作業するとき、役割がはっきりするので、みんなで作りやすくなります。
  • 不具合(バグ)が起きたとき、どこを直せばいいか分かりやすくなります。

SOLIDの意味:

  • S: 単一責任の原則(Single Responsibility Principle)
  • O: 開いていて閉じている原則(Open/Closed Principle)
  • L: リスコフの置きかえ原則(Liskov Substitution Principle)
  • I: インターフェース分けの原則(Interface Segregation Principle)
  • D: 依存の向きを逆にする原則(Dependency Inversion Principle)

原則1:単一責任の原則(SRP)

1つのクラスには、1つの仕事だけをさせよう!

クラスにたくさんの役割を持たせると、あとで変更が大変になります。この原則では、「ひとつのクラス=ひとつの役割」と考えます。

// スコアを管理するクラス
public class ScoreManager : MonoBehaviour
{
    private int score = 0;
    public void AddScore(int points)
    {
        score += points;
        Debug.Log($"Score: {score}");
    }
}

// ゲームの流れを管理するクラス
public class GameManager : MonoBehaviour
{
    public ScoreManager scoreManager;

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.S))
            scoreManager.AddScore(10);
    }
}

スコアの処理はScoreManager、ゲームの流れはGameManagerが担当。
スコアの仕組みを変えたくなっても、ScoreManagerだけ直せばOKです。


原則2:開いていて閉じている原則(OCP)

新しい機能は追加OK。でも、古いコードはなるべく変えない!

この原則では、「拡張はOK、変更はNG」がキーワードです。あとから新しい機能を増やすのに、前のコードを触らずにすむと安心です。

public interface IAttack
{
    void Execute();
}

public class MeleeAttack : MonoBehaviour, IAttack
{
    public void Execute() => Debug.Log("🔪 ちかづいて攻撃!");
}

public class RangedAttack : MonoBehaviour, IAttack
{
    public void Execute() => Debug.Log("🏹 とおくから攻撃!");
}

public class Player : MonoBehaviour
{
    public MonoBehaviour attackBehaviour; // MeleeAttack または RangedAttack
    private IAttack attack;

    void Awake() => attack = attackBehaviour as IAttack;

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
            attack.Execute();
    }
}

新しい攻撃方法(例:魔法攻撃)を追加しても、PlayerクラスはそのままでOK!


原則3:リスコフの置きかえ原則(LSP)

親クラスの代わりに子クラスを使っても、ちゃんと動くようにしよう!

いろんな敵を同じように扱えたら便利ですね。親クラスの「Enemy」と同じルールを守っていれば、どんな子クラスでも使えます。

public abstract class Enemy : MonoBehaviour
{
    public abstract void Attack();
}

public class Goblin : Enemy
{
    public override void Attack() => Debug.Log("🗡️ ゴブリンの攻撃!");
}

public class Troll : Enemy
{
    public override void Attack() => Debug.Log("🔨 トロールの攻撃!");
}

public class EnemyManager : MonoBehaviour
{
    public List<Enemy> enemies;

    void Start()
    {
        foreach (var e in enemies)
            e.Attack();
    }
}

GoblinもTrollも、Enemyとして扱えるから、コードを分けずに一括で使えます。


原則4:インターフェース分けの原則(ISP)

必要な機能だけ、必要な人に!

「全部入り」のインターフェースより、「必要な分だけ」を分けて使った方が使いやすいです。

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

public interface IDamageable
{
    void TakeDamage(int amount);
}

public class Player : MonoBehaviour, IMovable
{
    public float speed = 5f;
    public void Move(Vector3 dir) => transform.Translate(dir * speed * Time.deltaTime);

    void Update()
    {
        var dir = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
        Move(dir);
    }
}

public class DestructibleBox : MonoBehaviour, IDamageable
{
    public int hp = 3;
    public void TakeDamage(int amount)
    {
        hp -= amount;
        if (hp <= 0) Destroy(gameObject);
    }
}

プレイヤーは「動く」、箱は「ダメージを受ける」。それぞれ必要なことだけ持っています。


原則5:依存の向きを逆にする原則(DIP)

大事な役割は、細かい仕組みにしばられないように!

細かい実装に直接つながるのではなく、「ルール(インターフェース)」に頼ることで、入れ替えや変更が楽になります。

public interface IAudioService
{
    void PlaySound(string clipName);
}

public class AudioService : MonoBehaviour, IAudioService
{
    public AudioClip[] clips;
    private AudioSource source;

    void Awake() => source = gameObject.AddComponent<AudioSource>();

    public void PlaySound(string name)
    {
        var clip = System.Array.Find(clips, c => c.name == name);
        if (clip != null) source.PlayOneShot(clip);
    }
}

public class SoundManager : MonoBehaviour
{
    private IAudioService audioService;

    void Awake() => audioService = FindObjectOfType<AudioService>();

    public void OnButtonClick() => audioService.PlaySound("Click");
}

SoundManagerは「音を鳴らす」だけを考え、どう鳴らすかはAudioServiceにまかせています。
こうすることで、別の音再生方法にもすぐに差し替えできます。


おわりに:SOLID原則を使って、よいプログラムを作ろう!

この5つの原則は、どれも「読みやすく、直しやすく、安全なコード」を作るための大事な考え方です。

最初はむずかしく感じるかもしれません。でも、少しずつ使ってみることで自然と身につきます。

Unityでのゲーム作りを楽しみながら、SOLID原則も覚えていきましょう!