Related Posts Plugin for WordPress, Blogger...

2-16 InvokeRepeating() vs StartCoroutine()

本章要來介紹控制敵人發射Projectile的方法,繼上一章我們完成了讓敵人發射Projectile的方法(每個Frame都發射),這一章要介紹如何控制發射的速度,以及當玩家離開敵人的攻擊範圍時,敵人就必須停止發射的方法。

首先,我們這次會用到Untiy的InvokeRepeating及StartCoroutine方法,如不清楚的話請參閱Untiy官方提供的教學:

MonoBehaviour.InvokeRepeating
https://docs.unity3d.com/ScriptReference/MonoBehaviour.InvokeRepeating.html

MonoBehaviour.StartCoroutine
https://docs.unity3d.com/ScriptReference/MonoBehaviour.StartCoroutine.html

兩者最大的差異,在於InvokeRepeating呼叫方法時是string reference,這對於未來進行方法的重構時非常糟糕,不但找錯誤不方便,編輯器也無法自動幫你修改名稱,所以再三建議大家盡量不要用string reference的方法。再者,InvokeRepeating只能單一支援延遲執行跟每次執行的頻率,若在方法內部想要針對特殊情況進行暫停、修改執行頻率等等都不太可能。

相對StartCoroutine就能辦到,所以StartCoroutine是非常強大又好用的方法。

以下兩種寫法都會介紹。首先我設置了一個bool型別的變數isAttacking,然後使用InvokeRepeating呼叫SpawnProjectile。

這時進入敵人的攻擊範圍後,敵人就會開始發射Projectile,並且不會再向之前那樣連續發射。

然後以下是用StartCoroutine的寫法:


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
using UnityStandardAssets.Characters.ThirdPerson;

public class Enemy : MonoBehaviour, IDamageable {

 [SerializeField] float maxHealthPoints = 100f;
 [SerializeField] float attackRadius = 3.0f;
 [SerializeField] float chaseRadius = 10.0f;
 [SerializeField] float damagePerShot = 9f;
 [SerializeField] float secondsBetweenShots = 0.5f;
 [SerializeField] GameObject projectileToUse;
 [SerializeField] GameObject projectileSocket;

 bool isAttacking = false;
 float currentHealthPoint = 100f;
 AICharacterControl aiCharacterControl = null;
 GameObject player;
 IEnumerator coroutine;

 public float healthAsPercentage{
  get{ return currentHealthPoint / maxHealthPoints; }
 }

 void Start(){
  aiCharacterControl = GetComponent ();
  player = GameObject.FindGameObjectWithTag ("Player");
 }

 void Update(){
  // 計算Player跟Enemy的距離
  float distanceToPlayer = Vector3.Distance (player.transform.position, transform.position);
  // 若彼此之間的距離小於attackRadius,就讓Enemy開始攻擊Player
  if (distanceToPlayer <= attackRadius && !isAttacking) {
   isAttacking = true;
   coroutine = SpawnProjectile ();
   StartCoroutine (coroutine);
  } 
  if (distanceToPlayer > attackRadius && isAttacking) {
   isAttacking = false;
   StopCoroutine (coroutine);
  }
  // 若彼此之間的距離小於chaseRadius,就讓Enemy開始追蹤Player
  if (distanceToPlayer <= chaseRadius) {
   aiCharacterControl.SetTarget (player.transform);
  } else {
   aiCharacterControl.SetTarget (transform);
  }
 }

 IEnumerator SpawnProjectile(){
  while (true) {
   yield return new WaitForSeconds (secondsBetweenShots);

   // Instantiate可以生成GameObject,Quaternion.identity為方向不進行任何轉向
   GameObject newProjectile = Instantiate(projectileToUse, projectileSocket.transform.position, Quaternion.identity);
   // 取得Projectile
   Projectile projectileComponent = newProjectile.GetComponent ();
   // 設定Projectile的攻擊威力
   projectileComponent.SetDamage(damagePerShot);
   // 計算發射Projectile到Player之間的單位向量
   Vector3 unitVectorToPlayer = (player.transform.position - projectileSocket.transform.position).normalized;
   float projectileSpeed = projectileComponent.projectileSpeed;
   // 將單位向量乘以發射速度,透過velocity將Projectile發射出去吧!
   newProjectile.GetComponent ().velocity = unitVectorToPlayer * projectileSpeed;
  }
 }

 public void TakeDamage(float damage){
  // Math.Clamp可以確保數值運算後不會低於最小值或者高於最大值
  currentHealthPoint = Mathf.Clamp (currentHealthPoint - damage, 0f, maxHealthPoints);
 }

 void OnDrawGizmos(){
  //繪製攻擊範圍
  Gizmos.color = new Color(255f, 0f, 0f, 0.5f);
  Gizmos.DrawWireSphere (transform.position, attackRadius);

  //繪製移動範圍
  Gizmos.color = new Color(0f, 0f, 255f, 0.5f);
  Gizmos.DrawWireSphere (transform.position, chaseRadius);
 }
}


完成!執行遊戲以後,玩家進入攻擊範圍敵人便開始攻擊,離開範圍後便會停止攻擊。



留言