👥

【Unity】クラスを継承した特殊ユニットの作り方【タワーディフェンス】

2020/12/04に公開

はじめに

タワーディフェンスを作ろうの記事3回目になります。
本当は前回で終わりにしようと思ったのですが友人から引き続き質問が来ている事もありせっかくなのでゲームとして形になるまで続けることにしました。
続きものですがテーマを絞って記事単独でも役立つようにしたいと思っています。
今回のテーマは「クラスの継承」です。

前回、前々回の記事はこちら。
https://zenn.dev/supple/articles/2012a0ebfe13181286af
https://zenn.dev/supple/articles/64c9f9aaf2e7ca6f7151

拠点機能を持つユニット

よくあるタイプのゲームとしてお互いの拠点を破壊しあうルールのゲームがありますね。
今回はこの拠点機能を持つユニットを作ってみます。

拠点に必要な機能

タワーディフェンスの拠点に必要な機能はどんなものでしょうか。
拠点も敵に攻撃されるユニットの一つなので、ユニットとしての機能は一通り必要です。
それに加えて敵拠点を攻撃するためのユニットを生産する機能が要ります。
無制限に出せてしまうとゲームになりませんから、ゲーム性を持たせるためにコストの概念が必要です。

  • ユニットとしての基本機能
  • ユニット生産資源の蓄積
  • 資源を消費してのユニットの生産

他にも勝利条件に絡むものなどありますがそれは今度にして、今回は上記3つの機能を継承を使って実装していきます。

継承とは

まず継承とは一体何でしょうか。
入門書や授業なんかではよく「果物とリンゴ、オレンジ」とか「動物と犬、猫」等のたとえ話で解説されますが、抽象的過ぎて正直ピンとこない人も多いのではないかと思います。
細かい事は置いといてまずはこう考えてみましょう。

「継承とはクラスの機能を引き継いだ新しいクラスを作ることができる拡張機能です」

厳格な人が聞いたら怒りそうな文章ですが、最初はこれくらいシンプルな方が頭に入りやすいと思います。

Unitクラスを継承して拠点機能を持つFortUnitクラスを作る

話を戻します。
Unitの機能を引き継いだ新しいクラスFortUnitクラスを作ります。

FortUnit.cs
public class FortUnit : Unit
{
}

クラス名の右側の普段MonoBehaviourと書いているところがUnitになっています。
これがFortUnitクラスはUnitクラスを継承しているよ、という宣言になります。
MonoBehaviourとUnitとFortUnitの関係は下の図のような感じになります。

UnitクラスはMonoBehaviourクラスを継承しており、FortUnitクラスはUnitクラスを継承しています。
FortUnitクラスはMonoBehaviourクラスとUnitクラスの全機能を使うことができます。

FortUnitクラスにユニット生産機能を持たせる

ユニット生産に必要なパラメーター

FortUnitクラスに資源の生産力と現在持っている資源の量のパラメーターを追加します。

FortUnit.cs
public float productivity;  // 生産力
public float resourcePoint; // 資源ポイント

Unitクラスには生産に必要なコストのパラメーターを追加します。

Unit.cs
public float cost;  // 生産コスト

資源ポイントの貯蓄

FortUnitクラスでUnitクラスのUpdateUnitをオーバーライドして時間経過で資源ポイントを増えるようにします。

メソッドのオーバーライド

継承したクラスには継承元のクラスのメソッドを上書きする機能があり、これをオーバーライドと呼びます。
オーバーライドすることで基底クラスの機能を変更したり、拡張できたりします。
今回は基底クラスの機能に生産ポイントの蓄積機能を拡張します。

FortUnit.cs
    // ユニットの更新(オーバーライド)
    public override void UpdateUnit(float deltaTime)
    {
        base.UpdateUnit(deltaTime);
        // 資源ポイントの蓄積(一秒間にproductivityだけ貯まる)
        resourcePoint += productivity * deltaTime;
    }
Unit.cs
    // ユニットの更新
    public virtual void UpdateUnit(float deltaTime)
    {
        // 中身は省略。前回の記事を参照して下さい。
    }

Unitクラスのメソッドにvirtualキーワードを追加することでオーバーライドすることが可能になり、overrideキーワードを使用することで基底クラスの機能を上書きすることができます。
基底クラスの機能を拡張したい場合、baseキーワードを使うことで基底クラスにアクセスできるので、基底クラスのメソッドを呼んでから拡張部分を記述します。
図にすると以下のような感じです。

ユニットの生産

前々回の記事でBattleManagerに持たせていたユニット生成機能をFortUnitに移します。
ただし、コストが足りない場合は生成できないようにします。

FortUnit.cs
    // ユニットを生産する
    public void SortieUnit(Unit unitPrefab)
    {
        if(resourcePoint < unitPrefab.cost)
        {
            Debug.Log("出撃コストが不足しています");
            return;
        }
        // コストを消費する
        resourcePoint -= unitPrefab.cost;

        // ユニットを配置する
        GameObject go = Instantiate(unitPrefab.gameObject, transform);
        go.transform.position = transform.position;
        // 一番近い敵を目標に設定する
        Unit unit = go.GetComponent<Unit>();
        unit.SetTargetUnit(BattleManager.Get().FindNearestEnemy(this));
    }
}

ユニット生産機能を使う

適当なボタンに以下のスクリプトをアタッチして、ButtonコンポーネントのOnClickイベントにOnButtonClickメソッドを設定すればユニット生産ボタンになります。

SortieButton.cs
public class SortieButton : MonoBehaviour
{
    public FortUnit fortUnit;   // 拠点ユニット
    public Unit unitPrefab;     // 生産するユニットのプレハブ
    
    // ボタンが押されたときの挙動
    public void OnButtonClick()
    {
        // ユニットを出撃する
        fortUnit.SortieUnit(unitPrefab);
    }
}

継承についての補足

継承とはの項ではあえて触れませんでしたが、継承の大きなメリットとして抽象化が挙げられます。
FortUnitクラスはUnitクラスを継承しているのでUnitとしての性質も持ち合わせています。
なので、FortUnitはUnitとして扱うことができるのです。
このプログラムではBattleManagerクラスでユニットの一元管理をしています。

BattleManager.cs
public List<Unit> sortieUnits = new List<Unit>();

今回の実装で、このsortieUnitsリストの中にUnitクラスとFortUnitクラスが混在することになります。
Unitのリストなのにこの中にFortUnitが混ざったらエラーになるのでは?
と思うかもしれませんが問題なく動きます。
Unitクラスのメソッドが呼ばれるとき、その中身がFortUnitクラスならFortUnitのメソッドを呼んでくれるので動作にも問題ありません。

これは非常に大きなメリットです。
将来色々な特殊能力を持ったユニットを作りたくなっても基本ロジックを変更することなく実装できるようになります。
先ほどは継承は拡張機能といいましたが、どちらかというとこの抽象化の方が継承の本質となります。

最後に

継承が適切に使える時点で初心者じゃないよなーと思いつつ、かなり噛み砕いて初心者向けに解説してみたつもりです。(それでも難しいかもしれませんが…)
自動で定期的にユニットを生産する敵側の拠点ユニットも説明しようと思ったのですが力尽きたので次回に回します。

Discussion