🎮【C#入門】「モブ」は継承で作れ!~敵も村人も動くオブジェクト~

2025年8月2日

はじめに

RPGやアクションゲームを作っていると、敵キャラやNPC(村人など)を「動く存在(ムービングオブジェクト)」として扱うことがよくあります。

こうした「共通して動くキャラ」=モブ(mob)を、どのように効率よくコードで表現するか?

答えは、「継承」と「多態性(ポリモーフィズム)」を使うことです。


🧭「モブ(mob)」って何?

もともと「mob」は mobile object(移動するオブジェクト) の略語で、MMORPGなどで敵キャラやNPCの総称として使われてきました。

日本でも「雑魚モブ」「敵モブ」などと使われるようになり、プレイヤー以外の動くキャラ全般を指す言葉として定着しています。


🏗️ 設計の基本方針

今回は以下の3種類を設計します:

  • MovingObject(基底クラス):共通の移動機能を持つ
  • Enemy(派生クラス):敵キャラとしての振る舞い
  • Villager(派生クラス):村人NPCとしての振る舞い

💻 サンプルコード

using System;

class MovingObject
{
    public string Name { get; set; }

    public MovingObject(string name)
    {
        Name = name;
    }

    public virtual void Move()
    {
        Console.WriteLine($"{Name} はゆっくり動いています。");
    }
}

class Enemy : MovingObject
{
    public Enemy(string name) : base(name) {}

    public override void Move()
    {
        Console.WriteLine($"{Name} はプレイヤーに向かって走ってきます!");
    }

    public void Attack()
    {
        Console.WriteLine($"{Name} は攻撃してきた!");
    }
}

class Villager : MovingObject
{
    public Villager(string name) : base(name) {}

    public override void Move()
    {
        Console.WriteLine($"{Name} は村の中をのんびり歩いています。");
    }

    public void Talk()
    {
        Console.WriteLine($"{Name}:こんにちは!");
    }
}

class Program
{
    static void Main()
    {
        MovingObject[] mobs =
        {
            new Enemy("ゴブリン"),
            new Villager("村人A"),
            new Enemy("オオカミ"),
            new Villager("村人B")
        };

        foreach (var mob in mobs)
        {
            mob.Move();  // 多態性により適切なMove()が呼ばれる
        }

        // 特定の機能はキャストして使う
        ((Enemy)mobs[0]).Attack();
        ((Villager)mobs[1]).Talk();
    }
}

🔍 ポイント解説

キーワード説明
virtual / overrideMove() メソッドを各クラスで上書きできるように設定
base(name)基底クラスのコンストラクタを呼び出し、名前をセット
多態性(ポリモーフィズム)MovingObject型の配列でも、Enemy や Villager の Move() が正しく呼ばれる

🤔 なぜ継承を使うのか?

  • 共通部分(例:名前、基本の移動)を一箇所で定義できる
  • 新しいモブを追加する際に使い回しやすい
  • 複数の種類を1つのコレクション(Listなど)で一括管理できる

✍️ 練習問題

次のようなクラスを追加してみましょう!

【練習A】BossEnemy クラス

  • Enemy を継承し、特別な Move() と Attack() を実装せよ
  • 例:「ゆっくりと重々しい足音で近づいてくる…」「超強力な攻撃を繰り出した!」

【練習B】モブに座標を追加せよ

  • X, Y プロパティを持たせ、座標に応じた行動をとるようにしてみよう
  • 例:Move() の中で X++ などの処理を入れる

練習問題【練習A】【練習B】に対する サンプル解答(C#コンソールアプリ版)を示します。

先ほどの「モブ = MovingObject」設計をベースにして拡張しています。


✍️ 練習A:BossEnemyクラスの実装

class BossEnemy : Enemy
{
    public BossEnemy(string name) : base(name) {}

    public override void Move()
    {
        Console.WriteLine($"{Name} はゆっくりと重々しい足音で近づいてくる…");
    }

    public new void Attack()
    {
        Console.WriteLine($"{Name} は超強力な攻撃を繰り出した!");
    }
}

🔍 解説

  • Enemy を継承し、Move() を override で上書き
  • Attack() は Enemy のものと違う動作をするため、new キーワードで隠蔽して定義

✍️ 練習B:座標の追加

class MovingObject
{
    public string Name { get; set; }
    public int X { get; set; }   // 横方向座標
    public int Y { get; set; }   // 縦方向座標

    public MovingObject(string name)
    {
        Name = name;
        X = 0;
        Y = 0;
    }

    public virtual void Move()
    {
        X++;
        Console.WriteLine($"{Name} は ({X}, {Y}) に移動しました。");
    }
}

🔍 解説

  • X, Y を追加して移動先を記録
  • 各派生クラスで Move() を上書きしていても、必要に応じて X++ を共通処理として活かせます

🧪 動作テスト用メイン(拡張版)

class Program
{
    static void Main()
    {
        MovingObject[] mobs =
        {
            new Enemy("ゴブリン"),
            new Villager("村人A"),
            new BossEnemy("ドラゴンキング")
        };

        foreach (var mob in mobs)
        {
            mob.Move(); // 多態性により適切なMove()が呼ばれる
        }

        // BossEnemy の攻撃(明示的なキャスト)
        ((BossEnemy)mobs[2]).Attack();
    }
}

✅ 実行結果(例)

ゴブリン はプレイヤーに向かって走ってきます!
村人A は村の中をのんびり歩いています。
ドラゴンキング はゆっくりと重々しい足音で近づいてくる…
ドラゴンキング は超強力な攻撃を繰り出した!

🔁 さらに拡張するなら…

  • Move() 内で X, Y をランダムに変化させて「歩き回る」表現を追加
  • 画面外への移動制限(境界チェック)を入れる
  • List<MovingObject> を使って複雑なゲームマップ上の動作を模擬


🧩 おわりに

「モブ」はゲームの中でプレイヤーと並ぶ重要な要素です。

彼らを効率よく定義・管理するためにも、継承と多態性を活かす設計をぜひ覚えておきましょう。

次はこの仕組みをUnity上のスクリプトとして活用してみると、さらに理解が深まりますよ。


🧠 関連リンク


訪問数 4 回, 今日の訪問数 1回