1. 程式人生 > >Unity 有限狀態機(Finite State Machine)的理解 與 實現簡單的可插拔(Pluggable)AI指令碼物件。

Unity 有限狀態機(Finite State Machine)的理解 與 實現簡單的可插拔(Pluggable)AI指令碼物件。

#Unity 有限狀態機(Finite State Machine)的理解 與 實現簡單的可插拔AI指令碼物件。

  一般的遊戲AI都是使用狀態機的設計模式來實現的。發現官方有教程,就跟了一遍,這裡就總結一下。
  先簡單說一下狀態模式。就是根據當前狀態和對應條件,選則需要轉換的下一個狀態。例如本文的例子,坦克預設狀態是巡邏(狀態),在發現有敵人時(條件),轉換成追殺(狀態),在敵人逃脫或死掉了之後(條件),又變成巡邏(狀態)。

  遊戲場景:綠色標籤為巡邏點。藍色標籤為玩家出生點。紅色為AI出生點。(如圖玩家被兩個AI追殺。)

AI物件結構

  每個AI物件都有一個狀態控制器(StateController)指令碼元件,包含一個當前狀態(State),且狀態包含需要執行動作(Action),還有狀態轉換的條件(Transition)。

  Unity中檔案分類層級如下。其中DefaultEnemyStats是預設AI的一些配置。包括攻擊距離,移動速度,旋轉速度等等。

State (狀態)

using UnityEngine;

[CreateAssetMenu (menuName = "PluggableAI/State")]
public class State : ScriptableObject 
{
    public Action[] actions;                        //動作
    public Transition[] transitions;                //通過決定,選擇下一種動作決定
public Color sceneGizmoColor = Color.gray; //拿來渲染eyes的Gizmos顏色 //每一幀更新狀態,在StateController的OnUpdate中呼叫。 public void UpdateState(StateController controller) { DoActions(controller); //執行動作 CheckTransition(controller); //檢測轉換狀態 } //順序執行動作列表的動作。
private void DoActions(StateController controller) { for (int i = 0; i < actions.Length; i++) actions[i].Act(controller); } //檢查所有轉換狀態,並改變狀態。 private void CheckTransition(StateController controller) { for (int i = 0; i < transitions.Length; i++) { //這裡條件轉換隻有兩個,所以直接用Bool型別來判斷。當然也可以有多種條件轉換。 bool decisionSucceeded = transitions[i].decision.Decide(controller); if (decisionSucceeded) controller.TransitionToState(transitions[i].trueState); else controller.TransitionToState(transitions[i].falseState); } } }
狀態名 說明 配置
Remain State 保持原來狀態。不包含任何屬性。 略。
PatrolChaser 巡邏者狀態。在巡邏點之間巡邏,如果檢測到敵人,轉為追殺狀態。
ChaseChaser 追殺者狀態。導航到目標一定距離並持續攻擊,直到目標死掉,轉回巡邏者模式。
PatrolScanner 同Patrol Chaser。巡邏到敵人,轉為追殺。
ChaseChaser 同Chase Chaser 。追殺敵人,但到目標死掉,轉換成警覺模式。
AlertScanner 警覺模式。其實就是旋轉掃描敵人,直到掃描到目標或超過了一定時間。

  通過上表其實可以發現有兩個非常相似的動作,只是為了演示Alert Scanner 的。其實是可以優化的,就是修改Transition的結構,不再是隻有兩種狀態選擇,而是多種,這樣就可以更好的重用狀態。

Action(動作)

using UnityEngine;

public abstract class Action : ScriptableObject 
{
    //State直接呼叫這個方法來執行動作。
    public abstract void Act(StateController controller);
}
動作名 說明 原理
PatrolAction 巡邏動作。從StateController搞來巡邏點列表。在這些巡邏點之間巡邏移動。 設定巡邏點為導航目標點,到達後設置下一個巡邏點為導航點。
ChaseAction 追隨動作。最短距離到達目標。 設定目標為導航目標點,導航過去就是了。
AttackAction 攻擊動作。攻擊目標。 射出檢測射線(紅色的),檢測到目標就發炮攻擊。

Decision(決定)

using UnityEngine;

public abstract class Decision : ScriptableObject 
{
    //通過這個方法的返回值來判斷決定的選擇。
    public abstract bool Decide(StateController controller);
}
決定名 說明 原理
LookDecision 檢測決定。檢測到目標就設定為追蹤目標,並且返回True。 射出檢測球體射線(綠色的)來檢測。
ActiveStateDecision 活動狀態決定。就是判斷追蹤的目標是否active(啟用狀態)。 直接返回追蹤目標的active的bool值。
ScanDecision 掃描決定。說是掃描其實並沒有掃描,只是在轉圈而已。一直轉到外部條件改變狀態,或者超過了掃描限定的時間。 停止導航。配合Time.deltaTime旋轉自己。返回是否超過掃描限定時間。

Transitions(狀態轉換)

//狀態轉換。通過決定的返回值,選則兩種狀態中其中一個
[System.Serializable]
public class Transition 
{
    public Decision decision;
    public State trueState;
    public State falseState;
}

測試結果

第一種,巡邏->發現目標->追殺->目標死亡->巡邏。(顏色配合下圖樣例使用。)

這裡寫圖片描述

1. 起始 Patrol Chase 狀態,巡邏點之間移動,還沒有檢測到目標,眼裡射出的是清澈的綠色。

2. 發現了目標,轉為Chase Chaser 狀態,射出了充滿憤怒的紅色射線,並且一直攻擊目標。

3. 當目標化為灰燼,轉回 Patrol Chase 狀態。

第二種,巡邏->發現目標->追殺->目標死亡->旋轉掃描目標->{兩種情況:1. 發現目標,回到第二步。2. 超過掃描時間,回到第一步(巡邏)。(顏色配合下圖樣例使用。)

這裡寫圖片描述

1. 起始 Patrol Scanner 狀態,巡邏,眼裡射出的是原諒的綠色。

2. 發現剛剛殺了藍孩子的小紅,轉為 Chase Scanner 狀態,追殺之。

3. 殺掉小紅之後,轉為 Alert Scanner 狀態。旋轉掃描周圍是否有敵人。

4. 沒有掃到,回到 Patrol Scanner 狀態。