Unity FSM有限状态机

来源:互联网 发布:收淘宝店铺拿去干嘛 编辑:程序博客网 时间:2024/06/12 10:12

关于人物或敌人的多种状态之间的切换,我们平常的做法就是定义个枚举,把所需的状态放在枚举里面,我们可能要在项目很多地方调用switch方法或者是设置人物状态。当状态比较多的时候,对状态的管理和获取也会变得很麻烦。所以这个时候,我们需要一个有限状态机来统一管理我们的状态(state)。

有限状态机包括两个部分:(1)状态集 FSMState,(2)状态管理机 FSMSystem.

一. 状态集FSMState代表一组状态的集合,一个游戏中可能有多个状态集,所以我们再设计FSMState时最好将它设计为抽象类,这样就能通过继承FSMState拥有一些公共的属性和方法,同时又有自己特定的状态。

二. 一个状态管理机主要的功能有这两点:

1.能够添加和删除状态集(FSMState).

2.能够切换和获取 某个状态集(FSMState)当前的状态(state).

我们还是用个小例子来解析一下:场景有两个物体,一个NPC,一个Player,NPC平时就是巡逻的状态,当Player距离NPC多近时NPC开始追逐Player,超过多远之后就继续巡逻。

下面上脚本:

状态机脚本

using System.Collections;using System.Collections.Generic;using UnityEngine;/// <summary>/// 为过度加入枚举标签,对应相应的状态/// </summary>public enum Transition{    NullTransition=0,              //用这个过渡来代表你的系统中不存在的状态    SawPlayer,                     //这里是NPC的两个过渡    LostPlayer}/// <summary>/// 为状态加入枚举标签/// </summary>public enum StateID{    NullStateID=0,    ChasingPlayer,              //为配合NPC添加两个状态          FollowingPath               }/// <summary>/// 状态类/// </summary>public abstract class FSMState       {    protected Dictionary<Transition, StateID> map = new Dictionary<Transition, StateID>();    /// <summary>    /// 为每一个子类初始化当前的状态    /// </summary>    protected StateID stateID;    public StateID ID    {        get { return stateID; }    }    /// <summary>    /// 添加过渡    /// </summary>    /// <param name="trans"></param>    /// <param name="id"></param>    public void AddTransition(Transition trans,StateID id)      {        //验证每个参数是否合法        if(trans==Transition.NullTransition)        {            Debug.LogError("FSMState error:NullTransition is not allowed for a real transition");            return;        }        if(id==StateID.NullStateID)        {            Debug.LogError("FSMState error:NullStateID is not allowed for a real stateID");            return;        }        //要知道这是一个确定的有限状态机(每个过渡对应一种状态,不能产生分支)        //检查当前的过渡是否已经在字典当中了        if(map.ContainsKey(trans))        {            Debug.LogError("FSMState error:There already has transition " + trans.ToString() + "check for another");            return;        }        map.Add(trans, id);    }    /// <summary>    /// 这个方法在状态地图中删除transition-stateid对    /// 如果过渡不存在地图中将打印一个错误    /// </summary>    /// <param name="trans"></param>    public void DeleteTransition(Transition trans)    {        //check for NullTransition        if(trans==Transition.NullTransition)        {            Debug.LogError("FSMState error:NullTransition is not allowed");            return;        }        //删除之前确认键值对是否存在于状态地图中        if(map.ContainsKey(trans))        {            map.Remove(trans);            return;        }        Debug.LogError("FSMState error:Transition " + trans.ToString() + "passed to " + stateID.ToString() + "was not exist");    }    /// <summary>    /// 该方法在该状态接收到一个过渡时返回状态机需要成为的新状态    /// </summary>    /// <param name="trans"></param>    /// <returns></returns>    public StateID GetOutputState(Transition trans)    {        //检查一下map状态字典中是否存在这个过渡        if(map.ContainsKey(trans))        {            return map[trans];        }        return StateID.NullStateID;    }/// <summary>    /// 这个方法用来设立进入状态前的条件    /// 在状态机分配它到当前状态之前他会被自动调用    /// </summary>public virtual void DoBeforeEntering() { }    /// <summary>    /// 这个方法用来让一切都是必要的,例如在有限状态机变化到另一个时重置变量    /// 在状态机切换到新的状态之前它会被自动调用    /// </summary>    public virtual void DoBeforeLeaving() { }    /// <summary>    /// 要切换状态的条件,原因    /// NPC是被该类约束下对象的一个引用    /// </summary>    /// <param name="player"></param>    /// <param name="npc"></param>    public abstract void Reason(GameObject player, GameObject npc);    /// <summary>    /// 切换状态后对应的行动    /// 表现->该方法用来控制NPC在游戏世界中的行为    /// NPC的任何动作,移动或者交流都需要放置在这 ,    /// NPC是被该类约束下对象的一个引用    /// </summary>    /// <param name="player"></param>    /// <param name="npc"></param>    public abstract void Act(GameObject player, GameObject npc);}/// <summary>/// 该类便是有限状态机类/// 它持有NPC的状态集合,并且有添加,删除状态的方法,以及改变当前正在执行的状态/// </summary>public class FSMSystem{    private List<FSMState> states;    //通过预装一个过渡的唯一方式来改变状态机的状态    //不要直接改变当前的状态    /// <summary>    /// 当前的状态ID    /// </summary>    private StateID currentStateID;    /// <summary>    /// 当前的状态类信息    /// </summary>    private FSMState currentFSMState;    public StateID CurrentStateID    {        get        {            return currentStateID;        }    }    public FSMState CurrentFSMState    {        get        {            return currentFSMState;        }    }    public FSMSystem()    {        states = new List<FSMState>();    }    /// <summary>    /// 这个方法为有限状态机置入新的状态    /// 或者在改状态已经存在列表时打印错误信息    /// 第一个添加的状态也是最初的状态    /// </summary>    public void AddState(FSMState s)    {        //添加前检查是否为空        if(s==null)        {            Debug.LogError("FSM error:Null reference is not allowed");        }        //被装在的第一个状态也是初始状态        if(states.Count==0)        {            states.Add(s);            currentFSMState = s;            currentStateID = s.ID;            return;        }        //如果该状态未被添加过,则加入集合        foreach(FSMState state in states)        {            if(state.ID==s.ID)            {                Debug.LogError("FSM error: There already exist " + s.ID.ToString());                return;            }        }        states.Add(s);    }    /// <summary>    /// 删除一个已存在状态机中的状态    /// 在它不存在时打印错误信息    /// </summary>    /// <param name="id"></param>    public void DeleteState(StateID id)    {        //在删除前检测其是否为空        if(id==StateID.NullStateID)        {            Debug.LogError("FSM error:NullStateID is not allowed for a real state");            return;        }        foreach(FSMState state in states)        {            if(state.ID==id)            {                states.Remove(state);                return;            }        }        Debug.LogError("FSM error:Can't delete state " + id.ToString() + ",it was not in list");    }    /// <summary>    /// 该方法基于当前状态和过渡状态是否通过来尝试改变状态机的状态,当当前状态没有目标状态用来过渡时则打印错误信息    /// 切换当前状态到下一个要转换的状态    /// </summary>    /// <param name="trans"></param>    public void PerformTransition(Transition trans)    {        //在改变当前状态前检测NullTransition        if(trans==Transition.NullTransition)        {            Debug.LogError("FSM error:NullTransition is not allowed");            return;        }        //在改变状态前检测当前状态是否可作为过渡的参数        StateID id = currentFSMState.GetOutputState(trans);        if(id==StateID.NullStateID)        {            Debug.LogError("FSM error: State " + currentFSMState.ID.ToString() + "does not allowed");            return;        }        //更新当前的状态机和状态编号        currentStateID = id;        foreach(FSMState state in states)        {            if(state.ID==currentStateID)            {                //在状态变为新状态前执行后处理                currentFSMState.DoBeforeLeaving();                currentFSMState = state;                //在状态可以使用Reason(动机)或者Act(行为)之前为它的决定条件重置它自己                currentFSMState.DoBeforeEntering();                break;            }        }            }}


