TD-ScriptableObjectで敵のデータを作る part05

開発中はテスト用のデータをよく利用します。現在の敵のオブジェクトがまさにそれです。実際にゲームを作る場合、様々な敵の種類を用意する必要が出てきます。このようなときの対応の1つとしてScriptableObject(スクリプタブルオブジェクト)というものがあります

目次

敵のデータを作成する

敵のデータで必要なものを洗い出す

タワーディフェンスゲームで敵に必要なデータは何でしょうか?

  • 名前(なくても良いけど)
  • 敵の画像(もしくはプレファブ)
  • 耐久値(HP)
  • 攻撃力
  • スピード

最低でもこのあたりが欲しいところです。Unityだと、敵をプレファブ化して、インスペクターで設定するという方法も悪くありませんが、今回はデータのみの塊を作成し、それを利用したいと思います。

敵のスクリプタブルオブジェクトを作るスクリプト

こういうのは作って見るのが一番はやい。ということで上記の要件を満たしたスクリプタブルオブジェクトを作ってみましょう。EnemyDataというスクリプトを作成します。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[CreateAssetMenu(fileName = "EnemyData", menuName = "TD/Create EnemyData")]
public class EnemyData : ScriptableObject
{
    public string enemyName;
    public Sprite enemySprite;
    public float maxHp;
    public float power;
    public float speed;
}

注意ポイントとしては以下

  • クラス名の右隣、いつもはMonoBehaviourになっているところがScriptableObject
  • クラス名の上の行にいつもと違う但し書きがある
    • なくても良いものもあるけど、とりあえず書いておいてください
  • メンバー変数はpublic
    • このあたりは慣れてきたり、用途に合わせて変更する必要がありますが、今回はすべてpublic

スクリプトが準備出来ただけではスクリプタブルオブジェクトとしては使えません。次の項目で実際に作ってみます

実際にスクリプタブルオブジェクト作ってみた

EnemyDataスクリプトが準備できたら、実際にスクリプタブルオブジェクトを作ってみたいと思います。Assets直下にScriptableObjectsフォルダを作成し、その中に作って見ましょう。

  • フォルダを作成
    • Assets/ScriptableObjects
  • フォルダ内でアセットを作成
    • 右クリック>Create>TD>Create EnemyData
    • アセットが作られるので敵の名前がわかるようなファイル名に変更(今回は1キャラなのでデフォルトでもOK)
  • 作成されたアセットのデータを設定する
    • 下図を参考にパラメータを設定してみてください
    • EnemyName:tako
    • EnemySprite:タコの画像
    • MaxHp:5
    • Power:5
    • Speed:2

スクリプタブルオブジェクトのデータを反映してみる

ではテストとして、スクリプタブルオブジェクトのデータが反映されるか確認してみたいと思います。スクリプトは以下のように変更。このタイミングで耐久力や攻撃力などをもたせます。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Cinemachine;

public class EnemyController : MonoBehaviour
{
    [SerializeField] private EnemyData enemyData; // 敵のデータ
    [SerializeField] private Castle targetCastle; // お城

    private float hp;
    private float power;
    private float speed;

    private void Start()
    {
        ApplyEnemyData(enemyData);

        var lineMover = GetComponent<LineMover>();
        lineMover.OnEndReached += (sender, e) =>
        {
            Debug.Log("お城に到着したよ!");
            targetCastle.TakeDamage(power);

            var impulseSource = GetComponent<CinemachineImpulseSource>();
            impulseSource.GenerateImpulse(0.1f);
        };
    }

    public void ApplyEnemyData(EnemyData enemyData)
    {
        hp = enemyData.maxHp;
        power = enemyData.power;
        speed = enemyData.speed;
        GetComponent<LineMover>().SetSpeed(speed);

        var spriteRenderer = GetComponent<SpriteRenderer>();
        spriteRenderer.sprite = enemyData.enemySprite;
    }
}

スクリプトの更新が出来たら、

  • 敵のスプライトを別の画像にする(もしくはnone)
  • インスペクターにEnemyDataをセット
  • ゲームを動かして以下を確認
    • スプライトが反映される
      • スピードが反映される
      • お城に与えるダメージが反映される

敵を生成させる

これにて作成したデータを反映させることができるようになりました。ゲームでは時間に応じて敵を生成します。実際には時間に応じて生成する必要がありますが、今回は1体だけ生成し、連続生成の準備をしたいと思います。

