FSM有限状态机(AI 小案例)

作者: _凉笙 | 来源:发表于2017-11-09 17:16 被阅读185次

    Wiki官网:http://wiki.unity3d.com/index.php/Main_Page

    FSM源码地址:http://wiki.unity3d.com/index.php/Finite_State_Machine

    image.png

    在这里我以一个Demo为例演示有限状态机的作用,首先我这里要做的是一个NPC AI的巡逻功能,在这里NPC会有两个状态,一个状态是巡逻的功能,第二个状态是追逐主角的功能,我这里让NPC距离主角如果大于10的距离的时候让NPC进入巡逻状态,而当NPC距离主角如果小于5的时候,我让NPC进入追逐主角的状态。
    下面自己来写一个FSM,首先创建一个脚本FSMState,写入一下代码,里面包含一些添加和删除判断转换条件的方法

    using System.Collections.Generic;
    using UnityEngine;
    
    //有哪些状态转换的条件
    public enum Transition
    {
        NullTransition=0,
        SawPlayer,//看到主角
        LostPlayer//看不到主角  
    }
    //状态ID,是每一个状态的唯一标识,每一个状态有一个StateID,而且跟其他的状态不可以重复
    public enum StateID
    {
        NullStateID=0,
        Patrol,//巡逻状态
        Chase//追主角状态
    
    
    }
    
    public abstract class FSMState {
        protected StateID stateID;
    
        public StateID ID { get { return stateID; } }
    
        //存储在不同的条件下可以转换到什么样的状态
        protected Dictionary<Transition, StateID> map = new Dictionary<Transition, StateID>();
    
        public  FSMSystem fsm;
    
        //添加状态转换条件
        public void AddTransition(Transition trans, StateID id)//表示由Transition这个条件可以装换到哪个StateID状态
        {
            if (trans == Transition.NullTransition || id == StateID.NullStateID)
            {
                //转换条件或者状态为空
                Debug.LogError("Transition or stateid is null!!!");//输出错误
                return;
            }
            //如果已经包含了这个状态
            if (map.ContainsKey(trans))
            {
                //当前状态已经存在
                Debug.LogError("State" + id + " is already has transition" + trans);
                return;
            }
            map.Add(trans, id);
        }
        //删除状态转换条件
        public void DeleteTransition(Transition trans)
        {
            //不包含这个trans转换条件
            if (map.ContainsKey(trans) == false)
            {
                //我们想删除的条件不存在
                Debug.LogWarning("The transition " + trans + "you want to delete is not exit in map!!!");
                return;
            }
            map.Remove(trans);
        }
    
        //根据传递过来的转换条件,判断是否可发生转换
        public StateID GetOutState(Transition trans)
        {
            //如果包含这个转换条件返回这个状态的StateID否则返回一个空的StateID
            if (map.ContainsKey(trans))
            {
                return map[trans];
            }
            return StateID.NullStateID;
        }
        //在进入当前状态之前,需要做的事情  初始化
        public virtual void DoBeforeEntering(){ }   
    
        //在离开当前状态的时候,需要做的事情  清理
        public virtual void DoBeforeLeaving() { }
    
        //在状态机处于当前状态的时候,会一直调用
        public abstract void DoUpdata();
    }
    

    接着是创建脚本FSMSystem,这是一个状态机管理类,有限状态机的系统管理类,写入一下代码

    using System.Collections.Generic;
    using UnityEngine;
    
    
    /// <summary>
    ///状态机管理类,有限状态机系统管理类
    /// </summary>
    public class FSMSystem  {
        //当前状态机下有哪些状态u
        private Dictionary<StateID, FSMState> states;
    
        //状态机处于什么状态
        private FSMState currentState;
    
        public FSMState CurentState
        {
            get { return currentState; }
        }
        public FSMSystem()
        {
            states = new Dictionary<StateID, FSMState>();
        }
        //添加状态
        public void AddState(FSMState state)
        {
            if (state==null)
            {
                Debug.LogError("The state you went to add is null");return;
    
            }
            if (states.ContainsKey(state.ID))
            {
                Debug.LogError("The state "+state.ID+" you want to add has already been added.");return;
            }
            state.fsm = this;
            states.Add(state.ID,state);
            
        }
        //从状态机移除状态
    
        public void DeleteState(FSMState state)
        {
            if (state == null)
            {
                Debug.LogError("The state you went to Delete is null"); return;
    
            }
            if (states.ContainsKey(state.ID)==false)
            {
                Debug.LogError("The state " + state.ID + " you want to Delete is not exit."); return;
            }
            states.Remove(state.ID);
        }
    
        //控制状态之间的转换
        public void PorformTransition(Transition trans)
        {
            if (trans==Transition.NullTransition)
            {
                Debug.LogError("NullTransition is not allowed for a real transition.");
            }
          StateID id =  currentState.GetOutState(trans);//判断发生这个条件的时候是否可发生转换
            if (id == StateID.NullStateID)
            {
                Debug.Log("Transition is not tobe happend!没有符合条件的转换");
                return;
            }
            FSMState state;
            states.TryGetValue(id, out state);
            currentState.DoBeforeLeaving();
            currentState = state;
            currentState.DoBeforeEntering();
        }
        //设置默认状态 启动状态机
        public void Start(StateID id)
        {
            FSMState state;
            bool isGet= states.TryGetValue(id, out state);
            if (isGet)
            {
                state.DoBeforeEntering();
                currentState = state;
    
            }
            else
            {
                Debug.LogError("The state"+id+"is not exit in the fsm.");
            }
        }
    
    }
    

    接着是NPC的控制管理,里面写入一些初始化方法,在里面添加两个状态的转换条件和状态,我这里有两个状态,一个是巡逻的状态,一个是追主角的状态.

    using UnityEngine;
    
    public class NPCController : MonoBehaviour {
    
        private FSMSystem fsm;
    
        public Transform[] waypoints;
        private GameObject player;
    
        // Use this for initialization
        void Start () {
            player = GameObject.FindGameObjectWithTag("Player").gameObject;
            InitFSM();
    
        }
    
        //初始化状态机  添加转换条件和状态
        void InitFSM()
        {
            fsm = new FSMSystem();
            PatrolState patrolState = new PatrolState(waypoints,this.gameObject, player);
            //添加转换条件
            patrolState.AddTransition(Transition.SawPlayer,StateID.Chase);//看到主角的时候转换到追主角状态
    
            ChaseState chaseState = new ChaseState(this.gameObject,player);
    
            chaseState.AddTransition(Transition.LostPlayer, StateID.Patrol);//没有看到主角的时候转换到巡逻状态
    
            //添加状态
            fsm.AddState(patrolState);
            fsm.AddState(chaseState);
    
            fsm.Start(StateID.Patrol);//设置默认状态为巡逻状态                                   
        }
    
        private void Update()
        {
            fsm.CurentState.DoUpdata();
        }
    }
    

    最后就是两个状态类了,里面写入控制的逻辑,当主角大于NPC10的位置时候,NPC就会在巡逻,进入巡逻状态,当主角位置小于NPC位置5的时候NPC就会追逐主角,进入追逐状态.这两个状态类脚本我分别命名为ChaseState(追逐状态)和PatrolState(巡逻状态),下面直接放上代码

    using UnityEngine;
    
    public class PatrolState : FSMState {
    
        private int targetWaypoint;
        private Transform[] waypoints;
        private GameObject npc;
        private Rigidbody npcRgb;
        private GameObject player;
    
        public PatrolState(Transform[] wp,GameObject npc,GameObject player)
        {
            stateID = StateID.Patrol;
            waypoints = wp;
            targetWaypoint = 0;
            this.npc = npc;
            this.player = player;
            npcRgb = npc.GetComponent<Rigidbody>();
        }
        public override void DoBeforeEntering()
        {
            Debug.Log("Enting state "+ID );
        }
    
        
        public override void DoUpdata()
        {
            CheckTransition();
            patrolMove();
        }
        //检查转换条件
        private void CheckTransition()
        {
            if (Vector3.Distance(player.transform.position, npc.transform.position) < 5)
            {
                fsm.PorformTransition(Transition.SawPlayer);
            }
        }
        //巡逻功能
        private void  patrolMove()
        {
    
            npcRgb.velocity = npc.transform.forward * 10;
            Transform targetTrans = waypoints[targetWaypoint];
            Vector3 tagetPosition = targetTrans.position;
            tagetPosition.y = npc.transform.position.y;
            npc.transform.LookAt(tagetPosition);
            if (Vector3.Distance(npc.transform.position, tagetPosition) < 1)
            {
                targetWaypoint++;
                targetWaypoint %= waypoints.Length;
            }
    
        }
    
    using UnityEngine;
    
    public class ChaseState : FSMState
    {
        private GameObject npc;
        private Rigidbody npcRgb;
        private GameObject player;
    
        public ChaseState(GameObject npc,GameObject player)
        {
            stateID = StateID.Chase;
            this.npc = npc;
            npcRgb = npc.GetComponent<Rigidbody>();
            this.player = player;
        }
        public override void DoBeforeEntering()
        {
            Debug.Log("Enting state " + ID);
        }
    
        public override void DoUpdata()
        {
            CheckTransition();
            ChanseMove();
         
        }
        private void CheckTransition()
    
        {
            if (Vector3.Distance(player.transform.position, npc.transform.position) > 10)
            {
                fsm.PorformTransition(Transition.LostPlayer);
            }
        }
        //追主角的运动
        private void ChanseMove()
        {
            npcRgb.velocity = npc.transform.forward * 10;
            Vector3 targetposition = player.transform.position;
            targetposition.y = npc.transform.position.y;
            npc.transform.LookAt(targetposition);
        }
      
    }
    
    最后在Unity里面创建Player和NPC,我这里以Capsule代替,然后NPC上挂上Rigidbody刚体组件和NPCController脚本,然后再创建四个位置。表示巡逻的目标点。再将这四个目标点赋值给NPCController脚本里面,这样就可以运行了。 image.png 点4分13秒.gif

    相关文章

      网友评论

        本文标题:FSM有限状态机(AI 小案例)

        本文链接:https://www.haomeiwen.com/subject/rjrmmxtx.html