有限状态机FSM(finite state machine) 二

有限状态机FSM(finite state machine) 二

延续上一篇继续有限状态机
有限状态机FSM(finite state machine) 二_第1张图片
上一篇中的状态切换判断是在每一个状态类的 OnExecute 方法中各种 if else 硬编码逻辑 当状态比较多、条件比较多的时候,就会显得非常的臃肿,且又不利于阅读以及扩展了。
那么我们能不能通过配置来省略各种 if else if else 硬编码呢 ? 答案:有的。
如上图每一条状态转换的连线,我们称之为转换 Transition

休息状态 Transition 吃饭状态
休息状态 Transition 打篮球
休息状态 Transition 写作业
那也就是 休息有三个 Transition,每一个 Transition 上有 0 到多个条件
上图中转换条件是抽象的,下面将各种条件转换为具体的可判断的值
如下
设置属性饥饿感值 _senseHunger 取值范围 [0, 10]_senseHunger > 8 表示 饥饿
设置属性体力值 _energy 取值范围 [0, 10]_energy < 1 表示 累了
设置属性作业量 _homeWorkCount 取值范围 [0, 10]_homeWorkCount < 1 表示作业做完了
设置属性写作业强迫性 _needHomeWork 取值范围 [0, 10]_needHomeWork > 8 表示该写作业了
设置属性打篮球渴望值 _wantBasketball 取值范围 [0, 10]_wantBasketball > 8 表示想打篮球了

每个属性有一个初始值,在不同状态对某些值做不同增量(正、负),根据判断不同属性的值来判断每个 Transition是否成立
下面是各种状态之间转换的 Transition 条件
有限状态机FSM(finite state machine) 二_第2张图片
将配置转换为代码逻辑条件
上图中可以看出,
休息转换到吃饭条件:
Hunger(饥饿感 float值 ) > 8
Energy (精力 float 值) > 9.5
我们将每个属性的值视为角色的环境变量
建立一个条件参数类,包含参数名、参数类型、默认值、比较值

// 条件参数
public class Parameter
{
    // 参数名字
    public string _parameterName;
    // 参数类型
    public ParameterType _parameterType;
    // 参数比较类型
    public ParameterCompare _compare;
    // int 参考值
    public int intValue;
    // float 参考值
    public float floatValue;
    // bool 参考值
    public bool boolValue;
    // string参考值
    public string stringValue;
    /*
    Parameter parameter = new Parameter();
    // 参数名为 Age
    parameter._parameterName = "Age";
    // 参数类型为 int 类型
    parameter._parameterType = ParameterType.INT;
    // 参数 int参考值是 5
    parameter.intValue = 5;
    // 参数比较类型是 大于等于
    parameter._compare = ParameterCompare.GREATER_EQUALS;

    // 参数意思就是,我要和一个属性字段为Age的int类型值做比较,如果Age >= 5 则返回true
    */
}

参数类型

// 参数类型
public enum ParameterType
{
    Int = 1,    // int 类型

    Float = 2,  // float 类型

    Bool = 3,   // bool 类型

    String = 4, // 字符串类型
}

比较类型

// 参数比较类型
public enum ParameterCompare
{
    /// 
    /// 无效值
    /// 
    INVALID = 0,

    /// 
    /// 大于
    /// 
    GREATER = 1 << 0,

    /// 
    /// 小于
    /// 
    LESS = 1 << 1,

    /// 
    /// 等于
    /// 
    EQUALS = 1 << 2,

    /// 
    /// 不等于
    /// 
    NOT_EQUAL = 1 << 3,

    /// 
    /// 大于等于
    /// 
    GREATER_EQUALS = 1 << 4,

    /// 
    /// 小于等于
    /// 
    LESS_EQUAL = 1 << 5,
}

然后就是 Transition
可以包含多个参数则加一个参数列表List _parameterList
要知道将要跳转的状态 StateEnum ToState
有时候可能一个特殊逻辑,不太好通过几个参数来配置转换条件,需要加一个函数,则给 Transition 添加一个判断能否转换的函数 bool CanTransition()