NPC逻辑与状态集继承脚本:

using System;using System.Collections;using System.Collections.Generic;using UnityEngine;[RequireComponent(typeof(Rigidbody))]public class NPCController : MonoBehaviour{    public GameObject player;    public Transform[] path;    private FSMSystem fsm;    public void SetTransition(Transition t)    {        //该方法用来改变有限状态机的状态,有限状态机基于当前的状态和通过的过渡状态        //如果当前的状态没有用来通过的过度状态,则会抛出错误        fsm.PerformTransition(t);    }    private void Start()    {        MakeFSM();    }    private void FixedUpdate()    {        fsm.CurrentFSMState.Reason(player, gameObject);        fsm.CurrentFSMState.Act(player, gameObject);    }    private void MakeFSM()         //建造状态机    {        FollowPathState follow = new FollowPathState(path);        follow.AddTransition(Transition.SawPlayer, StateID.ChasingPlayer);        ChassPlayerState chass = new ChassPlayerState();        chass.AddTransition(Transition.LostPlayer, StateID.FollowingPath);        fsm = new FSMSystem();        fsm.AddState(follow);        fsm.AddState(chass);    }}public class FollowPathState : FSMState{    private int currentWayPoint;    private Transform[] waypoints;    public FollowPathState(Transform[] wp)      {        waypoints = wp;        currentWayPoint = 0;        stateID = StateID.FollowingPath;    }    public override void DoBeforeEntering()    {        Debug.Log("Followingpath before entering");    }    public override void DoBeforeLeaving()    {        Debug.Log("Followingpath before leaving");    }    //要切换状态的条件    public override void Reason(GameObject player, GameObject npc)    {        RaycastHit hit;        if (Physics.Raycast(npc.transform.position, npc.transform.forward, out hit, 15f))        {            if (hit.transform.gameObject.tag == "Player")            {                npc.GetComponent<NPCController>().SetTransition(Transition.SawPlayer);            }        }    }    //切换状态后的行动    public override void Act(GameObject player, GameObject npc)    {        Vector3 vel = npc.GetComponent<Rigidbody>().velocity;        Vector3 moveDir = waypoints[currentWayPoint].position - npc.transform.position;        if(moveDir.magnitude<1)        {            currentWayPoint++;            if(currentWayPoint>=waypoints.Length)            {                currentWayPoint = 0;            }        }        else        {            vel = moveDir.normalized * 10;            npc.transform.rotation = Quaternion.Slerp(npc.transform.rotation, Quaternion.LookRotation(moveDir), 5 * Time.deltaTime);            npc.transform.eulerAngles = new Vector3(0, npc.transform.eulerAngles.y, 0);        }        npc.GetComponent<Rigidbody>().velocity = vel;    }}public class ChassPlayerState : FSMState{    public ChassPlayerState()    {        stateID = StateID.ChasingPlayer;    }    public override void DoBeforeEntering()    {        Debug.Log("Followingpath before entering");    }    public override void DoBeforeLeaving()    {        Debug.Log("Followingpath before leaving");    }    //要切换状态的原因,条件    public override void Reason(GameObject player, GameObject npc)                 {        if (Vector3.Distance(npc.transform.position, player.transform.position) > 3)        {            npc.GetComponent<NPCController>().SetTransition(Transition.LostPlayer);        }    }    //切换状态后的行动    public override void Act(GameObject player, GameObject npc)    {        Vector3 vel = npc.GetComponent<Rigidbody>().velocity;        Vector3 moveDir = player.transform.position - npc.transform.position;        npc.transform.rotation = Quaternion.Slerp(npc.transform.rotation, Quaternion.LookRotation(moveDir), 5 * Time.deltaTime);        npc.transform.eulerAngles = new Vector3(0, npc.transform.eulerAngles.y, 0);        vel = moveDir.normalized * 10;        npc.GetComponent<Rigidbody>().velocity = vel;    }   }

完成.



原创粉丝点击