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程式碼如下:
撰寫好程式碼後,將Waypoint Container拉進Patrol Path。
大家可以從場景中看見繪製好的巡邏點。
接著回到EnemyAI撰寫巡邏狀態的程式碼吧。如下圖,我們先用GetChild取得巡邏點,用character的SetDestination方法設置巡邏點位置。
CycleWayPointWhenClose方法則是用來判斷Enemy是否已經抵達目前設定的巡邏點,若Enemy的位置與巡邏點位置小於waypointToLerance,便將標誌巡邏點的index加1。
以下提供完整的EnemyAI程式碼:
完成啦!讓我們執行遊戲試玩看看吧。
玩家在追擊範圍外,所以敵人循著場景中的巡邏點移動著。
當玩家接近敵人的巡邏點後,敵人追過來啦!
玩家進入攻擊範圍,敵人拿起鋤頭開始攻擊!
攻擊的好猛烈!
把敵人打死後,敵人還趴在地上繼續巡邏.......(- _ -)這個Bug會在後面的文章改進。
首先,我們將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會在後面的文章改進。
留言
張貼留言