FSM有限状态机学习

一.初识有限状态机

    以我拙见,所谓的有限状态机就是把switch case语法通过管理类+状态类的形式实现,在分支过多的情况下,使用switch case会使代码变得极其臃肿,这显然是我们要避免的,在看了一些资料后,简单的实现并使用有限状态机来写一个AI,虽然AI很适合使用FSM,但是不代表FSM只能用来做AI,实际上在学习完FSM后,立刻就想起过去写的一些代码可以使用FSM优化,比如用FSM来做卡牌游戏的回合判定状态等。

二.整理思路

    上来就写代码很明显是不明智的,第一步应该先理清思路,FSM由状态类和管理类组成,我们可以写一个状态的基类,我们每添加一种状态就会写一个它的子类,然后我们用一个管理类把所有的状态管理起来,这样就可以很方便的使用状态机。思考清楚后我们可以写代码了。

三.编写代码

1.定义枚举

public enum StateId
{
    NullStateId=0,
}
public enum Transtition
{
    NullTranstition=0,
}
每一种状态都应该有自己唯一的标示。

2.FSMState基类

public abstract class FSMState {

    protected StateId stateID;
    public StateId ID
    {
        get { return stateID; }
    }
    protected Dictionary<Transtition, StateId> map = new Dictionary<Transtition, StateId>();
    
    public void AddTranstition(Transtition trans,StateId id)
    {
        if (trans == Transtition.NullTranstition||id==StateId.NullStateId)
        {
            Debug.Log("Transtition or StateId is null");
            return;
        }
        if(map.ContainsKey(trans))
        {
            Debug.Log("Transtition is exist");
            return;
        }
        map.Add(trans, id);            
    }
    public void DeleteTranstition(Transtition trans)
    {
        if(trans==Transtition.NullTranstition)
        {
            Debug.Log("The Transtition is null");
            return;
        }
        if (!map.ContainsKey(trans))
        {
            Debug.Log("The Transtition is not exist");
            return;
        }
        map.Remove(trans);
    }
    public StateId GetNextStateID(Transtition trans)
    {
        if (trans == Transtition.NullTranstition)
        {
            Debug.Log("The Transtition is null");
            return default(StateId);
        }
        StateId stateId;
        if(map.TryGetValue(trans,out stateId))
        {
            return stateId;
        }
        else
        {
            Debug.Log("The Transtition is not exist");
            return default(StateId);
        }
    }
    public virtual void BeforeEnterState(){}
    public virtual void BeforeLeaveState(){}
    public abstract Transtition CheckTranstition();
    public abstract void DoUpdate();
}

不是抽象类的基类不是好基类。状态类中主要存储本状态的ID标示和装换条件和转换状态的对应表,我们一般使用字典来存储。

3.FSMSystem类

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

public class FSMSystem {
    public FSMState currentState;
    private Dictionary<StateId, FSMState> dic;
    public FSMSystem()
    {
        dic=new Dictionary<StateId,FSMState>();
    }
    public void AddState(FSMState state)
    {
        if(state==null)
        {
            Debug.LogError("The state is null");
            return;
        }
        if(dic.ContainsKey(state.ID))
        {
            Debug.LogError("The state is exist");
            return;
        }
        dic.Add(state.ID, state);
    }
    public void DeleteState(StateId id)
    {
        if (id == StateId.NullStateId)
        {
            Debug.LogError("The state you want to delete is null."); return;
        }
        if (!dic.ContainsKey(id))
        {
            Debug.LogError("The state " + id + " you want to delete is not exit."); return;
        }
        dic.Remove(id);
    }
    public void TranslateState(Transtition trans)
    {
        if(trans==Transtition.NullTranstition)
        {
            Debug.LogError("The Transtition is null");
            return;
        }
        StateId id = currentState.GetNextStateID(trans);
        if(id==default(StateId))
        {
            Debug.LogError("Current state is not exist Transtition");
            return;
        }
        FSMState fs;
        if (dic.TryGetValue(id, out fs))
        {
            currentState.BeforeLeaveState();
            currentState = fs;
            currentState.BeforeEnterState();
        }
        else
        {
            Debug.LogError("The FsmSystem is not contain the FsmState.");
            return;
        }        
    }
    public void Start(StateId id)
    {
        FSMState state;
        bool isGet = dic.TryGetValue(id, out state);
        if (isGet)
        {
            state.BeforeEnterState();
            currentState = state;
        }
        else
        {
            Debug.LogError("The state " + id + " is not exit in the fsm.");
        }
    }
    public void DOUpDate()
    {
        Transtition trans=currentState.CheckTranstition();
        if (trans != Transtition.NullTranstition)
            TranslateState(trans);
        currentState.DoUpdate();
    }
}
代码很简单,理清思路后没有什么难理解。

