Unity中的FSM有限状态机

有限状态机一般在unity的运用就是用来制作AI的,相比于行为树制作AI来说的话,有限状态机在制作AI方面会更加灵活,实现的效果会更加多元,但是也会相对行为树插件来说会更加麻烦

有点类似于动画状态机的效果,因此在学习FSM状态机的时候可以参考动画状态机
状态分三个生命周期,1.进入状态  2.执行中 3.结束状态(在FSMState通过三个虚方法来表示)

有限状态机FSM需要有以下几个固定脚本

Transition为状态机转换的条件--Framework
StateID为每个状态机的状态唯一标识码--Framework
FSMState为状态机(父类),其中有状态机的状态(用状态码来标识),还有转换状态条件(用字典存储)--Framework
FSMManager为状态机管理类,其中就用多个状态机,当前执行的状态机和添加删除切换当前执行的状态机等方法--Framework

有了框架部分的内容,后续就是为书写每个状态具体的脚本

Transition脚本

/// <summary>
/// 有哪些状态转换条件
/// </summary>
public enum Transition 
{
    NullTransition=0,
    CanSeeObject,
    CanotSeeObject,
}

 StateID脚本

/// <summary>
/// 状态标识符,每个状态对应一个标识符
/// </summary>
public enum StateID 
{
    NullStateID=0,
    Patrol,
    Chase,
}

 FSMState状态机脚本

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

/// <summary>
/// 抽象状态类,把状态类的相同功能提取出来放在共同抽象父类
/// </summary>
public abstract class FSMState 
{
    protected StateID stateID;
    public StateID StateID { get { return stateID; } }

    //对于当前对象来说,不同转换条件下就会对应不同一种状态
    protected Dictionary<Transition,StateID> stateTransDict = new Dictionary<Transition,StateID>();

    public FSMManager fSMManager;//这里需要指定状态机所属的状态机管理器,方便在子类脚本调用切换状态
    /// <summary>
    /// 添加转换条件
    /// </summary>
    /// <param name="trans">转换条件</param>
    /// <param name="id">转换的状态码</param>
    public void AddTransition(Transition trans,StateID id)
    {
        if(trans==Transition.NullTransition||id==StateID.NullStateID)
        {
            Debug.LogError("Transition or state is null");
            return;
        }
        if(stateTransDict.ContainsKey(trans) ) 
        {
            Debug.LogError($"The Transition: {trans} has existed");
            return;
        }
        stateTransDict.Add(trans,id);    
    }

    /// <summary>
    /// 移除转换条件
    /// </summary>
    /// <param name="trans">要移除的转换条件</param>
    public void RemoveTransition(Transition trans)
    {
        if(!stateTransDict.ContainsKey(trans))
        {
            Debug.LogError($"The Transition: {trans} is not existed");
            return;
        }
        stateTransDict.Remove(trans);
    }

    /// <summary>
    /// 根据传递过来的转换条件,判断是否能返回想要转换的状态
    /// </summary>
    /// <param name="trans">切换条件</param>
    /// <returns></returns>
    public StateID GetState(Transition trans)
    {
        if(stateTransDict.ContainsKey(trans))
        {
            return stateTransDict[trans];
        }
        return StateID.NullStateID;
    }

    /// <summary>
    /// 在进入状态之前,需要做的事
    /// 注意这里是虚方法,和下方的抽象方法不同,子类可以重写该方法(具体差别去看抽象方法和虚方法)
    /// </summary>
    public virtual void DoBeforeEntering()
    {

    }
    /// <summary>
    /// 在离开状态之前需要做的事
    /// </summary>
    public virtual void DoBeforeLeaving()
    {

    }
    /// <summary>
    /// 当状态机出于执行状态时执行的方法
    /// 注意这里是抽象方法,子类中必须实现该方法
    /// </summary>
    public abstract void DoUpdate();
    
}

 FSMManager状态机管理器脚本

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

/// <summary>
/// 状态机管理类
/// </summary>
public class FSMManager 
{
    //当前状态机管理器下面有哪些状态
    private Dictionary<StateID, FSMState> states;
    //状态机当前处于什么状态
    private FSMState currentState;
    public FSMState CurrentState { get => currentState; }

    public FSMManager()
    {
        states=new Dictionary<StateID, FSMState>();
    }

    //往状态机管理器里面添加状态
    public void AddState(FSMState state)
    {
        if(state==null)
        {
            Debug.LogError($"{state} is NULL");
            return;
        }
        if(states.ContainsKey(state.StateID))
        {
            Debug.LogError($"{state} is existed");
            return;
        }
        states.Add(state.StateID, state);
        state.fSMManager = this;
    }
    //删除状态机管理器中的状态
    public void RemoveState(FSMState state)
    {
        if (state == null)
        {
            Debug.LogError($"{state} is NULL");
            return;
        }
        if (!states.ContainsKey(state.StateID))
        {
            Debug.LogError($"{state} is not existed");
            return;
        }
        states.Remove(state.StateID);
    }