/// 
/// 状态装换条件
/// 
public class Transition
{
    // 切换状态事件
    private TransitionCallback _transitionCallback;
    private List<Parameter> _parameterList = new List<Parameter>();

    public Transition(StateEnum toState, TransitionCallback transitionCallback)
    {
        ToState = toState;
        _transitionCallback = transitionCallback;
    }

    public Transition(StateEnum toState, List<Parameter> parameterList)
    {
        ToState = toState;
        _parameterList = parameterList;
    }

    public StateEnum ToState { get; set; }

    public List<Parameter> ParameterList
    {
        get { return _parameterList; }
    }

    public bool CanTransition()
    {
        if (null == _transitionCallback)
        {
            return false;
        }
        return _transitionCallback();
    }
}

Transition 属于每个状态的,所以在 StateBase 中加入 List _transitionList = new List();来存储每个状态的转换
然后我们将 Excel 配置表导出为.csv文件,放入项目中,读取 .csv 配置将 每一行的转换实例化为一个 Transition,添加到所在的状态类中,这个过程放在 StateMachine 中处理了

角色的每个属性的值转换为 Parameter(角色的环境变量)存储在 StateMachine
每次执行 StateMachine.OnExecute 方法后,判断当前状态的 List _transitionList 是否有满足转换条件的,如果有则转换状态。
Transition中的 Parameter则需要跟 StateMachine中存储的环境变量Parameter做比较
StateMachine 类修改如下

using System.Collections.Generic;
using UnityEngine;

public enum StateEnum
{
    EAT = 0,        // 吃饭

    RESET = 1,      // 休息

    BASKETBALL = 2, // 打篮球

    HOMEWORK = 3,   // 写作业
}

public class StateMachine
{
    // 保存所有的状态
    private Dictionary<StateEnum, StateBase> _stateDic = new Dictionary<StateEnum, StateBase>();
    // 记录当前状态
    private StateBase _currentState;
    // 环境变量
    private Dictionary<string, Parameter> _parameterDic = new Dictionary<string, Parameter>();

    public StateMachine()
    {
        // 初始化状态、并存储
        _stateDic[StateEnum.EAT] = new StateEat();
        _stateDic[StateEnum.RESET] = new StateReset();
        _stateDic[StateEnum.BASKETBALL] = new StateBasketball();
        _stateDic[StateEnum.HOMEWORK] = new StateHomeWork();
    }

    public Dictionary<StateEnum, StateBase> StateDic
    {
        get { return _stateDic; }
    }

    // 获取当前状态
    public StateBase CurrentState
    {
        get { return _currentState; }
        private set { _currentState = value; }
    }

    // 状态转换方法
    public void TransitionState(StateEnum stateEnum)
    {
        // 如果当前状态不为空,先退出当前状态
        if (null != CurrentState)
        {
            CurrentState.OnExit();
        }

        // 令当前状态等于转换的新状态
        CurrentState = _stateDic[stateEnum];
        // 转换的新状态执行 进入方法
        CurrentState.OnEnter();
        CurrentState.OnExecute();
    }

    // 每帧执行的方法
    public void OnExecute()
    {
        if (null != CurrentState)
        {
            CurrentState.OnExecute();
        }

        // 判断CurrentState所有转换条件
        for (int i = 0; i < CurrentState.TransitionList.Count; ++i)
        {
            Transition transition = CurrentState.TransitionList[i];
            // 如果转换条件为 true,则转换状态
            if (transition.CanTransition() || CompareTransition(transition))
            {
                TransitionState(transition.ToState);
                break;
            }
        }
    }

    // 判断 Transition 是否满足
    private bool CompareTransition(Transition transition)
    {
        if (transition.ParameterList.Count <= 0)
        {
            return false;
        }
        for (int i = 0; i < transition.ParameterList.Count; ++i)
        {
            Parameter parameter = transition.ParameterList[i];
            Parameter environment = null;
            if (!_parameterDic.TryGetValue(parameter._parameterName, out environment)
                || !ConditionCompare.CompareParameter(environment, parameter))
            {
                return false;
            }
        }
        return true;
    }

    // 更新环境变量的值
    public void UpdateParameter(string parameterName, int intValue)
    {
        Parameter parameter = GetParameter(parameterName);
        parameter._parameterType = ParameterType.Int;
        parameter.intValue = intValue;
    }

