在我上一篇博客https://blog.csdn.net/moonstrace/article/details/114560794的文章里,针对如何利用ASTAR插件实现自动寻路时,利用了两个ENUM实现了一个简单的状态机。但这个状态机并不能具普遍适用。本文试图在这个基础上实现一个简单通用的有限状态机。
首先我们还是对有限状态机的功能进行拆解。首先,状态机应该与外部有交互功能,外部的输入要能够输入到状态机中。其次,状态机的核心功能是管理状态本身的改变,最后,要实现状态机的功能,其每个子状态显然应该有独特的功能实现才能实现普适性。从数据结构上看,我们可以简单的区分出stateManager以及具体state两个结构。StateManager保存状态目前的状态,而具体的state则负责执行具体的功能。
接下来我们看看分析一下具体的实现。首先,我们分别使用多个ENUM来管理状态。对每个StateManager,需要知道自身是处于延续还是转换当中;对FSM下的每个state我们都给一个int类型的编号来进行区分,这个有个小技巧是为了在inspector中方便使用,在inspecto可以定义为ENUM,而运行中使用int的值,从而避免使用模板。而为了便于每个状态管理自己,针对每个状态的活跃、淡入、淡出、结束销毁等状态再用一个enum来标记。其次,为了实现与外部的交互,仅需要在StateManager中暴露一个Target字段,告诉StateManger要向哪个状态转换即可。最后,每个子状态的多样化功能的延展显然也不是FSM本身的内容,但如何体现出来呢,我们将子状态抽象成一个Entity即可,具体子状态功能的system实现显然也不应该体现在FSM中。如此,一个StateManager应该具备如下结构:
public enum ManagerCondition
{
Init,
Continue,
Fading,
Destroy
}
public enum StateConditionEnumValue
{
Active,
Destroy,
Pause,
Entering,
Outing,
}
public struct StateManager : IComponentData
{
public ManagerCondition condition;
public int currentTag;
public int targetTag;
public Entity currentEntity;
public Entity targetEntity;
public float maxTranslationTime;
public float translatedTime;
}
通过Entity 来管理各个不同的子状态,可以在转化时,将各个子状态保存为一个(int,Entity)的IbufferElement,如此,要生成或销毁各个子状态只需要使用Instantiate接口就可以了,如此可以完美避开在StateManager中使用模板的问题。(吐槽一下Unity的job系统使用模板真的太复杂了。。我初期写了好几个模板来实现FSM后面全放弃了。。)转化的类大致如下,使用一个enum的模板可以在inspector中不再面对int的难以理解的数字:
public class InitStateManagerSystem<EnumTag> : MonoBehaviour, IConvertGameObjectToEntity, IDeclareReferencedPrefabs
where EnumTag : System.Enum
{
[Serializable]
public struct StateSetup
{
public EnumTag Tag;
public GameObject Prefab;
public float FadeOutTime;
}
public StateSetup[] States;
public bool UseLTW;
public float FadeTime;
public void Convert()
}