延续上一篇继续有限状态机
上一篇中的状态切换判断是在每一个状态类的 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 条件
将配置转换为代码逻辑条件
上图中可以看出,
休息转换到吃饭条件:
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
要知道将要跳转的状态 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
然后我们将 Excel 配置表导出为.csv
文件,放入项目中,读取 .csv
配置将 每一行的转换实例化为一个 Transition,添加到所在的状态类中,这个过程放在 StateMachine 中处理了
角色的每个属性的值转换为 Parameter
(角色的环境变量)存储在 StateMachine
每次执行 StateMachine.OnExecute
方法后,判断当前状态的 List
是否有满足转换条件的,如果有则转换状态。
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 状态切换逻辑删除掉
代码搜然不多,全部粘贴到这里显得臃肿,可读性查,代码下载地址,是一个Unity项目,如果只看代码在目录 FiniteStateMachine\Assets\Scripts 下,代码逻辑是跟引擎无关的,如有问题请评论留言,谢谢