Unity×C#でわかる!継承とポリモーフィズム : 中級者向けゲーム設計入門
Unity×C#でわかる!継承とポリモーフィズム : 中級者向けゲーム設計入門
C#プログラミングにおいて、継承とポリモーフィズムは押さえておきたい重要な概念の一つです。特にUnityのようにオブジェクト指向の仕組みを多用するゲームエンジンでは、継承を使った設計と多態性(ポリモーフィズム)を活かしたコードの書き方が、プロジェクトの拡張性や可読性を大きく左右します。本記事では、継承とポリモーフィズムの基礎からUnityでの実装例までを丁寧に解説していきます。
なぜ継承とポリモーフィズムが重要なのか
複数クラスの共通処理をまとめられる
ゲーム開発では、キャラクターや敵、アイテムなど多種多様なクラスが必要になります。もし全てのクラスで共通する処理を「コピペ」で書くと、修正や拡張が発生するたびにコードをあちこち直さなければなりません。継承を使えば共通部分は親クラス(基底クラス)で持ち、個別の動作を子クラスで実装できるため、保守性が高まります。
柔軟な挙動を後から追加できる
ポリモーフィズム(多態性)は「同じ型でも、実際には違う挙動を実装できる」仕組みです。C#ではvirtual
やabstract
、override
キーワードを使うことで、基底クラスのメソッドを子クラス側で自由に上書きし、新たな動きを与えられます。
たとえば「敵キャラ共通クラスEnemy
を継承して、ボスキャラだけ特殊攻撃を付加する」といった具合に、最小限の修正で新たな機能を追加しやすくなります。
UnityではMonoBehaviourを基底クラスとする
Unityのスクリプトは一般的にMonoBehaviour
を継承する形で書かれています。つまり、Unity環境自体が継承をベースに設計されており、いわば「継承の恩恵を活用する」構造になっています。自作の基底クラスでさらに拡張し、ポリモーフィズムを組み合わせれば、大規模開発でも管理しやすいアーキテクチャを構築できるでしょう。
C#の継承:基本構文と注意点
クラスの継承構文
// 親クラス(基底クラス)
public class Character
{
public string characterName;
public int hp;
public virtual void Attack()
{
Debug.Log(characterName + "の攻撃!");
}
}
// 子クラス(派生クラス)
public class Warrior : Character
{
// Warrior独自のプロパティ
public int stamina;
// Attackメソッドを上書き
public override void Attack()
{
// base.Attack()で親クラスの処理を呼び出しつつ、追加のロジックを実行
base.Attack();
Debug.Log("さらに、剣で追撃!");
}
}
-
public class 子クラス : 親クラス
の形で宣言 - 基底クラス側を
virtual
、派生クラス側をoverride
にすることで、多態的に振る舞えるメソッドを作成 -
base.メソッド名
を呼び出すと、親クラスの処理を一部引き継げます
abstractクラス・abstractメソッド
public abstract class Enemy
{
public string enemyName;
// 派生クラスで必ず実装させたいメソッド
public abstract void Attack();
// 共通処理
public void PrintName()
{
Debug.Log("敵の名前は" + enemyName);
}
}
-
abstract
クラス:インスタンスを直接作れないクラス -
abstract
メソッド:派生クラスで必ず実装を強制するメソッド
抽象クラスを使えば「派生クラスが必ずこのメソッドを持つ」という仕様を明示できるため、プロジェクト規模が大きくなるほどコードの一貫性を保ちやすくなります。
シールクラスとシールメソッド
public sealed class FinalClass
{
// これ以上継承されないクラス
}
// シールメソッド
public class BaseClass
{
public virtual void SpecialAttack() { }
}
public class ChildClass : BaseClass
{
public sealed override void SpecialAttack()
{
// このメソッドはこれ以上のoverrideを許可しない
}
}
-
sealed
クラス:継承を禁止してクラス階層の深掘りを防ぎたい場合に使用 -
sealed override
:特定のメソッドのみ更なるオーバーライドをブロックできる
通常、ゲームでは柔軟性を重視してsealed
を多用するケースは少ないですが、APIレベルで公開したくない機能を制限する際に役立ちます。
ポリモーフィズム(多態性):同じ呼び出しでも結果が異なる仕組み
overrideとオーバーロードの違い
- override:親クラスと同じシグネチャのメソッドを上書きし、実行結果を変更
- オーバーロード:同じメソッド名でも、引数の数や型が違うバリエーションを増やすだけで、ポリモーフィズムとは別概念
public class Mage : Character
{
// overrideによる上書き
public override void Attack()
{
Debug.Log("魔法で遠距離攻撃!");
}
// オーバーロードによる引数違い
public void Attack(int spellPower)
{
Debug.Log("魔力" + spellPower + "の魔法攻撃!");
}
}
インターフェースとの違い
インターフェース(interface
)はクラスではなく、実装を含まない「仕様の塊」です。
- 継承:親クラスのメソッドやプロパティを引き継いで利用・上書きできる
- インターフェース:メソッドのシグネチャのみ定義し、それを複数クラスで同じように実装
ゲーム開発では「特定の能力を持つ」共通インターフェースを用意して、複数クラスに実装させるケースもありますが、継承や抽象クラスとは別の使い方をすることが多いです。
Unityで多態性を活かす実装例
ここからは実際にUnity上で継承とポリモーフィズムを組み合わせた例を見ていきましょう。典型的なのは「敵クラスの基底Enemy」と「ボスや小型敵など、具体的な子クラス」による実装です。
1. 基底Enemyクラス
using UnityEngine;
public abstract class Enemy : MonoBehaviour
{
public string enemyName;
public int hp;
// abstractメソッド
public abstract void Attack();
// 共通メソッド
public void TakeDamage(int damage)
{
hp -= damage;
Debug.Log(enemyName + "は" + damage + "のダメージを受けた!HP: " + hp);
if(hp <= 0)
{
Death();
}
}
void Death()
{
Debug.Log(enemyName + "は倒れた…");
// 何らかの消滅処理
Destroy(gameObject);
}
}
-
MonoBehaviour
を継承する抽象クラス -
Attack()
を必ず派生クラスで実装させる -
TakeDamage()
は全敵共通の処理を提供
2. 小型敵(Slime)クラス
public class Slime : Enemy
{
void Start()
{
enemyName = "スライム";
hp = 30;
}
public override void Attack()
{
Debug.Log(enemyName + "の体当たり攻撃!");
// 体当たりロジック
}
}
-
Enemy
を継承 -
Attack()
をoverride
で具体的に実装(スライム固有の攻撃)
3. ボス敵(Dragon)クラス
public class Dragon : Enemy
{
void Start()
{
enemyName = "ドラゴン";
hp = 200;
}
public override void Attack()
{
Debug.Log(enemyName + "は炎を吐いた!");
// 炎ブレスロジック
}
}
- 同じ
Attack()
メソッドを、ドラゴン用に全く別の処理で実装 - 「Enemy型」に統一的にアクセスできても、実際には多態的に処理が変わる
4. ゲーム内での呼び出し
public class EnemyManager : MonoBehaviour
{
// シーンに配置された敵オブジェクトをまとめて管理
private Enemy[] enemies;
void Start()
{
enemies = FindObjectsOfType<Enemy>();
// Enemyを継承した全クラス(SlimeやDragon)が格納される
}
void Update()
{
// テスト:スペースキーで全敵が攻撃
if(Input.GetKeyDown(KeyCode.Space))
{
foreach(var e in enemies)
{
e.Attack();
// ここではEnemy型だが、実際にはSlimeやDragonのAttack()が呼ばれる
}
}
}
}
-
FindObjectsOfType<Enemy>()
で、シーン上のEnemy
を継承したオブジェクト全てを取得 - 1行の
e.Attack()
呼び出しで、各クラス独自の攻撃を多態的に実行できる
このようにポリモーフィズムを活かすと、敵の種類が増えてもAttack()
を追加実装するだけで拡張でき、EnemyManager
側のコード変更を最小限に抑えられます。
継承とポリモーフィズムがもたらすメリット・デメリット
メリット
-
共通処理の一元化
バグ修正やロジック変更があっても、基底クラスを直すだけで全子クラスに反映されます。 -
柔軟な拡張性
新しい種類の敵やキャラクターを追加しても、最小限の変更で済むため開発が楽になります。 -
可読性の向上
コードを「抽象度の高い基底部分」と「具体的な派生部分」に分けて整理できるため、大規模プロジェクトでも見通しを保ちやすいです。
デメリット
-
複雑化のリスク
クラス階層を深くしすぎると、継承関係の把握が難しくなります。 -
単一継承の制約
C#では複数のクラスから同時に継承できないため、設計によってはインターフェースとの組み合わせが必要になる場合もあります。 -
使い分けの学習コスト
抽象クラスやインターフェースなど複数の仕組みがあるので、チームで統一的なコーディングルールを決めないと混乱が生じやすくなります。
応用:コンポーネント指向設計との融合
Unityは、継承と同じくらい「コンポーネント指向」が重要な世界です。GameObjectに複数のコンポーネント(スクリプトやColliderなど)を付け足して機能を拡張していきます。
- 「動き自体は親クラスで共通化し、各種ステータスはScriptableObjectなど別コンポーネントに分離」
- 「パーティクル演出やサウンド制御は、独立したコンポーネントに任せる」
このように継承とコンポーネント指向をバランスよく使い分けることで、重複コードを減らしながら高い拡張性を実現できます。
実装イメージ
-
Slime
やDragon
はEnemyを継承しつつ、HP表示やパーティクルなど各種コンポーネントを必要に応じて追加
まとめ:継承とポリモーフィズムを駆使して拡張性の高いUnity開発を
C#の継承とポリモーフィズムは、ゲーム開発において大きな威力を発揮する仕組みです。
- 基底クラスで共通機能を提供し、子クラスで具体的挙動を実装
- 多態性(ポリモーフィズム)により、一括管理しつつ多様な動きを実現
- 抽象クラスやインターフェース、コンポーネント指向などの合わせ技で高い柔軟性を確保
Unity特有のMonoBehaviour
継承も、C#のオブジェクト指向を理解しておけばより活かしやすくなります。設計段階で「どの部分を共通化し、どこを派生クラスに任せるか」を明確にすることで、後々の拡張も楽になり、メンテナンスコストを大幅に抑えられるでしょう。
この記事を読んでもっと実践したいと感じたあなたへ
Unity開発を効率よく進めるためには、実践的なスキルと仲間との交流が欠かせません。
そんな方におすすめのステップが、下記の3つです。
1. 有料教材「どこでもUnity教室」でゲーム制作を短期マスター
- 5日でシンプルなFPS完成:初心者向けに要点を押さえたカリキュラム
- C#や最新のInputSystem、FPS実装まで網羅:つまずきやすいポイントを先回りで解説
- 購入特典:Discord招待+サンプルプロジェクトDLで、疑問や実装例を即確認
たった5日でFPSゲームが作れる自分に変わる教材はこちら
2. 無料コミュニティで、疑問をすぐに解消&モチベーションUP
- 初心者~中級者までOK:学習進度に合わせて質問や情報共有
- 質問サポートが充実:わからないことを仲間や講師に即相談
- 学習仲間と切磋琢磨:一緒に学ぶから続けやすい
Discordサーバー参加はこちら
3. 実績豊富な“ゲーム開発所RYURYU”があなたをトータルサポート
- コナラ総販売200件超:さまざまなUnity開発の依頼を対応
- VR/AR/AIなど最新技術にも精通:幅広いノウハウを活かして開発支援
- ゲームクリエイター甲子園や東京ゲームショウなど出展実績多数
ご相談・お問い合わせはこちら
継承やポリモーフィズムの理解が深まれば、Unityでの開発の幅はぐんと広がります。ぜひこれらのリソースを活用して、さらに実践的なスキルアップを目指してみてください。
Discussion