Unityでゲーム開発をしていると、Enemy、Player、Itemといったクラスを自然に作ります。実はこの時点で、あなたはすでにオブジェクト指向の入り口に立っています。
しかし、以下のような疑問をスッキリ解決できているでしょうか?
- なぜ Enemy 型の変数で Slime を扱えるのか?
- なぜ if 文で分岐させず、Attack() を呼び出すだけでいいのか?
- 継承とポリモーフィズムの決定的な違いは何か?
この記事では、Unityの具体的なコードを通して、「オブジェクト指向を理解して、設計に活かす」ためのエッセンスを解説します。
この記事でわかること
- オブジェクト指向とは何か(Unity例)
- 手続き型プログラムとの違い
- オブジェクト指向の基本要素4つ
- Enemy / Slime / Dragon を使った実践的な設計例
オブジェクト指向とは「役割のパッケージ化」
オブジェクト指向とは、一言で言えば「ゲーム内の登場人物や仕組みを、そのまま『独立したモノ』として定義する考え方」です。
Unityでは、GameObject にアタッチする MonoBehaviour 自体がオブジェクト指向の体現です。

手続き型 vs オブジェクト指向
かつての「手続き型」では、データと処理がバラバラになりがちでした。
| 特徴 | 手続き型(if/switch依存) | オブジェクト指向 |
| 中心 | 処理の流れ(アルゴリズム) | モノ(クラス)の役割 |
| 拡張性 | 敵が増えるたびに if が増える | 新しいクラスを作るだけで完結 |
| 保守性 | どこを直せばいいか迷子になる | 関連するデータと処理が一箇所に集まる |

手続き型の特徴
- 処理の流れが中心
- if / switch が増え続ける
- 敵が増えるたびに修正が必要
int playerHp;
float playerSpeed;
void PlayerMove() {}
void PlayerAttack() {}
void EnemyAttack() {}オブジェクト指向の特徴
- 関連するものが1か所に集まる
- 修正・拡張が楽
- Unityのコンポーネント設計と相性抜群
class Player
{
public int hp;
public float speed;
public void Move() {}
public void Attack() {}
}オブジェクト指向の4大要素:Unity実践編
Unity開発で特に重要な「カプセル化」「継承」「抽象化」「ポリモーフィズム」を解説します。
カプセル化:勝手にステータスを書き換えさせない
「HPを直接いじらせず、ダメージ計算用のメソッドを通す」のがカプセル化の基本です。
public class Player : MonoBehaviour
{
// [SerializeField]でインスペクターには出すが、他クラスからは隠す
[SerializeField] private int hp = 100;
// HPへの操作は必ずこのメソッドを経由させる
public void TakeDamage(int damage)
{
hp = Mathf.Max(0, hp - damage); // 0以下にならない等の制限をかけられる
if (hp <= 0) Die();
}
private void Die() => Debug.Log("死亡しました");
}
抽象化 & 継承:共通のルールを決める
「敵(Enemy)なら攻撃(Attack)ができるはずだ」という共通のルールを作るのが抽象化、それを具体化するのが継承です。
// 【抽象化】Enemyとは何かを定義する(設計図)
public abstract class Enemy : MonoBehaviour
{
public abstract void Attack(); // 具象クラスで必ず実装させる
}
// 【継承】Enemyのルールを引き継いで具体化
public class Slime : Enemy
{
public override void Attack() => Debug.Log("スライムの体当たり!");
}
public class Dragon : Enemy
{
public override void Attack() => Debug.Log("ドラゴンの火炎放射!");
}

ポリモーフィズム:同じ命令で違う動きをさせる
ここが一番の魔法です。相手がスライムかドラゴンかを知らなくても、「Enemy(敵)ならAttackしろ」と命令するだけで、適切な動きが実行されます。
public class BattleManager : MonoBehaviour
{
void ExecuteEnemyTurn()
{
// ヒエラルキー上の全Enemyを取得(SlimeもDragonも混ざってOK)
Enemy[] enemies = FindObjectsOfType<Enemy>();
foreach (var enemy in enemies)
{
// 相手が誰であっても「Attack()」を呼ぶだけ!
// Slimeなら体当たり、Dragonなら火炎が出る
enemy.Attack();
}
}
}
なぜ「if文」より「オブジェクト指向」なのか?
もしポリモーフィズムを使わずに敵の攻撃を実装しようとすると、こうなります。
// NG例:敵が増えるたびにここを書き換える必要がある
void BadAttack(GameObject enemy)
{
if (enemy.name == "Slime") { /* スライムの処理 */ }
else if (enemy.name == "Dragon") { /* ドラゴンの処理 */ }
else if (enemy.name == "Goblin") { /* 新しい敵が増えたら? */ }
}このように if や switch で分岐させる設計は、「修正漏れによるバグ」や「GameManagerの肥大化」を招きます。オブジェクト指向を使えば、GameManager は何も変えずに、新しい敵クラスを追加するだけで済みます。
まとめ
- カプセル化:private を使い、予期せぬ数値変更を防ぐ。
- 抽象化・継承:共通の「役割」を親クラスで作る。
- ポリモーフィズム:型を親クラスでまとめ、命令を共通化する。
Unityの GetComponent<T>() や OnCollisionEnter(Collision collision) も、実はこのオブジェクト指向の仕組みの上で動いています。
まずは「共通の役割を持つ敵」を abstract class で作るところから始めてみませんか?
サイトアイコン-2-150x150.png)