EnemyControllerを編集

敵を生成するプログラムから作りたかったんですが、初期化などで呼び出すメソッドがなかったため、敵単体の方を先に修正します。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Cinemachine;

public class EnemyController : MonoBehaviour
{
    private EnemyData enemyData; // 敵のデータ
    private Castle targetCastle; // お城

    private float hp;
    private float power;
    private float speed;

    public void Initialize(EnemyData enemyData, LineRenderer lineRenderer, Castle targetCastle)
    {
        this.enemyData = enemyData;

        hp = enemyData.maxHp;
        power = enemyData.power;
        speed = enemyData.speed;
        GetComponent<LineMover>().SetSpeed(speed);

        var spriteRenderer = GetComponent<SpriteRenderer>();
        spriteRenderer.sprite = enemyData.enemySprite;

        var lineMover = GetComponent<LineMover>();
        lineMover.Initialize(0, speed, lineRenderer);

        this.targetCastle = targetCastle;

        lineMover.OnEndReached += (sender, e) =>
        {
            Debug.Log("お城に到着したよ!");
            this.targetCastle.TakeDamage(power);

            var impulseSource = GetComponent<CinemachineImpulseSource>();
            impulseSource.GenerateImpulse(0.1f);
        };
    }
}

上記の変更をすることで、インスペクターで敵のスクリプタブルオブジェクトをセットできなくなります。これはInitializeメソッドで明示的に設定を行う必要を持たせるためです。初期化できる人を限定することで設定箇所を見つけやすくする狙いもあります。

スクリプトの変更が出来たら、ヒエラルキーのtakoをPrefabsフォルダを作成し、そこにドラッグアンドドロップをしてください。プレファブ化が出来たらヒエラルキーのtakoは消します。

  • フォルダを作成
    • Assets/Prefabs
  • 敵をプレファブ化
    • ヒエラルキーのタコをPrefabsフォルダにドラッグアンドドロップ
    • 青い箱っぽいのになれば成功
    • 名前をEnemyPrefabとかに変更しておきましょう

EnemyGeneratorスクリプトを作成

まずは敵を作るための基本となるEnemyGeneratorスクリプトを作成します。このあたりからインスペクター依存なのか、プログラム依存なのか気をつけないとうまく動かなくなってきます。インスペクターでセットするオブジェクトもヒエラルキーなのかプロジェクトビューなのかとかの問題もありますね。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EnemyGenerator : MonoBehaviour
{
    [SerializeField] private LineRenderer lineRenderer;
    [SerializeField] private Castle targetCastle;
    [SerializeField] private EnemyData enemyData;

    [SerializeField] private EnemyController enemyPrefab;

    private void Start()
    {
        var enemy = Instantiate(enemyPrefab, transform.position, Quaternion.identity);
        enemy.Initialize(enemyData, lineRenderer, targetCastle);
    }

}

インスペクターのセット

ジェネレーターのプログラムが作成できたらセットを行って思ったとおりに動いてくれるか確認してみましょう。

  • 空のゲームオブジェクトを作成
    • 名前:EnemyGenerator
    • 位置などは適当でも大丈夫ですが、Resetしておくといいですね。気持ちいい
  • スクリプトのアタッチ
    • EnemyGeneratorゲームオブジェクトにEnemyGeneratorスクリプトをアタッチ
  • インスペクターのセット
    • LineRenderer:今まで敵キャラが移動を行っていたラインをセット
    • TargetCastle:攻撃対象のお城(ラインの終端に相当するお城)
    • EnemyData:今回は指定した1キャラのみ。先程作成したスクリプタブルオブジェクト
    • EnemyPrefab:プレファブ化したやつ。

動かして確認してみる

この変更で、画面的な違いはありません。今まで通りに動いていることを確認します。次に作るスケジュール通りに敵を生成するために必要な儀式です。

さ、大丈夫だったでしょうか?一見意味のない確認に思えるかも知れませんが、大きな変更を行う前に、各機能が正しく動いていることを確認するのはとても重要です。次回ではこの単体の敵を作成する機能を利用して、スケジュールした敵を生成していく予定です。

Challenge!

お手持ちの敵キャラ画像などがあれば、新しいスクリプタブルオブジェクトを作成し、タコ以外の敵を作ってみましょう。パラメータを変えたりすると、強い敵やもっと弱い敵を作ることができるようになります。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

コメント

コメントする

目次