有限状态机的构造和详细解析

有限状态机:
关于有限状态机的定义是状态机在状态发生改变的时候 下一状态不仅和当前状态有关 还输入(在下面我们将的里面就是当前状态里的判断要转换的方法里面所执行的判断)有关。


在游戏中 有限状态机的应用很广泛,不仅实用于所有的角色,怪物中,甚至UI,场景都可以使用有限状态机来进行管理;
有限状态机的设计核心:
有限制状态机设计的时候最核心的原则就是 单一职责原则 和里氏替换原则【不知道这俩的请自行查询】
单一职责就是:
每一个状态都有专门的一个脚本进行处理他的行为;
里氏替换:
所有具体状态类(下面的(OneState TwoState))继承于一个抽象状态类(FSMState);这样 不管是用那个状态实例化的对象,都可以借助基类进行调用其中的方法;

基于以上两点,这种方式才符合开闭原则【不知道 开闭原则 的请自行查询】;

首先我们先构建状态类的基类:FSMState

using System;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 变换条件 枚举
/// </summary>
public enum Transition
{/// <summary>
/// 空的转换条件
/// </summary>
    Null=0,
    /// <summary>
    /// 对应第一个转换条件
    /// </summary>
    One,
    Two
}
public enum StateID
{/// <summary>
/// 空状态
/// </summary>
    Null = 0,
    /// <summary>
    /// 对应第一个装换状态
    /// </summary>
    One,
    Two
}
/// <summary>
/// 状态机的状态类 没一个具体的状态需要继承其 并重写一些函数
/// </summary>
public abstract class FSMState
{
    /// <summary>
    ///状态的ID 会被每一个子类继承过去
    /// </summary>
    protected StateID stateID;
    /// <summary>
    /// 获取状态ID  属性
    /// </summary>
    public StateID StateID { get { return stateID; } }
    /// <summary>
    /// 每个条件对应的状态存储字典 会被每一个子类继承过去
    /// </summary>
    protected Dictionary<Transition, StateID> map = new Dictionary<Transition, StateID>();
    /// <summary>
    /// 状态所属的状态机
    /// </summary>
    protected FSMSystem fSM;
    public FSMState(FSMSystem fsm)
    {
        fSM = fsm;
    }
    /// <summary>
    /// 往状态机里添加转换条件和条件对应的状态
    /// </summary>
    /// <param name="tr">条件</param>
    /// <param name="ID">状态ID</param>
    public void AddTransition(Transition tr, StateID ID)
    {
        if (tr == Transition.Null)
        {
            Debug.LogError("转换条件不能为空:【AddTransition】");
            return;
        }
        if (ID == StateID.Null)
        {
            Debug.LogError("状态不能为空:【AddTransition】");
            return;
        }
        if (map.ContainsKey(tr))
        {
            Debug.LogError(tr+"已经存在:【AddTransition】");
            return;
        }
        map.Add(tr, ID);
    }
    /// <summary>
    /// 删除某一状态转换
    /// </summary>
    /// <param name="tr"></param>
    public void DeleteTransition(Transition tr)
    {
        if (!map.ContainsKey(tr))
        {
            Debug.LogError("状态不存在 无法删除");
            return;
        }
        map.Remove(tr);
    }
    /// <summary>
    /// 通过转换条件 获取对应的状态 多用在获取要转换的下一个状态时
    /// </summary>
    /// <param name="tr"></param>
    /// <returns></returns>
    public StateID GetStateIDByTransition(Transition tr)
    {
        if (!map.ContainsKey(tr))
        {
            Debug.Log("该状态不存在于状态机中,");
        }
        return map[tr];
    }
    /// <summary>
    /// 进入状态的第一针调用  多用于 初始化该状态的一些信息
    /// </summary>
    public virtual void EnterInto()
    {
    }
    /// <summary>
    /// 状态离开时调用   多用于 用来对该状态善后
    /// </summary>
    public virtual void Leaving()
    {

    }
    /// <summary>
    /// 该状态执行的行为 没帧调用
    /// </summary>
    public abstract void Action(GameObject[] obj);
    /// <summary>
    /// 判断当前状态是否需要转换到其他状态 ,没帧调用
    /// </summary>
    public abstract void Reason(GameObject[] obj);

}
然后创建状态机类:

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

public class FSMSystem
{
    /// <summary>
    /// 存储当前状态机的每个ID对应的状态
    /// </summary>
    private Dictionary<StateID, FSMState> states=new Dictionary<StateID, FSMState>();