四.FSM的简单使用

我们设计一个简单的敌人AI,敌人围绕着基地在巡逻,当发现玩家的时候,它会切换成追击状态,当玩家跑出它的范围后会切换回巡逻状态。
首先我们添加枚举标示,状态有巡逻和追击两种,触发条件对应发现玩家和超出范围
public enum StateId
{
    NullStateId=0,
    Patrol,
    Pursuit
}
public enum Transtition
{
    NullTranstition=0,
    FindPlayer,
    OutOfRange
}
编写PatrolState和Pursuit
public class PatrolState : FSMState {
    private int targetIndex;
    private GameObject npc;
    private GameObject player;
    private Transform[] wayPoint;
    private Rigidbody rigid;
    public PatrolState(GameObject npc,GameObject player,params Transform[] point)
    {
        stateID = StateId.Patrol;
        targetIndex = 0;
        this.npc = npc;
        this.player = player;
        wayPoint = point;
        rigid = npc.GetComponent<Rigidbody>();
    }
    public override void BeforeEnterState()
    {
        
    }
    public override void BeforeLeaveState()
    {
        
    }
    public override void DoUpdate()
    {
        rigid.velocity = npc.transform.forward * 3;
        Transform targetPosition = wayPoint[targetIndex];
        targetPosition.position = new Vector3( targetPosition.position.x,npc.transform.position.y,targetPosition.position.z);
        npc.transform.LookAt(targetPosition);
        if(Vector3.Distance(npc.transform.position,targetPosition.position)<1)
        {
            targetIndex++;
            targetIndex %= wayPoint.Length;
        }
    }
    public override Transtition CheckTranstition()
    {
        if(Vector3.Distance( npc.transform.position,player.transform.position)<5)
        {
            return Transtition.FindPlayer;
        }
        return Transtition.NullTranstition;
    }
}
要注意的是基类中的抽象方法是必须要在子类中实现的,虚方法可以不重写,会直接使用父类的方法。
public class PursuitState : FSMState {

    private GameObject npc;
    private GameObject player;
    private Rigidbody rigid;
    public PursuitState(GameObject npc,GameObject player)
    {
        stateID = StateId.Pursuit;
        this.npc = npc;
        this.player = player;
        rigid = npc.GetComponent<Rigidbody>();
    }
    public override void BeforeEnterState()
    {
        
    }
    public override void BeforeLeaveState()
    {
        
    }
    public override Transtition CheckTranstition()
    {
        if(Vector3.Distance( npc.transform.position,player.transform.position)>10)
        {
            return Transtition.OutOfRange;
        }
        return Transtition.NullTranstition;
    }

    public override void DoUpdate()
    {
        rigid.velocity = npc.transform.forward * 3;
        Vector3 targetPostion = player.transform.position;
        targetPostion.y = npc.transform.position.y;
        npc.transform.LookAt(targetPostion);
    }
}

编写挂载的AI类,使用FSMSystem
public class NPCAI : MonoBehaviour {

    public Transform[] wayPoint;
    public GameObject player;
    public FSMSystem fsm;

    void Start()
    {
        player = GameObject.FindGameObjectWithTag("Player");
        InitFSM();
    }
    void Update()
    {
        fsm.DOUpDate();
    }
    private void InitFSM()
    {
        fsm = new FSMSystem();
        FSMState patrol = new PatrolState(this.gameObject, player, wayPoint);
        patrol.AddTranstition(Transtition.FindPlayer, StateId.Pursuit);
        fsm.AddState(patrol);
        FSMState pursuit = new PursuitState(this.gameObject, player);
        pursuit.AddTranstition(Transtition.OutOfRange, StateId.Patrol);
        fsm.AddState(pursuit);
        fsm.Start(StateId.Patrol);
    }
}
完成演示




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值