Related Posts Plugin for WordPress, Blogger...

4-12 Enemy AI Behaviour

本章要來製作新的Enemy AI啦!分為巡邏、追擊、攻擊三種狀態,敵人一開始會在原地的附近巡邏,當玩家靠近敵人以後便會開始追擊,一旦進入攻擊範圍就會開始攻擊玩家。

首先,我們將Enemy更名為EnemyAI。記得因為Class名稱改變的關係,有使用到Enemy的地方都會變成Nothing Selected,前幾章都有教大家如何更換,所以就不再贅述了。

以下針對幾行關鍵的程式碼進行解說,然後會再放完整程式碼給大家。我們先定義一個enum型別名為State,裡面有四種狀態idle, patrolling, attacking, chasing。

在Update中,我們先是判斷玩家與敵人之間的距離,若距離小於『追擊半徑』,便停止其他狀態,啟動追擊狀態。同理,若距離小於『攻擊半徑』,同樣停止其他的狀態並啟動攻擊狀態。

如下圖為追擊狀態的程式碼,我們使用character系統提供的移動功能追擊玩家。

如下圖為攻擊狀態的程式碼,使用Weapon System攻擊玩家,Weapon System會自動根據武器的動作更換Attack Animation。所以說,前半段我們如此辛苦的重構,在這邊就馬上體現出價值了!Enemy的攻擊模式跟自由度比過去高非常多。

接著來製作巡邏狀態吧。如下圖,請大家在場景中新增一個Empty GameObject,取名為Patrol Path,並於底下新增幾個GameObject取名為WayPoint,這些WayPoint將作為Enemy的巡邏座標。

如圖,讓我們佈置一下巡邏點吧。


是否發現這些巡邏點很不容易看到呢!

所以,接下來我們使用Gizmos來繪製巡邏點!請於Project視窗新增Script,名為WaypointContainer。

WaypointContainer程式碼如下:

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

namespace RPG.Character{
 public class WaypointContainer : MonoBehaviour {
  void OnDrawGizmos(){

   Vector3 firstPosition = transform.GetChild (0).position;
   Vector3 previousPosition = firstPosition;

   Gizmos.color = new Color(100f, 0f, 0f, 1f);
   foreach (Transform waypoint in transform) {
    Gizmos.DrawSphere (waypoint.position, .4f);
    Gizmos.DrawLine (previousPosition, waypoint.position);
    previousPosition = waypoint.position;
   }

   Gizmos.DrawLine (previousPosition, firstPosition);
  }
 }
}

撰寫好程式碼後,將Waypoint Container拉進Patrol Path。

大家可以從場景中看見繪製好的巡邏點。

接著回到EnemyAI撰寫巡邏狀態的程式碼吧。如下圖,我們先用GetChild取得巡邏點,用character的SetDestination方法設置巡邏點位置。

CycleWayPointWhenClose方法則是用來判斷Enemy是否已經抵達目前設定的巡邏點,若Enemy的位置與巡邏點位置小於waypointToLerance,便將標誌巡邏點的index加1。

以下提供完整的EnemyAI程式碼:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
using RPG.Core;
using RPG.Weapons;

namespace RPG.Character{
 [RequireComponent(typeof(WeaponSystem))]
 public class EnemyAI : MonoBehaviour {

  [SerializeField] float attackRadius = 3.0f;
  [SerializeField] float chaseRadius = 10.0f;
  [SerializeField] WaypointContainer patrolPath;
  [SerializeField] float waypointToLerance = 2.0f;

  enum State {idle, patrolling, attacking, chasing}
  State state = State.idle;

  float distanceToPlayer;
  int nextWaypointIndex;
  PlayerMovement player;
  Character character;
  WeaponSystem weaponSystem;


  void Start(){
   player = GameObject.FindObjectOfType();
   character = GetComponent ();
   weaponSystem = GetComponent ();
  }

  void Update(){
   // 計算Player跟Enemy的距離
   distanceToPlayer = Vector3.Distance (player.transform.position, transform.position);

   // 自動巡邏
   if (distanceToPlayer > chaseRadius && state != State.patrolling) {
    StopAllCoroutines ();
    StartCoroutine (Patrol ());
   }

   // 開始追擊
   if (distanceToPlayer <= chaseRadius && state != State.chasing) {
    StopAllCoroutines ();
    StartCoroutine (ChasePlayer ());
   }

   // 攻擊
   if (distanceToPlayer <= attackRadius && state != State.attacking) {
    StopAllCoroutines ();
    StartCoroutine (AttackPlayer ());
   }

  }

  IEnumerator Patrol(){
   state = State.patrolling;

   while (distanceToPlayer > chaseRadius) {
    // 取得巡邏點位置
    Vector3 nextWaypointPos = patrolPath.transform.GetChild (nextWaypointIndex).position;
    // 設定巡邏點
    character.SetDestination (nextWaypointPos);
    // 檢查是否已靠近下一個巡邏點
    CycleWaypointWhenClose (nextWaypointPos);
    yield return new WaitForSeconds(0.5f);
   }
  }

  private void CycleWaypointWhenClose(Vector3 nextWaypointPos){
   // 計算Enemy是否已經抵達巡邏點
   if (Vector3.Distance (transform.position, nextWaypointPos) <= waypointToLerance) {
    // 更新巡邏點編號
    nextWaypointIndex = (nextWaypointIndex + 1) % patrolPath.transform.childCount;
   }
  }

  IEnumerator ChasePlayer(){
   state = State.chasing;

   while (distanceToPlayer <= chaseRadius) {
    character.SetStopDistance (attackRadius);
    character.SetDestination (player.transform.position);
    yield return new WaitForEndOfFrame ();
   }
  }

  IEnumerator AttackPlayer(){
   state = State.attacking;

   while (distanceToPlayer <= attackRadius) {
    weaponSystem.AttackTarget (player.gameObject);
    yield return new WaitForEndOfFrame ();
   }
  }

  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);
  }
 }
}

完成啦!讓我們執行遊戲試玩看看吧。

玩家在追擊範圍外,所以敵人循著場景中的巡邏點移動著。

當玩家接近敵人的巡邏點後,敵人追過來啦!

玩家進入攻擊範圍,敵人拿起鋤頭開始攻擊!

攻擊的好猛烈!

把敵人打死後,敵人還趴在地上繼續巡邏.......(- _ -)這個Bug會在後面的文章改進。

留言