【細說游戲 AI】這個 AI 好厲害,給我也整一個之狀態機 AI


前言

最近也看了不少關於游戲 AI 的相關文章,對這方面挺感興趣,就打算嘗試實現各種 AI,然后總結,寫成一個系列文章,這篇文章將介紹狀態機 AI,並給出在 Unity 中的相關實現。
去年 5 月份學校的動畫與游戲程序設計這門課程布置了個大作業,用 Unity 做坦克大戰(雖說就是照着教程做罷了),當時就試着用狀態機寫了 AI。這次跟幾個小伙伴參加 CUSGA(第一屆中國大學生游戲開發創作大賽),做了個簡化版的 RTS 游戲,其中的 AI 也是通過狀態機實現的。將在這邊文章中做個總結。

注:游戲目前實現的功能還並不是很多,如果看了演示視頻,希望能給些相關的改進建議,等這段時間畢設搞完之后就開始繼續改進。

有限狀態機(FSM)

這段時間看了不少大佬的分析和文章,了解到狀態機大致有有限狀態機和分層狀態機兩種,這里將先介紹有限狀態機。

定義

狀態機這個概念對大家來說應該並不陌生,比如編譯原理中的有窮自動機(FA),字符串算法中的自動機(AC自動機、后綴自動機、回文自動機等)等。

這里將給出定義:

  1. 包含有限個狀態
  2. 可以表示為一張圖,節點為狀態,邊為狀態的改變

其形式大致與 Unity 的 Animation Controller 類似

Unity Animation Controller

實現

關於有限狀態機 AI 的實現,最簡單的當然就是 if... else 或是 switch 判斷當前狀態,然后執行相應的 action,但是這樣的實現會導致當狀態和動作空間變大時,代碼難以維護。因此,我在實現的時候都是采用的狀態模式。

關於目前在項目中使用的狀態機架構,主要是五個類,分別為 Action、State、Decision、Transition、StateController,代碼如下:

Action

public abstract class Action : ScriptableObject
{
    public abstract void Act(StateController controller);
}

Decision

public abstract class Decision : ScriptableObject
{
    public abstract bool Decide(StateController controller);
}

Transition

[System.Serializable]
public class Transifition
{
    public Decision decision;
    public State trueState;
    public State falseState;
}

State

[CreateAssetMenu (menuName = "AI/State")]
public class State : ScriptableObject
{
    public Action[] actions;
    public Transifition[] transitions;

    public void UpdateState(StateController controller)
    {
        DoActions(controller);
        CheckTransitions(controller);
    }

    private void DoActions(StateController controller)
    {
        for(int i = 0; i < actions.Length; i++)
        {
            actions[i].Act(controller);
        }
    }

   private void CheckTransitions(StateController controller)
    {
        for(int i = 0; i < transitions.Length; i++)
        {
            bool decisionSucceeded = transitions[i].decision.Decide(controller);

            State transitionState = decisionSucceeded ? transitions[i].trueState : transitions[i].falseState;

            controller.TransitionToState(transitionState);

        }
    }
}

StateController

public class StateController : MonoBehaviour
{
    public State currentState;
    public State remainState;
    /*
    	其他成員變量
    */

    private bool aiActive = true;

    private void Awake()
    {
        // 自身成員變量設置
    }

    // 用於 Manager 類來設置 AI
    public void SetupAI(/* 參數 */)
    {
        // 需要設置的東西
    }

    // 用於狀態的改變
    public void TransitionToState(State nextState)
    {
        if(nextState != remainState)
        {
            //Debug.Log(transform.gameObject.name + " start" + nextState);
            currentState = nextState;
            OnExitState();
        }
    }

    // 用於判斷進入該狀態是否經過 duration 時間
    public bool CheckifCountDownElapsed(float duration)
    {
        stateTimeElapsed += Time.deltaTime;
        return (stateTimeElapsed >= duration);
    }
    
    public void OnExitState()
    {
        stateTimeElapsed = 0;
    }

    void Update()
    {
        if (!aiActive)
            return;
        currentState.UpdateState(this);
    }
}

使用

以上便是這幾個類的相關代碼,下面來分別講一講如何使用

  1. Action 作為抽象類,表示在某一 State 下,AI 將會執行的動作,通過繼承的方式實現需要執行的動作,比如要實現 AI 的移動的話

    [CreateAssetMenu(menuName = "AI/Actions/Player/Move")]
    public class MoveAction : Action
    {
        public State standby;
    
        public override void Act(StateController controller)
        {
            Move(controller);
        }
    
        private void Move(StateController controller)
        {
            controller.navMeshAgent.SetDestination(controller.targetPoint + controller.RelativePosition);
            controller.navMeshAgent.isStopped = false;
    
            if (controller.navMeshAgent.remainingDistance <= controller.navMeshAgent.stoppingDistance && !controller.navMeshAgent.pathPending)
            {
                // 到目標點后,改變狀態為 standby
                controller.navMeshAgent.isStopped = true;
                controller.TransitionToState(standby);
            }
        }
    }
    
  2. Decision 也是抽象類,在某一 State 下,AI 通過 Decision 的 Decide 來判斷是否滿足某一條件,比如下面場景:當 AI 在移動過程中遇到敵人則需要進入戰斗狀態,那么此時需要實現的 Decision 如下

    [CreateAssetMenu (menuName = "AI/Decisions/Player/Encounter")]
    public class EncounterDecision : Decision
    {
        // 用於玩家 AI 在前往目的地的過程中做決策
        public override bool Decide(StateController controller)
        {
            bool encounter = Encounter(controller);
            return encounter;
        }
    
        private bool Encounter(StateController controller)
        {
            Collider[] objects = Physics.OverlapSphere(controller.transform.position, controller.stats.attackRange * controller.stats.visionRange, 1 << 9);
            foreach (Collider c in objects)
            {
                // TODO: 設置攻擊目標
                controller.attackObject = c;
                return true;
            }
            return false;
        }
    }
    
  3. Transition 則用於 State 之間的轉換,代表了能從當前狀態可以轉到的狀態,其包含了 Decision、true State 和 false State。

  4. State 則表示狀態,其包含了 Actions 和 Transitions。

  5. StateController 則表示當前 AI,所有有關 AI 的屬性都可放置在其中,比如在我們做的游戲 Demo 中,StateController 就表示了兵團中的士兵,其包含了 Health、Attack、NavemeshAgent、AnimationController 等屬性,我們在游戲中可通過 Manager 相關的類來操作這些 StateController,從而控制 AI 的啟動、暫停等。

當實現了相關的類之后,便可在 Unity 中通過創建資源的方式來創建 State、Decision 和 Action,然后拼成我們需要的資源。

小節

本打算五一期間就把這篇文章寫出來的,但是中間出去玩了兩天,然后又咸魚了幾天,就拖到了現在。中間也在一直思考幾個問題:

  1. 要如何將現在實現的這個狀態機改進成分層狀態機,目前暫時還想不到,感覺這個分層的設計可能需要結合實際場景(希望大佬們能夠提供一些思路 orz
  2. 如何在當前狀態機框架中更好地實現動畫的表現,我目前是在實現 Action 的時候通過 Animation Controller 的狀態,以及執行時間來改變當前的動畫,但總感覺不是很好。(主要最近在知乎上看到林爺的文章之后感覺可以設計一個專門控制動畫的類)

繼續整畢設去了 orz,想想還有一個月多一點就要畢業了,好快- -


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM