【第9回】敵キャラがプレイヤーを攻撃できるようにする

前回の作業で一応敵キャラが目的地に向かって移動するようになりました。しかし今の状態では単純に目的地に向かって動くだけで、プレイヤーと接触しても何も起きないので敵というよりは単なる「的」ですよね。

そこでここではゾンビのC#スクリプトを改良して、

  • プレイヤーが近くにいる場合はプレイヤーを追いかけて攻撃する
  • 攻撃を受けたプレイヤーは残りの弾薬数が減ってしまう

という挙動を実現させましょう。

スポンサーリンク

敵キャラがプレイヤーを追いかけて攻撃するようになるC#スクリプト

では早速ですが「EnemyController」スクリプトに色々と変更を加えます。C#スクリプトを開いたら下のように書き換えてください(※今回は変更が多いので、長くなりますが全文を掲載します)。

using System.Collections;
using UnityEngine;
using UnityEngine.AI;	// ★変更1

public class EnemyController : MonoBehaviour
{

  [SerializeField]
  Animator animator = null;
  [SerializeField]
  NavMeshAgent navmeshAgent = null;   // ★変更1
  [SerializeField]
  Transform target = null;   // ★変更1
  [SerializeField]
  CapsuleCollider capsuleCollider = null;
  [SerializeField, Min(0)]
  int maxHp = 3;
  [SerializeField]
  float deadWaitTime = 3;

  // ★変更2
  [SerializeField]
  float chaseDistance = 5;
  [SerializeField]
  Collider attackCollider = null;
  [SerializeField]
  int attackPower = 10;
  [SerializeField]
  float attackTime = 0.5f;
  [SerializeField]
  float attackInterval = 2;
  [SerializeField]
  float attackDistance = 2;

  // アニメーターのパラメーターのIDを取得(高速化のため)
  readonly int SpeedHash = Animator.StringToHash("Speed");
  readonly int AttackHash = Animator.StringToHash("Attack");
  readonly int DeadHash = Animator.StringToHash("Dead");

  bool isDead = false;
  int hp = 0;
  Transform thisTransform;

  // ★変更2
  bool isAttacking = false;
  Transform player;
  Transform defaultTarget;
  WaitForSeconds attackWait;
  WaitForSeconds attackIntervalWait;

  public int Hp
  {
    set
    {
      hp = Mathf.Clamp(value, 0, maxHp);
    }
    get
    {
      return hp;
    }
  }

  void Start()
  {
    thisTransform = transform;  // transformをキャッシュ(高速化)

    // ★変更2
    defaultTarget = target;
    player = GameObject.FindGameObjectWithTag("Player").transform;

    // ★変更2。WaitForSecondsをキャッシュして高速化
    attackWait = new WaitForSeconds(attackTime);
    attackIntervalWait = new WaitForSeconds(attackInterval);

    InitEnemy();	
  }

  void Update()
  {
    if (isDead)
    {
      return;
    }

    CheckDistance();	// ★変更2
    Move();	// ★変更1
    UpdateAnimator();
  }

  void InitEnemy()
  {
    Hp = maxHp;
  }

  // 被ダメージ処理
  public void Damage(int value)
  {
    if(value <= 0)
    {
      return;
    }

    Hp -= value;

    if(Hp <= 0)
    {
      Dead();
    }
  }

  // 死亡時の処理
  void Dead()
  {
    isDead = true;
    capsuleCollider.enabled = false;
    animator.SetBool(DeadHash, true);

    // ★変更2
    StopAttack();
    navmeshAgent.isStopped = true;

    StartCoroutine(nameof(DeadTimer));
  }

  // 死亡してから数秒間待つ処理
  IEnumerator DeadTimer()
  {
    yield return new WaitForSeconds(deadWaitTime);

    Destroy(gameObject);
  }

  // ★変更1
  void Move()
  {
    navmeshAgent.SetDestination(target.position);
  }

  // アニメーターのアップデート処理
  void UpdateAnimator()
  {
    // ★変更1
    animator.SetFloat(SpeedHash, navmeshAgent.desiredVelocity.magnitude);
  }

  // ★変更2 以下を追加
  
  void CheckDistance()
  {
    // プレイヤーまでの距離(二乗された値)を取得
    // sqrMagnitudeは平方根の計算を行わないので高速。距離を比較するだけならそちらを使った方が良い
    float diff = (player.position - thisTransform.position).sqrMagnitude;

    // 距離を比較。比較対象も二乗するのを忘れずに
    if (diff < attackDistance * attackDistance)
    {
      if (!isAttacking)
      {
        StartCoroutine(nameof(Attack));
      }
    }
    else if (diff < chaseDistance * chaseDistance)
    {
      target = player;
    }
    else
    {
      target = defaultTarget;
    }
  }

  IEnumerator Attack()
  {
    isAttacking = true;
    animator.SetTrigger(AttackHash);
    attackCollider.enabled = true;

    yield return attackWait;

    attackCollider.enabled = false;

    yield return attackIntervalWait;

    isAttacking = false;
  }

  void StopAttack()
  {
    StopCoroutine(nameof(Attack));
    attackCollider.enabled = false;
    isAttacking = false;
  }

  private void OnTriggerEnter(Collider other)
  {
    if (other.gameObject.CompareTag("Player"))
    {
      FpsGunControler gun = other.gameObject.GetComponent<FpsGunControler>();

      if(gun != null)
      {
        gun.CurrentAmmo -= attackPower;
      }
    }
  }

}

C#スクリプトの解説

コメントを入れておいたので詳しい解説は省略しますが、主に

  • プレイヤーに近づいたら追いかける
  • さらにプレイヤーに近づいたら攻撃する

という処理を追加しました。

攻撃については、トリガーを設定しておいてその範囲内にプレイヤーが入ったら弾薬が減るようにしてあります。

必要な設定を行う

さて色々変更を加えたので追加の設定を行う必要があります。主な作業は次の2つです。

  1. プレイヤーキャラクターにタグを設定する
  2. ゾンビにトリガーを設定する

プレイヤーキャラクターにタグを設定する

まず、多分まだプレイヤーキャラクターにタグを設定していないと思います。タグを確認して「Untagged」になっているようであればそれを「Player」に変更しておいてください。

こうしないとゾンビのスクリプトからプレイヤーを検索できないので注意。

ゾンビにトリガーを設定する

次に、ゾンビが攻撃に使うトリガーを設定します。ゾンビのプレハブに下の図のようにBox Colliderを追加して設定を行いましょう。

攻撃用のトリガーの設定

ちなみにコンポーネント名の横のチェックは外しておいてください。そうしないと(ゾンビが攻撃していなくても)ゾンビに近づくだけで弾薬が減ってしまいます。

テストプレイして動作を確認しよう

ここまでできたらテストプレイして動作を確認してみましょう。適当な位置に新しいゲームオブジェクトを作り、ゾンビの「Target」欄に登録して再生ボタンを押してみてください。ゾンビが

  • プレイヤーが離れている場合は登録地点に向かう
  • プレイヤーが近づくと追いかけてくる
  • プレイヤーがさらに近づくと攻撃してくる

という動きをすれば成功です。

敵の攻撃処理

(画像クリックでGIF再生)


次のページ→敵を一定間隔で生成するゲームオブジェクトの作り方