使用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

初始在射程范围内检测玩家,检测到之后进行攻击,玩家离开范围后停止攻击。

猜你喜欢

转载自blog.csdn.net/GameObject14715/article/details/82720988