    /// <summary>
    /// 当前状态ID
    /// </summary>
    private StateID currentStateID;
    /// <summary>
    /// 当前状态
    /// </summary>
    private FSMState currentState;
    /// <summary>
    /// 添加状态机的状态
    /// </summary>
    /// <param name="state"></param>
    public void addState(FSMState state)
    {
        if (state == null)
        {
            Debug.LogError("要添加的状态为空");
            return;
        }
        if (states.ContainsKey(state.StateID))
        {
            Debug.LogError("当前状态已经存在:" + state.ToString() + ";不能重复添加");
            return;
        }
        //如果当前状态机没有正在进行的状态 则吧这个加入状态机的状态设置成当前状态 当然这里也可以吧这个放在状态机的构造函数里直接构造出来
        if (currentState == null)
        {
            currentStateID = state.StateID;
            currentState = state;
        }
        states.Add(state.StateID, state);
    }
    /// <summary>
    /// 删除某一ID 一般用不到
    /// </summary>
    /// <param name="id"></param>
    public void DeleteState(StateID id)
    {
        if (id == StateID.Null)
        {
            Debug.LogError("要删除的状态不能是空状态");
            return;
        }
        if (!states.ContainsKey(id))
        {
            Debug.LogError("要删除的状态不存在:" + id);
            return;
        }
        states.Remove(id);
    }
    /// <summary>
    /// 执行状态转变 在状态里 判断状态是否需要转换的时候 需要转换的时候转换;
    /// </summary>
    /// <param name="tr"></param>
    public void PerformTransition(Transition tr)
    {
        if (tr == Transition.Null)
        {
            Debug.LogError("不能转换成空条件");
            return;
        }
        StateID id = currentState.GetStateIDByTransition(tr);
        if (id == StateID.Null)
        {
            Debug.LogError("当前要转换到的状态为空状态");
        }
        if (!states.ContainsKey(id))
        {
            Debug.LogError("当前状态不存在于状态机中 无法转换"+ id);
        }
        //校验完毕 执行转换
        //未转换状态时 执行状态离开操作
        currentState.Leaving();
        //更换状态
        currentState = states[id];
        currentStateID = id;
        //执行状态进入的操作
        currentState.EnterInto(); 
    }
    /// <summary>
    /// 执行当前状态的行为方式;
    /// </summary>
    /// <param name="obj">这里的OBJ是用来判断转换条件和行为对象的 可以直接在具体状态中用字段存储 在状态进入时用进入状态的函数初始化</param>
    public void Update( GameObject[] obj)
    {
        currentState.Reason(obj);
        currentState.Action(obj);
    }
}

上面就是状态机的基础 下面我们来看看怎么使用:


我们简单的创建了两个具体状态类(我并没有写完整 只是大概的写了下):

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


public class OneState : FSMState
{
    /// <summary>
    /// 实现构造方法
    /// </summary>
    /// <param name="fsm"></param>
    public OneState(FSMSystem fsm) : base(fsm)
    {
    }

    public override void Action(GameObject[] obj)
    {
        //这里写上这个状态所需要执行的东西
    }

    public override void Reason(GameObject[] obj)
    {
        //这里执行这个状态转换到其他状态的判断
    }
}
第二个具体状态类:

using System;
using System.Collections.Generic;
using UnityEngine;
public class TwoState : FSMState
{
    /// <summary>
    /// 实现构造方法
    /// </summary>
    /// <param name="fsm"></param>
    public TwoState(FSMSystem fsm) : base(fsm)
    {
    }

    public override void Action(GameObject[] obj)
    {
        //这里写上这个状态所需要执行的东西
    }

    public override void Reason(GameObject[] obj)
    {
        //这里执行这个状态转换到其他状态的判断
    }
}
有限状态机的使用者:

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


/// <summary>
/// 这个是使用有限状态机的 比如玩家 怪物 等等
/// </summary>
public class UseFSM:MonoBehaviour
{
    private FSMSystem fSMSystem;
    private void Awake()
    {
        MakeFSM();
    }
    private void Update()
    {
        fSMSystem.Update(new GameObject[] { });//这里传入当前状态判断转换条件是所需要依赖的对象 比如 怪物判断是否能攻击时 需要传入玩家 因为需要借助玩家计算攻击距离是否足够
        //这里的另一种方式是将判断所依赖的对象在状态里进行获取 并在该状态的 EnterInto 方法中初始化;
    }
    void MakeFSM()
    {
        fSMSystem = new FSMSystem();
       //制作状态并设置该状态的转换条件
        FSMState one = new OneState(fSMSystem);
        one.AddTransition(Transition.One, StateID.One);
        FSMState two = new TwoState(fSMSystem);
        two.AddTransition(Transition.Two, StateID.Two);


        //将状态加入到状态机中
        fSMSystem.addState(one);
        fSMSystem.addState(two);


    }
}

下面附上源码下载地址: 希望大家多多斧正 

源码链接:https://pan.baidu.com/s/1mi8A46O  密码:wf6d


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值