FSM有限状态机学习及Unity3D案例讲解

引言:近日忙于毕业论文,今天看到涨了3个粉丝,不甚惊喜。遂今日更新FSM有限状态机学习,希望大家共同进步!
开发版本:Unity 2017.1.1f1、VS 2017
适合人群:初学Unity者

一.有限状态机定义

    有限状态机(英文:Finite State Machine,简称FSM),是指在不同阶段呈现不同的运行状态的系统,这些状态是有限的,不重叠的。这样的系统在某一时刻一定会处于其中所有状态中的一个状态,此时它接收一部分的输入,产生相应的响应,并且迁移到可能的状态。
    一般有限状态机有一下几种类型的动作:
    1.进入动作:进入当前状态时执行
    2.退出动作:退出当前状态时执行
    3.输入动作:在当前状态时执行
    4.转移动作:在进行特定切换状态时执行
    有限状态机提供了描述和控制应用逻辑的强大方法,可以应用于控制NPC行为,图形界面管理等方面。FSM可以使得各个状态之间相互独立,互不影响,避免了状态与状态之间的耦合度,具有规则简单,可读性和可验证性等优点。

二.整理思路

简单的有限状态机就是switch语句,但如果状态过多的情况下,switch会使得代码显得臃肿,可扩展性很低。
假设敌人有四种状态,Idle、Patrol、Chase、Attack,switch实现方法如下:
public enum States
{
    Idle,//空闲状态
    Patrol,//巡逻状态
    Chase,//追逐状态
    Attack//攻击状态
}

public class SimpleFSM : MonoBehaviour
{
    private States currentState = States.Idle;

    private void Update()
    {
        switch (currentState)
        {
            case States.Idle:
                //TODO 空闲状态动作
                break;
            case States.Patrol:
                //TODO 巡逻状态动作
                break;
            case States.Chase:
                //TODO 追逐状态动作
                break;
            case States.Attack:
                //TODO 攻击状态动作
                break;
        }
    }
}

接下来整理“高级的”有限状态机实现方法,FSM由状态类和管理类组成,先创建一个状态基类FSMState,用于实现状态类的基本方法,每添加一种状态,就作为它的子类。然后,用一个管理类FSMSystem把所有状态管理起来,方便使用状态机。FSM类图如下所示:


三.实现代码

1.定义枚举类型

public enum StateID
{
    NullStateID,
}
public enum Transition
{
    NullTransition
}

定义每一种状态的ID和状态之间的转换条件

2.FSMState基类

public abstract class FSMState
{
    protected StateID stateID;
    public StateID ID { get { return this.stateID; } }
    protected Dictionary<Transition, StateID> map = new Dictionary<Transition, StateID>();
    protected FSMSystem fsm;

    public FSMState(FSMSystem fsm)
    {
        this.fsm = fsm;
    }

    /// <summary>
    /// 添加转换条件
    /// </summary>
    /// <param name="trans">转换条件</param>
    /// <param name="stateID">转换的目标状态</param>
    public void AddTransition(Transition trans, StateID stateID)
    {
        if (trans == Transition.NullTransition)
        {
            Debug.LogError(trans + "为空,转换条件不允许为空");return;
        }
        if (stateID == StateID.NullStateID)
        {
            Debug.LogError(stateID + "为空,状态ID不允许为空"); return;
        }
        if (map.ContainsKey(trans))
        {
            Debug.LogError(trans + "已经存在,请查看该转换条件是否正确");
        }
        else
        {
            map.Add(trans, stateID);
        }
    }

    /// <summary>
    /// 删除转换条件
    /// </summary>
    /// <param name="trans">需删除的转换条件</param>
    public void DeleteTransition(Transition trans)
    {
        if (trans == Transition.NullTransition)
        {
            Debug.LogError(trans + "为空,转换条件不允许为空"); return;
        }
        if (map.ContainsKey(trans) == false)
        {
            Debug.LogError(trans + "不存在,请查看该转换条件是否正确"); 
        }
        else
        {
            map.Remove(trans);
        }
    }

