有限状态机

有限状态机中有两个核心类,FSMSystem和FSMState

前者是管理状态,每一个npc身上都要有一个FSMSystem的实例对象。

后者是各种状态的基类,用abstract修饰,由它会派生出其他类,有多少种状态就会派生出多少个类。

另外有两个枚举类型,转换条件Transition和状态编号StateID。StateID的每一个值会对应一个FSMState的一个子类

下面举一个例子,一个npc有三种状态,巡逻,追逐,攻击,所以FSMState会有三个派生类

有四种转换条件,不同的状态下可以触发其中的某些转换条件

//转换状态的条件,看见玩家,失去玩家(跟丢了),靠近玩家,玩家死亡,以及一个空状态
public enum Transition
{
    NullTransition,
    SeePlayer,
    LosePlayer,
    CloseToPlayer,
    PlayerDie
}
//状态编号,巡逻,追逐,攻击,以及一个空编号,三种状态对应FSMState的三个派生类
public enum StateID
{
    NullStateID,
    Patrol,
    Chase,
    Attack
}
/// <summary>
/// 状态基类,抽象类,只能继承不能实例化
/// </summary>
public abstract class FSMState 
{
    protected StateID stateID;                 //每一个状态都会对应一个id

    public StateID iD
    {
        get
        {
            return stateID;
        }
    }

    protected Dictionary<Transition, StateID> map;    //在该状态下能进行哪几种转换,每一个转换条件对应一个id,也就是触发该条件时会转换到哪种状态

    protected FSMSystem fsmSystem;  //一个FSMSystem的引用,表示该状态是由哪个状态机来管理,构造函数中赋值

    public FSMState(FSMSystem system)
    {
        fsmSystem = system;
        map = new Dictionary<Transition, StateID>();
    }

    //给字典添加内容,该状态下能进行哪几种转换,传入每种转换条件以及对应的状态id
    public void AddTransition(Transition trans, StateID id)
    {
        if(trans == Transition.NullTransition || id == StateID.NullStateID)
        {
            Debug.LogError("transition or id is null");
            return;
        }

        if (map == null)
            Debug.Log("kkkkkk");
        if(map.ContainsKey(trans))
        {
            Debug.LogError("transition is already in dictionary");
            return;
        }

        map.Add(trans, id);
    }

    //删除某种转换条件,也就是在该状态下不能再进行这种转换
    public void DeleteTransition(Transition trans)
    {
        if(map.ContainsKey(trans) == false)
        {
            Debug.LogWarning("this transition did not exist");
            return;
        }
        map.Remove(trans);
    }

    //传递过来的转换条件,判断能否发生转换
    public StateID GetStateID(Transition trans)
    {
        if (map.ContainsKey(trans))
            return map[trans];
        return StateID.NullStateID;
    }

    //进状态之前要做的事
    public virtual void DoWhileEntering() { }
    //出状态之前要做的事
    public virtual void DoWhileLeaving() { }
    //状态机处于当前状态时会一直调用
    public abstract void Act();
    //判断转换条件
    public abstract void Reason();
}

npc有三种状态,所以上面的FSMState有三个派生类,PatrolState,ChaseState,AttackState

三个子类都必须重写上面的Act和Reason方法,前者需要放在Update里面每一帧里调用,后者用来检查是否需要转换状态,也是需要每一帧调用,一般可以放在Act方法里面。

DoWhileEntering和DoWhileLeaving可以选择性的重写,因为在进出某个状态时不一定要执行,也可能什么事都不做

/// <summary>
/// 巡逻状态,继承自FSMState
/// </summary>
public class PatrolState : FSMState
{
    private Transform[] transforms;                 //巡逻时的几个目标点
    private GameObject npc;                                   //NPC对象
    private GameObject player;                                  //游戏玩家对象
    private Rigidbody npcRigidbody;                           //NPC身上的刚体
    private int targetPoint = 0;                                             //巡逻目标点的指针

