🎯

(初学者向け)": MonoBehaviour"って何なのか

2023/12/06に公開

はじめに

「Unity始めたて、プログラミングも初心者!」みたいな状態では、もはやおまじないでしかない、: MonoBehaviourの文字列。私も最初の頃、全く意味も分からずなんとなくこの文字列を書いていました。

この記事は、: MonoBehaviourが何の意味を成しているのかを出来るだけ正確に、分かりやすく解説する試みです。

オブジェクト指向について

: MonoBehaviourを知るには、オブジェクト指向プログラミング(以下OOP)の考え方について最低限知っておく必要があります。

C#はOOPをベースの考え方として作られているプログラミング言語です。

OOPの三大要素として、以下が挙げられることがしばしばあります。

  • カプセル化
  • 継承
  • 多態性(ポリモーフィズム)

このうち、今回の話で主に大事なのは「継承」です。ずばり、: MonoBehaviourは継承の構文だからです

継承

では、継承についてザックリ説明していきます。

誤解を恐れずにひとことで言うと、「クラスの機能を全部引き継いだクラスを作れる機能」です。

例として、以下のような敵ゾンビのクラスを考えてみましょう。

public class EnemyZombie : MonoBehaviour
{
    public int hp;

    public void TakeDamage(int value)
    {
        hp -= value;
        if (hp <= 0)
        {
            hp = 0;
            Destroy(gameObject); // 死亡処理
        }
    }
}

EnemyZombieは、hpTakeDamage(int)を実装したクラスになります。例えば銃弾クラスを作ってから、こんな風に呼ぶことが出来るでしょう。

public class Bullet : MonoBehaviour
{
    public int damage;

    private void OnCollisionEnter(Collision other)
    {
        if (other.gameObject.GetComponent<EnemyZombie>() != null)
        {
            other.gameObject.GetComponent<EnemyZombie>().TakeDamage(damage);
        }
        
        Destroy(gameObject);
    }
}

これによって、敵にダメージを与えることができそうです。

ここで、他にもスクリプトで制御したいことがある敵が沢山いるとします。

  • MPも持っているEnemyMage
  • Bullet(弾)が効かないEnemyInvincible
  • 特殊な動き方をするEnemyTeleporter
  • etc...

まあ、いくらでも考えられますね。でも、これら全部をクラスとして書いていくと、同じことを何回も書いてしまいそうです。どの敵もhpは持っていますし、hpが0になったら死亡するということが確定している場合、全ての敵にこの処理を記述するのはちょっと効率的でないように感じるでしょう。

実際のところ、効率的でない上、変更に弱くなります

例えば、たくさん敵クラスを作っておいた後で、「hpが0になったときの死亡エフェクト」を追加したくなった場合…

public class EnemyZombie : MonoBehaviour
{
    public int hp;
+   public ParticleSystem particle;

    public void TakeDamage(int value)
    {
        hp -= value;
        if (hp <= 0)
        {
            hp = 0;
            Destroy(gameObject); // 死亡処理
+           Instantiate(particle, transform.position, Quaternion.identity);
        }
    }
}

こうなると、全ての敵クラスに書き漏れの無いように同じ処理を何度も書かないといけません。ちょっとした変更があるたびにこの作業が続いてしまいます…。

しかも、Bulletクラス側も、このようなコードを書かないと対応できなくなってしまいます。

public class Bullet : MonoBehaviour
{
    public int damage;

    private void OnCollisionEnter(Collision other)
    {
        if (other.gameObject.GetComponent<EnemyZombie>() != null)
        {
            other.gameObject.GetComponent<EnemyZombie>().TakeDamage(damage);
        }
+	else if (other.gameObject.GetComponent<EnemyMage>() != null)
+	{
+	    other.gameObject.GetComponent<EnemyMage>().TakeDamage(damage);
+	}
+	else if (other.gameObject.GetComponent<EnemyTeleporter>() != null)
+	{
+	    other.gameObject.GetComponent<EnemyTeleporter>().TakeDamage(damage);
+	}
+	
+	// 敵クラスが増えるたびにこれが続く...
        
        Destroy(gameObject);
    }
}

こういったときに選択肢として挙げられるのが、「継承」です。