    //从当前状态转换到目标状态(参数为转换的条件)
    public void TransState(Transition trans)
    {
        if(trans==Transition.NullTransition)
        {
            Debug.LogError($"{trans} is  null");
            return;
        }
       StateID id= currentState.GetState(trans);//获取到要切换的状态机ID
       if(id==StateID.NullStateID)
        {
            return;
        }
        else
        {
            currentState.DoBeforeLeaving();//切换之前执行的方法
            currentState = states[id];//切换状态
            currentState.DoBeforeEntering();//进入到新的状态机执行的方法
        }
    }
    //启动状态机管理器时当前的默认状态
    public void StartFSMManager(StateID id)
    {
        if (id == StateID.NullStateID)
        {
            Debug.LogError($"{id} is  null");
            return;
        }
        currentState = states[id];
        currentState.DoBeforeLeaving();        
        currentState.DoBeforeEntering();
    }
}

以下脚本就是针对不同的AI行为可以进行不同的脚本控制

控制NPC的脚本,该脚本就是用来控制NPC状态的切换等功能

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

public class NPCControl : MonoBehaviour
{
    private FSMManager fSMManager;//每个角色都应该有个状态机管理器,参考动画状态机

    public Transform[] wayPoints;//NPC巡逻路径点
    public GameObject player;
    // Start is called before the first frame update
    void Start()
    {
        InitFSM();
    }

    void InitFSM()//初始化状态机
    {
        fSMManager = new FSMManager();

        PatrolState patrolState = new PatrolState(wayPoints,this.gameObject,player);//创建NPC的状态
        patrolState.AddTransition(Transition.CanSeeObject,StateID.Chase);//为巡逻状态机添加转换条件,在看见玩家的时候就切换为追逐状态

        ChaseState chaseState = new ChaseState(this.gameObject, player);
        chaseState.AddTransition(Transition.CanotSeeObject, StateID.Patrol);

        fSMManager.AddState(patrolState);//为状态机管理器添加NPC的状态机
        fSMManager.AddState(chaseState);

        fSMManager.StartFSMManager(StateID.Patrol);
    }
    private void Update()
    {
        fSMManager.CurrentState.DoUpdate();//每一帧都会执行当前状态的方法
    }
}

例如NPC追逐目标的AI脚本

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

public class ChaseState : FSMState//追逐状态
{
    private GameObject npc;
    private Rigidbody npcRd;//npc的刚体
    private GameObject player;
    public ChaseState(GameObject npc, GameObject player)
    {
        stateID=StateID.Chase;
        this.npc = npc;
        npcRd = npc.GetComponent<Rigidbody>();
        this.player = player;
    }
    public override void DoBeforeEntering()
    {
        Debug.Log("进入追逐状态机");
    }
    public override void DoUpdate()
    {
        npcRd.velocity= npc.transform.forward * 3;
        Vector3 targetPosition=player.transform.position;
        npc.transform.LookAt(targetPosition);

        CheckTransition();
    }
    private void CheckTransition()//检查转换条件
    {
        if (Vector3.Distance(player.transform.position, npc.transform.position) > 10)
        {
            fSMManager.TransState(Transition.CanotSeeObject);
        }
    }
}

NPC巡逻脚本

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

public class PatrolState : FSMState//巡逻状态
{
    private int targetPointIndex;
    private Transform[] wayPoints;
    private GameObject npc;
    private Rigidbody npcRd;//npc的刚体
    private GameObject player;
    public PatrolState(Transform[] wayPoints, GameObject npc,GameObject player)
    {
        stateID = StateID.Patrol;
        this.wayPoints = wayPoints;
        this.npc = npc;
        npcRd=npc.GetComponent<Rigidbody>();
        this.player = player;
    }
    public override void DoBeforeEntering()
    {
        Debug.Log("进入巡逻状态机");
    }
    public override void DoUpdate()
    {
        Patrol();
        CheckTransition();
    }
    public override void DoBeforeLeaving()
    {
        npcRd.velocity = npc.transform.forward * 0;
    }

    private void CheckTransition()//检查转换条件
    {
        if(Vector3.Distance(player.transform.position,npc.transform.position)<5)
        {
            fSMManager.TransState(Transition.CanSeeObject);
        }
    }
    private void Patrol()//巡逻
    {
        npcRd.velocity = npc.transform.forward * 3;
        Transform targetTrans = wayPoints[targetPointIndex];
        Vector3 targetPosition = targetTrans.position;
        npc.transform.LookAt(targetPosition);
        if (Vector3.Distance(npc.transform.position, targetPosition) < 0.1f)
        {
            targetPointIndex++;
            targetPointIndex %= wayPoints.Length;
        }
    }
}

猜你喜欢

转载自blog.csdn.net/qq_62947569/article/details/135048818
今日推荐