
スライム、コウモリ、ゴーレム・・・。敵の種類が増えてくると、「HPを持っていて、ダメージを受けて、0になったら消える」という同じ処理を、敵の数だけコピーすることになりがちです。コピーしたコードは、あとで直すときに全部を直さないといけません。今回は、共通の部分を1つのクラスにまとめて、違うところだけをそれぞれのクラスに書ける継承(けいしょう)と、abstract(アブストラクト)をご紹介します!
この記事で学べること
- 継承とは何か(いつも書いている
: MonoBehaviourの意味) - 親クラスと子クラスの作り方と、
protectedの使いどころ virtualとoverrideで、子クラスごとに動きを変える方法abstractで「形だけ決めて、中身は子クラスにまかせる」方法- 親クラスの型で、種類の違うものをまとめて扱う方法
- 応用編:ScriptableObjectと組み合わせて、効果をアセットにする
前提知識
- 変数でデータを保存しよう
- メソッドを作ろう:処理に名前を付けてまとめる
- Input Systemで入力を受け取ろう:動作確認でクリックを使います
- 数が変わるデータを扱おう:List
- ScriptableObjectでデータを作ろう:応用編で使います
継承ってどんなもの?
実は、継承はいつも使っています。スクリプトを作ると最初に書かれている、この1行です。
public class Player : MonoBehaviour
: MonoBehaviourは「MonoBehaviourを受け継いだPlayerクラスを作る」という意味です。MonoBehaviourを受け継いでいるので、自分で書いていないtransformやGetComponent、Destroyが使えますし、StartやUpdateもUnityが呼んでくれます。
- 親クラス(基底クラス):受け継がれる側。例:MonoBehaviour
- 子クラス(派生クラス):受け継ぐ側。例:Player
- 子クラスは、親クラスの変数とメソッドをそのまま使える。そこに自分だけの変数やメソッドを足せる
「親の持ち物を全部もらって、自分の分を書き足す」のが継承です。今回は、この「親」を自分で作ってみます。
親クラスと子クラスを作ってみよう
親クラス:Enemy
まずは、どの敵にもある「HP」と「ダメージを受ける」を持った親クラスを作りましょう。
- Projectウインドで右クリック>Create>MonoBehaviour Script で「Enemy」を作り、中身を次のように書き換える
using UnityEngine;
// 敵の共通部分。HP とダメージの処理をまとめる
public class Enemy : MonoBehaviour
{
[SerializeField] protected int hp = 30;
public void TakeDamage(int damage)
{
hp -= damage;
Debug.Log($"{name}に{damage}ダメージ!残りHP:{hp}");
if (hp <= 0)
{
Destroy(gameObject);
}
}
}
- protected
privateと同じく、ほかのクラスからは見えない。ただし、子クラスからは使える- 子クラスでもHPを触りたいので、
privateではなくprotectedにする
| 書き方 | 自分のクラス | 子クラス | ほかのクラス |
|---|---|---|---|
private | 使える | 使えない | 使えない |
protected | 使える | 使える | 使えない |
public | 使える | 使える | 使える |
子クラス:Slime
次に、Enemyを受け継いだSlimeを作ります。: MonoBehaviourのところを: Enemyにするだけです。
using UnityEngine;
// スライム。Enemy を受け継いで、左右にぷるぷる動く
public class Slime : Enemy
{
private Vector3 startPosition;
private void Start()
{
startPosition = transform.position;
}
private void Update()
{
float offset = Mathf.Sin(Time.time * 3f) * 0.5f;
transform.position = startPosition + new Vector3(offset, 0f, 0f);
}
}
- : Enemy
- EnemyはMonoBehaviourを受け継いでいるので、SlimeもMonoBehaviourとしてGameObjectに付けられる
- HPとTakeDamageは書いていない
- 親のEnemyに書いてあるので、Slimeにも最初からある

