TD-ディフェンダーの攻撃処理[弾を飛ばす]-part08

ディフェンダーユニットを作成出来ました。今度は敵を見つけたら攻撃する処理を作成します。

目次

攻撃を行う処理の仮実装

ディフェンダーの攻撃はいったん仮実装を行います。大味に作りながら各要素を整える。全体を一気に整えようとするとどこから手をつけたら良いかが分かりにくくなることがあります。

一定間隔ごとに攻撃処理

ディフェンダーユニットが一定間隔ごとに攻撃する処理を実装します。まずは実際に攻撃するのではなく、その「起こり」の部分を作りましょう。こういうときはやっぱりデバッグログで確認よ!

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

public class DefenderController : MonoBehaviour
{
    [SerializeField] private DefenderData defenderData;

    private void Start()
    {
        Initialize(defenderData);
    }

    public void Initialize(DefenderData defenderData)
    {
        this.defenderData = defenderData;

        var spriteRenderer = GetComponent<SpriteRenderer>();
        spriteRenderer.sprite = defenderData.unitSprite;

        StartCoroutine(AttackInterval());
    }

    private IEnumerator AttackInterval()
    {
        yield return new WaitForSeconds(defenderData.intervalSec);

        // ここで攻撃処理を実装する
        Debug.Log("攻撃したよ!");

        // 攻撃後、再び攻撃間隔を待つ
        StartCoroutine(AttackInterval());
    }
}

今回はCoroutineというものを利用して、非同期的に処理を実装しました。

StartCoroutineとIEnumeratorを利用することで、普段のプログラムとは異なる流れを作ることが出来ます。

WaitForSecondsの行では対象の行でintervalSec秒だけ停止して、経過後にDebug.Logの行が実行されます。プログラムを動かしてみると、intervalSecで設定した間隔でログが表示されるようになります。

攻撃前に攻撃される側にダメージを受ける処理を作る

攻撃する側とされる側、両方の実装が必要になりますが、プログラム的な順序としては先にダメージを受ける側から作成します。実際の開発だとエラー出しながらやったりしますが、ここではエラーなく進めましょう。

EnemyControllerに書きメソッドを追加します。

    public void TakeDamage(float damage)
    {
        if (hp <= 0)
        {
            return;
        }
        hp -= damage;
        Debug.Log("HP: " + hp + "/" + enemyData.maxHp);
        if (hp <= 0)
        {
            Destroy(gameObject);
        }
    }

攻撃処理の仮実装

敵を攻撃できるようになったので、次はどの敵を攻撃するかを決めたいと思います。まずは下記を考慮したいところですが、射程範囲内のチェックだけは考慮しつつ、一旦範囲外でも攻撃します。(当たったかどうかの確認を省くため)

  • すべての敵を探す
  • 見つけた敵の中で一番近い敵に狙いを定める
  • その敵が射程範囲内かどうか

AttackIntervalメソッドを修正して対応します。

    private IEnumerator AttackInterval()
    {
        yield return new WaitForSeconds(defenderData.intervalSec);

        // ここで攻撃処理を実装する

        // シーン内のEnemyControllerを探す
        EnemyController[] enemies = FindObjectsOfType<EnemyController>();
        EnemyController nearestEnemy = null;
        float nearestDistance = Mathf.Infinity;
        foreach (var enemy in enemies)
        {
            // 一番近い敵を探す
            float distance = Vector3.Distance(transform.position, enemy.transform.position);
            if (distance < nearestDistance)
            {
                nearestEnemy = enemy;
                nearestDistance = distance;
            }
        }

        if (nearestEnemy != null)
        {
            if (nearestDistance <= defenderData.range)
            {
                Attack(nearestEnemy, defenderData.power);
            }
            else
            {
                Debug.Log("敵が範囲外だよ!");
                // ただし今回は攻撃を行う
                Attack(nearestEnemy, defenderData.power);
            }
        }

        // 攻撃後、再び攻撃間隔を待つ
        StartCoroutine(AttackInterval());
    }
    private void Attack(EnemyController target, float power)
    {
        target.TakeDamage(power);
    }

あとは動かしてみると、ログにダメージが与えられているのが確認できると思います。実際に撃破出来たかどうかを確認したい場合、次のような対処をしてみて、敵を倒すところまでやってみても良いですね。

  1. ディフェンダーの攻撃力を上げる
  2. ディフェンダーの攻撃インターバルを早くする
  3. 敵のスピードを遅くする
  4. 敵の耐久度を下げる

攻撃用の弾を飛ばす

暫定的に攻撃ができるようになりました。ゲームではダメージを与える弾が着弾することで攻撃を行います。

攻撃用の弾のスクリプトを作成

攻撃用の弾に必要な機能は以下。スクリプト名はBulletとしましょう。弾速に関しては、一旦固定とします。

  • どれだけの攻撃力を持っているか
  • どの敵を追いかけるのか
  • エラー対応
    • 狙っている敵がいなくなった時
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Bullet : MonoBehaviour
{
    private EnemyController targetEnemy;
    private float speed = 10.0f;
    private float power = 1.0f;

    public void Initialize(int power, EnemyController target)
    {
        this.targetEnemy = target;
        this.power = power;
    }
    void Update()
    {
        if (targetEnemy == null)
        {
            Destroy(gameObject);
            return;
        }

        transform.position = Vector3.MoveTowards(transform.position, targetEnemy.transform.position, speed * Time.deltaTime);

        // ターゲットまでの距離が0.1以下になったらターゲットを破壊する
        if (Vector3.Distance(transform.position, targetEnemy.transform.position) < 0.1f)
        {
            targetEnemy.TakeDamage(power);
            Destroy(gameObject);
        }
    }
}

弾を作る

弾は次の手順でプレファブ化まで行います

  • 2D Object>Sprites>Circle
    • 名前をBulletに変更。
    • Bulletスクリプトを貼り付ける
  • 色を変更
    • わかりやすく赤とかに変更します
  • 大きさを調整
    • ここまで同様の素材を利用している場合、スケールを0.25ぐらいに調整するといい感じ
    • 大きさはあまり関係が無いので視認できればOK

弾を射つ様にスクリプト変更

プレファブの準備が出来たらDefenderControllerを編集しましょう。変わっているのは弾のプレファブをセットできるようになったのと、Attackメソッドの中で敵を攻撃するんじゃなくて、弾に依頼する変更です。

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

public class DefenderController : MonoBehaviour
{
    [SerializeField] private Bullet bulletPrefab;

    private void Attack(EnemyController target, float power)
    {
        var bullet = Instantiate(bulletPrefab, transform.position, Quaternion.identity);
        bullet.Initialize(power, target);
    }
}

スクリプトを変更できたら、DefenderゲームオブジェクトのインスペクターにBulletプレファブをセットします。

動かして確認

あとは動かして確認してみましょう。今まで攻撃をしていたタイミングで弾が発射されます。ダメージ用のログが表示されるのも弾が敵に当たったタイミングになります。

早くて追いづらい場合はスクリプトのスピードを調整してみてください。

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

この記事を書いた人

コメント

コメントする

目次