    // 更新环境变量的值
    public void UpdateParameter(string parameterName, float floatValue)
    {
        Parameter parameter = GetParameter(parameterName);
        parameter._parameterType = ParameterType.Float;
        parameter.floatValue = floatValue;
    }

    // 更新环境变量的值
    public void UpdateParameter(string parameterName, bool boolValue)
    {
        Parameter parameter = GetParameter(parameterName);
        parameter._parameterType = ParameterType.Bool;
        parameter.boolValue = boolValue;
    }

    // 更新环境变量的值
    public void UpdateParameter(string parameterName, string stringValue)
    {
        Parameter parameter = GetParameter(parameterName);
        parameter._parameterType = ParameterType.String;
        parameter.stringValue = stringValue;
    }

    // 获取环境变量
    private Parameter GetParameter(string parameterName)
    {
        Parameter parameter = null;
        if (!_parameterDic.TryGetValue(parameterName, out parameter))
        {
            parameter = new Parameter();
            _parameterDic[parameterName] = parameter;
        }
        return parameter;
    }

    #region Config
    /// 
    /// 配置表,对于不同的角色可以有不同的配置表,灵活多变
    /// 
    /// 配置文件名
    public void SetConfigFile(string fileName)
    {
        TableRead.Instance.Init();
        TableRead.Instance.ReadCustomPath(Application.streamingAssetsPath);
        List<int> list = TableRead.Instance.GetKeyList(fileName);
        foreach (var key in list)
        {
            AnalysisTransition(fileName, key);
        }
    }

    // 解析每一行的 Transition
    private void AnalysisTransition(string fileName, int key)
    {
        int currentState = int.Parse(TableRead.Instance.GetData(fileName, key, "CurrentState"));
        int toState = int.Parse(TableRead.Instance.GetData(fileName, key, "ToState"));

        List<Parameter> parameterList = new List<Parameter>();
        for (int i = 1; i <= 2; ++i)
        {
            string name = TableRead.Instance.GetData(fileName, key, string.Format("Name{0}", i));
            if (string.IsNullOrEmpty(name))
            {
                continue;
            }
            Parameter parameter = new Parameter();
            parameter._parameterName = name;
            parameter._parameterType = (ParameterType)(int.Parse(TableRead.Instance.GetData(fileName, key, string.Format("Type{0}", i))));
            string value = TableRead.Instance.GetData(fileName, key, string.Format("Value{0}", i));
            // 根据类型将 value 解析为 int/float/bool/string 
            if (parameter._parameterType == ParameterType.Int)
            {
                parameter.intValue = int.Parse(value);
            }
            else if (parameter._parameterType == ParameterType.Float)
            {
                parameter.floatValue = float.Parse(value);
            }
            else if (parameter._parameterType == ParameterType.Bool)
            {
                parameter.boolValue = bool.Parse(value);
            }
            else if (parameter._parameterType == ParameterType.String)
            {
                parameter.stringValue = value;
            }
            parameter._compare = (ParameterCompare)(int.Parse(TableRead.Instance.GetData(fileName, key, string.Format("Compare{0}", i))));
            //将每个参数添加到集合中
            parameterList.Add(parameter);
        }

        // 实例化 Transition
        Transition transition = new Transition((StateEnum)toState, parameterList);
        // 根据配置 currentState 获取状态
        StateBase stateBase = _stateDic[(StateEnum)currentState];
        // 将Transition 添加到状态中
        stateBase.TransitionList.Add(transition);
    }
    #endregion
}

StateReset、StateBasketball、StateEat、StateHomeWork 的 OnExecute 中的 if else 状态切换逻辑删除掉

有限状态机FSM(finite state machine) 二_第3张图片

代码搜然不多,全部粘贴到这里显得臃肿,可读性查,代码下载地址,是一个Unity项目,如果只看代码在目录 FiniteStateMachine\Assets\Scripts 下,代码逻辑是跟引擎无关的,如有问题请评论留言,谢谢

你可能感兴趣的:(AI,FSM,有限状态机,有限状态机可配置,AI)