    public PatrolState(Transform[] tr, GameObject obj, GameObject player, FSMSystem fsmSystem) : base (fsmSystem)
    {
        stateID = StateID.Patrol;
        transforms = tr;
        npc = obj;
        npcRigidbody = npc.GetComponent<Rigidbody>();
        this.player = player;
    }

    //这个方法将会放在Update里面每一帧调用,需要做两件事,检查是否要转换状态,以及追逐时的逻辑控制
    public override void Act()
    {
        Reason();
        Patrol();
    }

    //控制追逐时的逻辑
    private void Patrol()
    {
        //控制它的运动,让它一直向自己的正前方运动然后每时每刻控制它的方向
        npcRigidbody.velocity = npc.transform.forward * 3;                       //必须要每帧调用,否则不会动
        Transform targetTr = transforms[targetPoint];
        Vector3 targetVect = targetTr.position;
        npc.transform.LookAt(targetTr);
        if (Vector3.Distance(npc.transform.position, targetVect) < 1)
        {
            targetPoint++;
            targetPoint %= transforms.Length;                 //到了数组最大值时再赋值为零
        }
    }

    //检查状态是否需要发生转换,这里是和玩家距离小于5米时触发”看见玩家“的条件,会换成另一种状态
    public override void Reason()
    {
        if (Vector3.Distance(player.transform.position, npc.transform.position) < 5)
        {
            fsmSystem.PerformTransition(Transition.SeePlayer);
        }
    }
}
/// <summary>
/// 追逐状态,继承自FSMState
/// </summary>
public class ChaseState : FSMState
{
    private GameObject npc;
    private GameObject player;
    private Rigidbody npcRigidbody;

    public ChaseState(GameObject npc, GameObject player, FSMSystem fsmSystem) : base (fsmSystem)
    {
        stateID = StateID.Chase;
        this.npc = npc;
        this.player = player;
        npcRigidbody = npc.GetComponent<Rigidbody>();
    }

    //同理,检查是否需要切换状态以及控制追逐玩家的逻辑
    public override void Act()
    {
        Reason();
        ChasingPlayer();
    }

    public override void Reason()
    {
        if (Vector3.Distance(npc.transform.position, player.transform.position) > 15)
            fsmSystem.PerformTransition(Transition.LosePlayer);
    }

    private void ChasingPlayer()
    {
        npcRigidbody.velocity = npc.transform.forward * 3;
        npc.transform.LookAt(player.transform);
    }
}
/// <summary>
/// 攻击状态,继承自FSMState,这个类这里没有写什么,需要的话可以自行添加
/// </summary>
public class AttackState : FSMState
{
    public AttackState(FSMSystem fsmSystem) : base(fsmSystem)
    {
        stateID = StateID.Attack;
    }

    public override void Act()
    {
        //do something
    }

    public override void Reason()
    {
        //check the transition 
    }
}

上述三个子类,分别控制进出以及处于当前状态时要做的事,不同的情况实现就不一样

下面是管理状态的类

/// <summary>
/// 状态管理,每一个npc身上都会持有一个该类的实例
/// </summary>
public class FSMSystem 
{
    private FSMState currentstate;                        //记录该npc当前状态

    public FSMState currentState
    {
        get
        {
            return currentstate;
        }
    }

    //所有状态的集合字典,该npc的所有能够到达的状态,状态有很多种,并不是每一个npc都能处于任何状态
    //本例的巡逻追逐攻击三种状态比较简单,另外举个例子,动物有走,跑,跳,飞四种状态,但是如果这个npc是一直猫,那就不可能处于飞的状态,所以它的状态集合里就不会出现飞
    private Dictionary<StateID, FSMState> statesDic;

    public FSMSystem()
    {
        statesDic = new Dictionary<StateID, FSMState>();
    }

