使用ScriptableObject基於有限狀態機AI與自由拓展

使用ScriptableObject基於有限狀態機AI與自由拓展

參考視頻:https://v.qq.com/x/page/c0537cidpg9.html

目標:

  1. 創建基於FSM(Finite State Machine有限狀態機)的遊戲AI。
  2. 實現狀態,動作,決策以及轉換。
  3. 使用ScriptableObject實現AI配置。

 

有限狀態機:

  • 一臺有有限個狀態的機器。
  • 狀態機因爲外部輸入或者環境改變(持續檢測)而轉換或者保持狀態。
  • 每個狀態包括一個或多個動作,和一個規定的轉換決策。

首先定義一個StateController,每個AI有且只有一個。其中的重要部分是當前狀態和保持狀態。

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

public class StateController : MonoBehaviour
{
    public State currentState;
    public State remainState;
    public EnemyStats enemyStats;
    public Transform eyes;
    public float HP = 100f;


    [HideInInspector] public NavMeshAgent navMeshAgent;
    //[HideInInspector] public Complete.TankShooting tankShooting;
    public List<Transform> wayPointList;
    [HideInInspector] public int nextWayPoint;
    [HideInInspector] public Transform chaseTarget;
    [HideInInspector] public float stateTimeElapsed;

    private bool aiActive = true;
    private bool isDead = false;

    void Awake()
    {
        navMeshAgent = GetComponent<NavMeshAgent>();
    }
    void Start()
    {

    }
    public void SetupAI(bool aiActivationFromTankManager, List<Transform> wayPointsFromTankManager)
    {
        wayPointList = wayPointsFromTankManager;
        aiActive = aiActivationFromTankManager;
        if (aiActive)
        {
            navMeshAgent.enabled = true;
        }
        else
        {
            navMeshAgent.enabled = false;
        }
    }
    private void Update()
    {
        if (!aiActive)
        {
            return;
        }
        currentState.UpdateState(this);
    }
    private void OnDrawGizmos()
    {
        Gizmos.color = currentState.sceneGizmoColor;
        Gizmos.DrawWireSphere(eyes.position, enemyStats.lookSphereCastRadius);
    }

    public void TransitionToState(State nextState)
    {
        if (nextState != remainState)
        {
            currentState = nextState;
            OnExitState();
        }
    }

    public bool CheckIfCountDownElapsed(float duration)
    {
        stateTimeElapsed += Time.deltaTime;
        return stateTimeElapsed >= duration;
    }

    public void OnExitState()
    {
        stateTimeElapsed = 0;
    }
    public void SetTarget(Transform t)
    {
        chaseTarget = t;
    }
    public void Damage()
    {
        HP -= 10;
    }
    public void AIDead()
    {
        if (isDead)
            return;
        isDead = true;
        aiActive = false;
        this.SendMessage("Dead1");
        this.SendMessageUpwards("OneMoreEnemyDead");
        this.GetComponent<Collider>().enabled = false;
    }
}

然後是定義狀態的部分State,其中包含一個每幀執行的UpdateState,運行兩個函數:

1執行當前N個Action動作

2執行N個Decision轉換檢測

using UnityEngine;
[CreateAssetMenu(menuName = "PluggableAI/State")]
public sealed class State : ScriptableObject
{
    public Action[] action;
    public Transition[] transitions;
    public Color sceneGizmoColor = Color.grey;

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

    private void DoActions(StateController controller)
    {
        for (int i = 0; i < action.Length; i++)
        {
            action[i].Act(controller);
        }
    }
    private void CheckTransitions(StateController controller)
    {
        for (int i = 0; i < transitions.Length; i++)
        {
            bool decisionSucceeded = transitions[i].decision.Decide(controller);
            if (decisionSucceeded)
                controller.TransitionToState(transitions[i].trueState);
            else
                controller.TransitionToState(transitions[i].falseState);
        }
    }
}

定義Action和Decision

using UnityEngine;

public abstract class Action : ScriptableObject {
public abstract void Act(StateController controller);
}
using UnityEngine;
[CreateAssetMenu(menuName = "PluggableAI/Actions/Chase")]
public class ChaseAction : Action
{
    private float chaseStopDis = 5f;
    private float QuaterSlerpParam= 0.1f;
    public override void Act(StateController controller)
    {
        Chase(controller);
    }
    public void Chase(StateController controller)
    {
        if(controller.chaseTarget == null)return;
        // if (Vector3.Distance(controller.transform.position, controller.chaseTarget.position) >= chaseStopDis)
        // {
        //     controller.navMeshAgent.destination = controller.chaseTarget.position;
        //     controller.navMeshAgent.isStopped = false;
        // }
        if (controller.navMeshAgent.remainingDistance <= chaseStopDis)
        {
            if (Vector3.Distance(controller.transform.position, controller.chaseTarget.position) <= chaseStopDis)
            {
                UnityEngine.AI.NavMeshHit hit;
                if (!controller.navMeshAgent.Raycast(controller.chaseTarget.position, out hit))
                {
                    Debug.DrawLine(controller.transform.position, controller.chaseTarget.position, Color.white);
                    // Target is "visible" from agent position.
                    controller.navMeshAgent.isStopped = true;
                    // 目標Rotation  
                    Quaternion t = Quaternion.LookRotation((controller.chaseTarget.position - controller.transform.position), Vector3.up);
                    // 進行旋轉  
                    controller.transform.rotation = Quaternion.Slerp(controller.transform.rotation, t, QuaterSlerpParam);
                    return;
                }
            }
        }
        Debug.DrawLine(controller.transform.position, controller.chaseTarget.position, Color.green);
        controller.navMeshAgent.destination = controller.chaseTarget.position;
        controller.navMeshAgent.isStopped = false;
    }
}
using UnityEngine;

public abstract class Decision : ScriptableObject 
{
	public abstract bool Decide(StateController controller);
}
using UnityEngine;
[CreateAssetMenu(menuName = "PluggableAI/Decision/LookDecision")]
public class LookDecision : Decision 
{
	public override bool Decide(StateController controller)
	{
		return Look(controller);
	}

	private bool Look(StateController controller)
	{
		RaycastHit hit;

		Debug.DrawRay(controller.eyes.position,controller.eyes.forward*controller.enemyStats.lookRange,Color.green);

		if(Physics.SphereCast(controller.eyes.position,controller.enemyStats.lookSphereCastRadius,
		controller.eyes.forward,out hit,controller.enemyStats.lookRange)
		&&hit.collider.CompareTag("Player"))
		{
			controller.chaseTarget = hit.transform;
			return true;
		}
		return false;
	}
}

這裏稍微解釋一下,首先AI一上來沒有動作,執行一個觀察檢測,通過射線檢測獲得玩家目標,一旦獲得玩家目標,就會進入到追蹤玩家的狀態。

這裏設計三種不同的AI

巡邏車AI

初始狀態按照規定路線巡邏,發現玩家後持續追蹤和攻擊,玩家離開一定範圍後回到原始位置。

飛行器AI

初始狀態隨機路線巡邏,發現玩家後持續追蹤。

炮塔AI

初始在射程範圍內檢測玩家,檢測到之後進行攻擊,玩家離開範圍後停止攻擊。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章