    /// <summary>
    /// 通过转换条件,得到目标状态
    /// </summary>
    /// <param name="trans">转换条件</param>
    /// <returns>返回目标状态</returns>
    public StateID GetTargetStateID(Transition trans)  
    {
        if (map.ContainsKey(trans) == false)
        {
            Debug.LogError(trans + "不存在,请查看该转换条件是否正确");
            return StateID.NullStateID;
        }
        else
        {
            return map[trans];
        }
    }

    public virtual void DoBeforeEntering() { }//进入动作
    public virtual void DoAfterLeaving() { }//离开动作
    public abstract void Act();//输入动作
    public abstract void Reason();//转移动作
}

状态类主要存储转换条件和转换状态的字典,可以添加删除转换条件,根据条件返回对应的状态,并定义状态类的四种动作。

3.FSMSystem管理类

public class FSMSystem
{
    private Dictionary<StateID, FSMState> states = new Dictionary<StateID, FSMState>();
    private FSMState currentState;

    /// <summary>
    /// 更新当前状态行为
    /// </summary>
    public void UpdateFSM()
    {
        currentState.Act();
        currentState.Reason();
    }

    /// <summary>
    /// 添加状态
    /// </summary>
    /// <param name="state">需管理的状态</param>
    public void AddState(FSMState state)
    {
        if (state == null)
        {
            Debug.LogError(state + "为空"); return;
        }
        if (currentState == null)
        {
            currentState = state;
        }
        if (states.ContainsValue(state))
        {
            Debug.LogError(state + "已经存在");
        }
        else
        {
            states.Add(state.ID, state);
        }
    }

    /// <summary>
    /// 删除状态
    /// </summary>
    /// <param name="id">需要删除状态的ID</param>
    /// /// <returns>删除成功返回true,否则返回false</returns>
    public bool DeleteState(StateID id)
    {
        if (id == StateID.NullStateID)
        {
            Debug.LogError(id + "为空");
            return false;
        }
        if (states.ContainsKey(id) == false)
        {
            Debug.LogError(id + "不存在");
            return false;
        }
        else
        {
            states.Remove(id);
            return true;
        }
    }

    /// <summary>
    /// 执行转换
    /// </summary>
    /// <param name="trans">转换条件</param>
    public void PerformTransition(Transition trans)
    {
        if (trans == Transition.NullTransition)
        {
            Debug.LogError(trans + "为空");return;
        }
        StateID targetID = currentState.GetTargetStateID(trans);
        if (states.ContainsKey(targetID) == false)
        {
            Debug.LogError(targetID + "不存在");return;
        }
        FSMState targetState = states[targetID];
        currentState.DoAfterLeaving();
        targetState.DoBeforeEntering();
        currentState = targetState;
    }
}

FSMSystem用来管理各个状态,字典存储ID和对应的状态,可以添加删除状态,并执行状态转换。

四.案例讲解

假设敌人按照路线巡逻,当发现玩家的时候,开始追逐。但玩家跑出追击范围的时候,敌人继续回去巡逻。

项目的源文件在文末,有需要的童鞋可以自行下载。

敌人巡逻效果如下:


敌人发现玩家开始追逐,当玩家逃离后,返回继续巡逻,效果如下:



分析此时敌人状态有两种,巡逻和追逐,转换条件也对应有两种

public enum StateID
{
    NullStateID,
    PatrolState,
    ChaseState
}
public enum Transition
{
    NullTransition,
    FindPlayer,
    LosePlayer
}

创建两个子类PatrolState和ChaseState都继承自FSMState,分别用来实现巡逻和追逐的状态行为。

public class PatrolState : FSMState
{
    private Transform enemy;
    private Transform player;
    private List<Transform> pathsList = new List<Transform>();
    private int index = 0;