継承は、: MonoBehaviourのように、:を付けた右に継承したいクラス名を入力して実装します。

まずは先ほどのクラスをEnemyとします。

-public class EnemyZombie : MonoBehaviour
+public class Enemy : MonoBehaviour
{
    public int hp;
    public ParticleSystem particle;

    public void TakeDamage(int value)
    {
        hp -= value;
        if (hp <= 0)
        {
            hp = 0;
            Destroy(gameObject); // 死亡処理
            Instantiate(particle, transform.position, Quaternion.identity);
        }
    }
}

次に、このクラスを、例えばZombieクラスから継承させます。: Enemyと記述します。

public class Zombie : Enemy
{
    // この状態で既にhpなどがZombieクラスにも備わっている
}

public class Mage : Enemy
{
    public int mp; // 継承先で新しい機能を実装できる!
}

必要な機能があったら追加するだけで、新しい敵を表現することが出来ました。
しかも、Bullet側も、Enemyクラスを参照すればいいだけなので、コードがすっきりして変更にも強くなります!

public class Bullet : MonoBehaviour
{
    public int damage;

    private void OnCollisionEnter(Collision other)
    {
        if (other.gameObject.GetComponent<Enemy>() != null)
        {
	    // 継承元のEnemyクラスを取得するだけ!
            other.gameObject.GetComponent<Enemy>().TakeDamage(damage); 
        }
        
        Destroy(gameObject);
    }
}

このようにして、最低限必要な機能をまとめておいて、それを継承することで使いやすく&変更に強くしようぜ!

というのが、継承の考え方の基本になります。

そして実は、: MonoBehaviourとは、「MonoBehaviourを継承するよ!」という意味だったのですね

継承のデメリット

さて、ここまで便利そうな継承ですが、デメリットも多く存在するので、濫用はオススメしません

使いたい方は、まずabstractキーワードや「共通化」と「抽象化」の違い等について勉強すると、使用するべきタイミングがなんとなくつかめてくると思います。

interface等も勉強すると、なお理解が深まると思います!

(継承は正直自分も使いこなせてません!)

MonoBehaviourって結局何よ

先ほど、: MonoBehaviourは「MonoBehaviourを継承するよ!」と言う意味であると説明しましたが、では、このMonoBehaviourクラスはどんなクラスなんでしょうか。

MonoBehaviourは、Unity開発側が、Unityのゲームエンジン上でコンポーネントを扱いやすくするために用意した巨大なクラスです

普段、: MonoBehaviourを書いたクラスでは、なんのためらいもなくgameObjectとかtransformとかGetComponent<T>()とか書くことが出来ますが、これらを、MonoBehaviourを継承することなく記述するとエラーになります。

それはすなわち、gameObjectとかtransformとかGetComponent<T>()MonoBehaviourクラスにて定義された変数や関数であると言うことを意味します。

こういった変数・関数は、全てUnityがMonoBehaviourクラスにて用意してくれているので、これを継承して使おうね、という流れになっているわけです。

MonoBehaviourで用意されているメソッド等は、以下の公式ドキュメントにも掲載されています。これらが全部実装されているというわけです。MonoBehaviourを継承すれば使うことが出来ます。

https://docs.unity3d.com/ja/2021.3/ScriptReference/MonoBehaviour.html

余談 : PureC#について

Unityで設計を勉強することになった際、"PureC#"という単語を聞くことがあると思います。

PureC#とは、MonoBehaviourを継承しないクラス(普通のC#クラス)の俗称です。

先述のMonoBehaviourは、膨大なpublicフィールドやプロパティ、メソッドを備えています。

MonoBehaviourを継承するとは、これら全部を使える状態のクラスを作る、ということに他なりません。よって、何も考えないと、必要以上に機能を持ったクラスが爆誕してしまいます。

そこでちょっと要らない機能を使ってしまうと、コードが分かりにくくなったり、保守性が下がったりという可能性が懸念されます。

また、MonoBehaviourではコンストラクタが利用できない等、様々な弊害が存在しています。

主にそういった理由から、PureC#で実装できる機能はPureC#に分離した方がいい(こともある)という考えがあります。

Discussion