    //往字典里添加状态
    public void AddState(FSMState state)
    {
        if(state == null)
        {
            Debug.LogError("the state you want to add is null");
            return;
        }
        if(statesDic.ContainsKey(state.iD))
        {
            Debug.LogError("the state you want to add is already exist");
            return;
        }
        statesDic.Add(state.iD, state);
    }

    //删除状态
    public void DeleteState(FSMState state)
    {
        if (state == null)
        {
            Debug.LogError("the state you want to delete is null");
            return;
        }
        if (statesDic.ContainsKey(state.iD) == false)
        {
            Debug.LogError("the state you want to delete is not exist");
            return;
        }
        statesDic.Remove(state.iD);
    }

    /// <summary>
    /// 控制状态的转换
    /// </summary>
    /// <param name="trans">转换条件</param>
    public void PerformTransition(Transition trans)
    {
        //首先判断传入的转换条件是否为空,为空直接返回
        if (trans == Transition.NullTransition)
        {
            Debug.LogError("the transition is null");
            return;
        }

        //不为空的话,查找此转换条件下能转换成什么状态,返回状态的id
        StateID id = currentstate.GetStateID(trans);

        //如果是空id则返回,说明该状态不能进行这种转换
        if (id == StateID.NullStateID)
        {
            Debug.LogError("this transition will not happen");
            return;
        }

        //以上异常情况都排除,在状态集合中找到相应的状态,并赋值
        FSMState state;
        statesDic.TryGetValue(id, out state);
        currentstate.DoWhileLeaving();
        currentstate = state;
        currentstate.DoWhileEntering();
    }

    /// <summary>
    /// 状态机开始工作,也就是赋予该npc一个初状态
    /// </summary>
    /// <param name="id">状态机的id</param>
    public void Start(StateID id)
    {
        if(id == StateID.NullStateID)
        {
            Debug.LogError("id is null");
            return;
        }
        FSMState state;
        if(statesDic.TryGetValue(id, out state))
        {
            currentstate = state;
            state.DoWhileEntering();
            return;
        }
        Debug.LogError("no such state");
    }
}

下面是一个控制npc的脚本,这个需要挂在npc身上

public class NPCcontrol : MonoBehaviour
{
    //一个状态管理类的实例,这里面记录的有这个npc当前处于何种状态
    private FSMSystem fsm;

    public Transform[] transforms;              //巡逻状态时需要用到的几个巡逻点的位置
    public GameObject npc;
    public GameObject player;
    //以上三个变量都是处于某种状态时要用到的,作为参数传递给某个状态

    void Start()
    {
        Init();
    }

    //初始化
    private void Init()
    {
        fsm = new FSMSystem();          //生成一个状态机

        //生成一个巡逻状态的实例,并给追巡逻态添加转换条件,在看见玩家时可以切换到追逐状态
        PatrolState patrolState = new PatrolState(this.transforms, npc, player,fsm);
        patrolState.AddTransition(Transition.SeePlayer, StateID.Chase);

        //生成一个追逐状态实例,并添加转换条件,三种情况下会切换到对应的新状态
        ChaseState chaseState = new ChaseState(npc,player, fsm);
        chaseState.AddTransition(Transition.CloseToPlayer, StateID.Attack);
        chaseState.AddTransition(Transition.LosePlayer, StateID.Patrol);
        chaseState.AddTransition(Transition.PlayerDie, StateID.Patrol);

        //同理,生成攻击状态的实例
        AttackState attackState = new AttackState(fsm);
        attackState.AddTransition(Transition.PlayerDie, StateID.Patrol);
        attackState.AddTransition(Transition.LosePlayer, StateID.Patrol);

        //将上述三种状态添加到状态管理系统的字典里
        fsm.AddState(attackState);
        fsm.AddState(patrolState);
        fsm.AddState(chaseState);

        //给一个初始状态,启动状态机
        fsm.Start(StateID.Patrol);
    }

    
    private void Update()
    {
        fsm.currentState.Act();             //每时每刻调用当前状态下的Act函数
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值