    public float smooth = 3;

    public PatrolState(FSMSystem fsm) : base(fsm)
    {
        stateID = StateID.PatrolState;
        enemy = GameObject.FindWithTag("Enemy").transform;
        player = GameObject.FindWithTag("Player").transform;
        Transform pathRoot = GameObject.Find("PathRoot").transform;
        foreach (Transform pathPoint in pathRoot)
        {
            pathsList.Add(pathPoint);
        }
    }

    public override void DoBeforeEntering()
    {
        Debug.Log("敌人开始巡逻了!");
    }

    public override void DoAfterLeaving()
    {
        Debug.Log("敌人发现玩家,结束巡逻!");
    }

    public override void Act()
    {
        Vector3 forward = pathsList[index].position - enemy.position;
        forward = new Vector3(forward.x, 0, forward.z);
        Quaternion targetQuaternion = Quaternion.LookRotation(forward, Vector3.up);
        enemy.rotation = Quaternion.Slerp(enemy.rotation, targetQuaternion, Time.deltaTime * smooth);
        enemy.Translate(Vector3.forward * Time.deltaTime * smooth);
        if (Vector3.Distance(enemy.position, pathsList[index].position) < 2f)
        {
            index++;
            index %= pathsList.Count;
        }
    }

    public override void Reason()
    {
        if (Vector3.Distance(enemy.position, player.position) < 4)
        {
            fsm.PerformTransition(Transition.FindPlayer);
        }
    }
}
public class ChaseState : FSMState
{
    private Transform enemy;
    private Transform player;
    public float smooth = 3;
    public float chaseSpeed = 5;

    public ChaseState(FSMSystem fsm):base(fsm)
    {
        stateID = StateID.ChaseState;
        enemy = GameObject.FindWithTag("Enemy").transform;
        player = GameObject.FindWithTag("Player").transform;
    }

    public override void DoBeforeEntering()
    {
        Debug.Log("敌人开始追逐玩家了!");
    }

    public override void DoAfterLeaving()
    {
        Debug.Log("敌人跟丢玩家,继续巡逻了!");
    }

    public override void Act()
    {
        Vector3 forward = player.position - enemy.position;
        forward = new Vector3(forward.x, 0, forward.z);
        Quaternion targetQuaternion = Quaternion.LookRotation(forward, Vector3.up);
        enemy.rotation = Quaternion.Slerp(enemy.rotation, targetQuaternion, Time.deltaTime * smooth);
        enemy.Translate(Vector3.forward * Time.deltaTime * chaseSpeed);  
    }

    public override void Reason()
    {
        if (Vector3.Distance(enemy.position, player.position) > 8)
        {
            fsm.PerformTransition(Transition.LosePlayer);
        }
    }
}

在Enemy类中,实例化FSMSystem对象,添加巡逻和追逐状态,还有之间的转换条件

public class Enemy : MonoBehaviour
{
    private FSMSystem fsm;

    private void Start()
    {
        fsm = new FSMSystem();
        FSMState patrolState = new PatrolState(fsm);
        FSMState chaseState = new ChaseState(fsm);
        fsm.AddState(patrolState);
        fsm.AddState(chaseState);
        patrolState.AddTransition(Transition.FindPlayer, StateID.ChaseState);
        chaseState.AddTransition(Transition.LosePlayer, StateID.PatrolState);
    }

    private void Update()
    {
        fsm.UpdateFSM();
    }
}


FSM有限状态机除了实现敌人的行为外,还可以用来管理UI界面的切换,使用方式基本相似。如果大神发现我分享的学习记录中有错误,还请不吝赐教。

案例百度云链接:链接:https://pan.baidu.com/s/1XYhmn3i9kwskai8_MEtAvg 密码:jchj

觉得我分享的学习记录不错的,点赞关注我哦!


猜你喜欢

转载自blog.csdn.net/qq_35361471/article/details/79847734