InspectorにHpの欄が出ていれば成功!Slimeには書いていないのに、親のEnemyから受け継いでいるのが分かります。
子クラスごとに動きを変えよう:virtualとoverride
次は、硬い敵「ゴーレム」を作ります。ゴーレムは、受けるダメージが半分になるようにしたいと思います。
親のメソッドを子クラスで書き換えるには、親にvirtual、子にoverrideの2つをセットで使います。
- 親のメソッドにvirtual(書き換えてもいいよ、の印)
- 子のメソッドにoverride(書き換えます、の印)
まずは、EnemyのTakeDamageにvirtualを付けます。
public virtual void TakeDamage(int damage)
ゴーレムを作りましょう。
using UnityEngine;
// ゴーレム。硬いので、受けるダメージが半分になる
public class Golem : Enemy
{
public override void TakeDamage(int damage)
{
base.TakeDamage(damage / 2);
}
}
- override
- 親のTakeDamageの代わりに、こちらが呼ばれる
- base.TakeDamage(…)
baseは親クラスのこと。親のTakeDamage(HPを減らして、0なら消える)を、半分にしたダメージで呼んでいる- 「違うところだけ書いて、残りは親にまかせる」のが継承のいいところ
base.を付けずにTakeDamage(damage / 2)と書くと、自分(GolemのTakeDamage)を呼び続けて止まらなくなります。親の処理を使いたいときは、必ずbase.を付けましょう。
形だけ決めて、中身は子にまかせる:abstract
どの敵も「動く」のは同じですが、動き方はバラバラです。こういうときはabstractを使って、「動くメソッドがあること」だけを親で決めておきます。
Enemyを次のように書き換えます。
using UnityEngine;
// 敵の共通部分。動き方は子クラスで決める
public abstract class Enemy : MonoBehaviour
{
[SerializeField] protected int hp = 30;
private void Update()
{
Move();
}
// 動き方。子クラスで必ず書く
protected abstract void Move();
public virtual void TakeDamage(int damage)
{
hp -= damage;
Debug.Log($"{name}に{damage}ダメージ!残りHP:{hp}");
if (hp <= 0)
{
Destroy(gameObject);
}
}
}
- abstract class
- 「そのままでは使わない、受け継がれるためのクラス」という印。EnemyそのものはGameObjectに付けられなくなる
- protected abstract void Move();
- 中身(
{ })を書かずに、;で終わる - 子クラスは、このメソッドを
overrideで必ず書かないといけない。書き忘れるとエラーになるので、作り忘れを防げる
- 中身(
- Update
- 親が毎フレームMoveを呼ぶ。子クラスは「どう動くか」だけを書けばいい
子クラスもそれぞれ書き換えます。Slimeは、UpdateをMoveにします。
using UnityEngine;
// スライム。左右にぷるぷる動く
public class Slime : Enemy
{
private Vector3 startPosition;
private void Start()
{
startPosition = transform.position;
}
protected override void Move()
{
float offset = Mathf.Sin(Time.time * 3f) * 0.5f;
transform.position = startPosition + new Vector3(offset, 0f, 0f);
}
}
コウモリは、円を描いて飛ぶようにします。
using UnityEngine;
// コウモリ。円を描いて飛ぶ
public class Bat : Enemy
{
private Vector3 centerPosition;
private void Start()
{
centerPosition = transform.position;
}
protected override void Move()
{
float angle = Time.time * 2f;
Vector3 offset = new Vector3(Mathf.Cos(angle), Mathf.Sin(angle), 0f) * 0.8f;
transform.position = centerPosition + offset;
}
}
ゴーレムは動かないので、中身は空っぽでOK。それでもMoveは書く必要があります。
using UnityEngine;
// ゴーレム。動かない。硬いので、受けるダメージが半分になる
public class Golem : Enemy
{
protected override void Move()
{
// 動かない
}
public override void TakeDamage(int damage)
{
base.TakeDamage(damage / 2);
}
}
子クラスにUpdateを書くと、親のUpdateは呼ばれなくなります(Unityは子のUpdateだけを呼びます)。親がUpdateを使っているときは、子クラスではUpdateを書かずに、今回のMoveのように親から呼んでもらうメソッドに書きましょう。
スライム子クラスにUpdateを書いたら、親のUpdateが動かなくなって、しばらく悩みました・・・



エラーが出ないから気づきにくいよね。「毎フレームの処理は親が呼ぶ」って決めておくと安心だよ!
親の型でまとめて扱おう
継承のもう1つのいいところは、「Enemyの仲間なら、全部Enemyとして扱える」ことです。スライムもコウモリもゴーレムも、List<Enemy>にまとめて入れられます。
クリックしたら、全部の敵にまとめて10ダメージを与えるスクリプトを作ってみましょう。
using System.Collections.Generic;
using UnityEngine;
// クリックすると、登録した敵全員に10ダメージを与える
public class AttackAll : MonoBehaviour
{
[SerializeField] private List<Enemy> enemies = new List<Enemy>();
[SerializeField] private int damage = 10;
private InputSystem_Actions inputActions;
private void Awake()
{
inputActions = new InputSystem_Actions();
}
private void OnEnable()
{
inputActions.Enable();
}
private void OnDisable()
{
inputActions.Disable();
}
private void Update()
{
if (inputActions.Player.Attack.WasPressedThisFrame())
{
foreach (Enemy enemy in enemies)
{
// 倒されて消えた敵は null になるので飛ばす
if (enemy == null)
{
continue;
}
enemy.TakeDamage(damage);
}
}
}
}
- List
- 中身がSlimeでもBatでもGolemでも、Enemyの仲間なら入れられる
- enemy.TakeDamage(damage)
- 呼んでいるのは同じTakeDamageでも、ゴーレムだけは自分で書いた方(半分になる方)が呼ばれる
- AttackAllは、敵の種類を知らなくていい。敵の種類が増えても、AttackAllを直す必要がない
- Attack
- InputSystem_ActionsのAttackは、最初からマウスの左クリックやEnterキーなどに割り当てられている


動かして確認してみよう!
- Hierarchyで2D Object>Sprites>Circle を2つ、Square を1つ作り、名前をSlime・Bat・Golemにする(色を変えると分かりやすい)
- それぞれにSlime・Bat・Golemのスクリプトを付ける
- Golemは、InspectorでHpを60にする
- 空のGameObject「Attacker」を作ってAttackAllを付け、Enemiesに3体をドラッグ&ドロップ
- 再生して、Gameビューを左クリック


- スライムは左右に、コウモリは円を描いて動いている
- クリックすると、Consoleにダメージのログが3行出る
- ゴーレムだけダメージが5(半分)になっている
- 3回クリックすると、スライムとコウモリが消える
応用編!ScriptableObjectと組み合わせる
継承は、12-8 ScriptableObjectでデータを作ろうと組み合わせると、もっと便利になります。ここでは「アイテムを使ったときの効果」をアセットにして、Inspectorで組み合わせられるようにしてみます。
- 回復する
- 攻撃力が上がる
- 回復して、攻撃力も上がる(2つの効果を持ったアイテム)
プレイヤーのステータス
効果をかける相手として、プレイヤーのステータスを用意します。
using UnityEngine;
// プレイヤーのステータス
public class PlayerStatus : MonoBehaviour
{
public int hp = 50;
public int maxHp = 100;
public int attack = 10;
}
効果の親クラス
効果の親クラスを、ScriptableObjectを受け継いだabstractクラスで作ります。
using UnityEngine;
// アイテムの効果の共通の形。中身は子クラスで決める
public abstract class ItemEffect : ScriptableObject
{
public abstract void Apply(PlayerStatus player);
}
効果の子クラス
回復の効果と、攻撃力が上がる効果を作ります。ファイルは1つのクラスに1つずつ作り、ファイル名とクラス名をそろえます。
using UnityEngine;
// HP を回復する効果
[CreateAssetMenu(fileName = "HealEffect", menuName = "Game/Effect/Heal")]
public class HealEffect : ItemEffect
{
[SerializeField] private int amount = 30;
public override void Apply(PlayerStatus player)
{
player.hp = Mathf.Min(player.hp + amount, player.maxHp);
Debug.Log($"HPが{amount}回復!HP:{player.hp}");
}
}
using UnityEngine;
// 攻撃力が上がる効果
[CreateAssetMenu(fileName = "PowerUpEffect", menuName = "Game/Effect/Power Up")]
public class PowerUpEffect : ItemEffect
{
[SerializeField] private int amount = 5;
public override void Apply(PlayerStatus player)
{
player.attack += amount;
Debug.Log($"攻撃力が{amount}上がった!攻撃力:{player.attack}");
}
}
- [CreateAssetMenu]は子クラスに付ける
- abstractのItemEffectはアセットにできないので、メニューに出すのは子クラスだけ
- Mathf.Min
- 2つのうち小さい方を返す。回復しても最大HPを超えないようにしている
アイテムに効果を持たせる
12-8で作ったItemDataに、効果のListを1行足します。
using System.Collections.Generic;
using UnityEngine;
// アイテム1つ分のデータ。Project ウインドの Create>Game>Item Data でアセットを作れる
[CreateAssetMenu(fileName = "NewItem", menuName = "Game/Item Data")]
public class ItemData : ScriptableObject
{
public string id; // セーブデータに保存する名前(英数字で、ほかのアイテムと重ならないように)
public string itemName; // 画面に出す名前
public Sprite icon; // アイコン
public int price; // 値段
[TextArea] public string description; // 説明文
public List<ItemEffect> effects = new List<ItemEffect>(); // 使ったときの効果
}
- List
- HealEffectのアセットもPowerUpEffectのアセットも、ItemEffectの仲間なので入れられる
アセットを作りましょう。
- Projectウインドで右クリック>Create>Game>Effect>Heal で「Heal30」を作る(Amount:30)
- Create>Game>Effect>Power Up で「PowerUp5」を作る(Amount:5)
- Item Dataの「Elixir」を作り、Item Name:エリクサー、Effectsの「+」で欄を2つ増やして、Heal30とPowerUp5をドラッグ&ドロップ


アイテムを使う
最後に、アイテムを使うスクリプトです。
using UnityEngine;
// 再生したら、設定したアイテムを1回使う
public class ItemUser : MonoBehaviour
{
[SerializeField] private ItemData item;
[SerializeField] private PlayerStatus player;
private void Start()
{
Debug.Log($"{item.itemName}を使った!");
foreach (ItemEffect effect in item.effects)
{
effect.Apply(player);
}
}
}
- 空のGameObject「Player」にPlayerStatusとItemUserを付ける
- ItemUserのItemにElixir、PlayerにPlayer自身をドラッグ&ドロップ
- 再生すると、Consoleに「エリクサーを使った!」「HPが30回復!HP:80」「攻撃力が5上がった!攻撃力:15」が出れば成功!


- 新しい効果(毒を治す、お金が増えるなど)は、ItemEffectの子クラスを1つ足すだけ。ItemUserもItemDataも直さなくていい
- 効果の組み合わせは、Inspectorでアセットを並べるだけで作れる
- カードゲームの「カードの効果」や、RPGの「スキルの効果」も同じ形で作れる
よくあるエラーと対処法
エラー:’Slime’ does not implement inherited abstract member ‘Enemy.Move()’
- 原因:親のabstractメソッドを、子クラスで書いていない
- 対処法:子クラスに
protected override void Move()を書く(何もしないなら中身は空でOK)
エラー:cannot override inherited member ‘Enemy.TakeDamage(int)’ because it is not marked virtual, abstract, or override
- 原因:親のメソッドに
virtualを付けずに、子クラスでoverrideした - 対処法:親のメソッドに
virtualを付ける
エラー:cannot change access modifiers when overriding ‘protected’ inherited member
- 原因:親が
protectedなのに、子クラスでpublic overrideやprivate overrideと書いた - 対処法:子クラスも親と同じ
protected overrideにする
Can’t add script behaviour ‘Enemy’. The script class can’t be abstract!
- 原因:abstractのクラスは、そのままGameObjectに付けられない
- 対処法:Slimeなどの子クラスを付ける。これは正しい動きです
親のUpdateが動かない
- 原因:子クラスにもUpdateを書いた(Unityは子のUpdateだけを呼ぶ)
- 対処法:子クラスではUpdateを書かず、親から呼ばれるメソッド(今回のMove)に書く
まとめ
: 親クラスで、親の変数とメソッドを受け継げる。いつもの: MonoBehaviourも継承- 子クラスからも使いたい変数は
protectedにする - 子で書き換えたいメソッドは、親に
virtual、子にoverride。親の処理はbase.で呼べる abstractは「形だけ決めて、中身は子にまかせる」。書き忘れはエラーで教えてくれる- 親の型(
List<Enemy>など)で、種類の違うものをまとめて扱える - ScriptableObjectと組み合わせると、効果をアセットにしてInspectorで組み合わせられる
次のステップ
- カードの効果(攻撃・防御・回復)を、今回の形で作る回:デッキ構築カードゲームの作り方#7(準備中)
- ScriptableObjectでデータを作ろう
- Unityスタートガイド 目次
コメント