StateTree框架分析

一、简介

"StateTree 是一种通用分层状态机,组合了行为树中的 选择器(Selectors) 与状态机中的 状态(States)过渡(Transitions) 。用户可以创建非常高效、保持灵活且井然有序的逻辑。

StateTree包含以树结构布局的状态。状态选择可以在树中的任意位置触发,但它最初从根开始。在选择过程中,将对每个状态的 进入条件(Enter Conditions) 求值。如果通过,选择将前进到该状态的子状态(如果可用)。如果没有子状态可用,将激活当前状态。" -- 官方文档

StateTree在5.1版本进行了重构,官方文档中的内容和编辑器展示的效果也有所不同:

  • 将Evaluator从状态内部提出到了State Tree的全局中,所有State可以共用同一套Evaluator

  • 新增基于Propertybag的Parameter系统,可以类似黑板功能的声明属性

  • 将Condition数据单独剥离数来为SharedInstanceData,使得多个实例可以共享一份数据。

二、UML

三、StateTree设计分析

3.1 享元模式下,私有数据机制

  1.   索引构建流程
  • compile时,会将EditorData中的数据写入StateTree中。此过程中会构建索引结构,其中,

    • DefaultInstanceData/SharedInstanceData用来存放所有Evaluator/Task/Condition/Transition的默认值。

    • 索引Nodes用来存储索引值

      • InstanceIndex:DefaultInstanceData的索引值

      • DataViewIndex:DataView的索引值。

    • States/Transition/EvaluatorBegin/EvaluatorEnd中记录Nodes的索引值。

  • 运行时,

    • 可以直接根据EvaluatorBegin/End-》Nodes(InstanceIndex)-》InstanceData中找到真实数据

    • CustomEvaluator如何找到在私有空间中的数据?在上边Evaluator遍历找到真实数据的同时,会构建DataView映射表,将DataViewIndex和真实数据建立映射,这样,在CustomEvaluator::Enter\Tick的时候,就可以通过CustomEvaluator中DataViewIndex找到真实数据,获取方法如下:

  1.   ExecutionContext数据驱动流程
    1. StateTree默认值初始化Component的InstancedData

      1. InstanceData数据实例,每个Component维护一个独有的数据空间,用于存放Task、Evaluator产生的数据。其中,condition的数据空间可以在不同的Component间共享,所以,此数据空间可以放在StateTree上的SharedInstanceData中。

      2. 初始化:FStateTreeExecutionContext::Start-》UpdateInstanceData

    2. InstancedData数据读写

3.2 数据流

  1. StateTree外部数据传入
      a. 基于PropertyBag的Parameter设计

      PropertyBag是5.1版本推出的黑板机制,可以在蓝图中动态新增、删除任意类型的成员。

    • 实现原理:其内部还是通过一个InstancedStruct来实现的, 但是,用来记录内存信息的ScriptStruct使用了一个新增的UPropertyBag来实现,当新增成员时,会对应创建一个FPropertyBagProertyDesc,记录成员的名称、数据类型和内存信息,从而,可以通过FPropertyBagPropertyDesc+StructMemory获取数据值。

    • 编辑器编辑功能:在蓝图中可以定义FInstancedPropertyBag成员变量,StructUtilsEditor插件提供了其编辑器显示的自定义UI:FPropertyBagDetails,UI布局注册位置如下:

      编辑器中UI效果如下:

    • 序列化功能:序列化顺序为:PropertyDesc序列化-》PropertyDesc的大小-》StructScript+StructMemory利用SerializeItem进行序列化-》内存大小-》总大小。

    • 暂不支持网络序列化

      b. 生产者-消费者模式的Event机制
    • 实现原理

      • 事件定义:StateTreeEvent中,使用GameplayTag记录事件名称,使用InstancedStruct记录事件参数。

      • 事件入口(生产):通过UStateTreeComponent::SendStateTreeEvent,将StateTreeEvent传递给Context,Context内部将Event存入InstancData中。

      • 事件消费:FStateTreeExecutionContext::Tick中,每次tick执行完毕后,销毁InstancData中的Event

  1. StateTree内部数据流转
      a. 执行上下文设计

      每次驱动StateTree时,都会新建一个执行上下文来实现对私有数据的初始化、绑定数据的更新、及方便业务层快速获取Instance数据的DataView映射表的构建。

      b. 数据绑定机制

      FStateTreePropertyBindings,简要概述就是记录绑定数据的FProperty,当拷贝时,根据DataView中source和target的内存地址进行拷贝。从而实现StateTree内部不同Node之间数据的传递。

3.3 状态管理

  • 执行上下文状态说明

    • Running: 状态运行时,此状态下,tick时才会执行TickEvaluators以及TickTasks,当TickTask实行时修改了Running状态,则调用StateCompleted,此处处理Task完毕逻辑

    • Failed:失败状态,会触发OnStateFailed||OnStateCompelete Trigger,执行Transition。

    • Succeeded:如果是成功状态,会触发OnStateSuccessed||OnStateCompelete Trigger,执行Transition。

    • Unset: 状态机初始状态设置为Unset

  • 执行逻辑(5.2版本)

    • Start时,从Root节点出发,递归查找可执行状态,如果没有可激活的状态,导致Tick时,所有的evaluator、task都不会执行

    • Tick时:执行当前激活状态内的Tasks,FStateTreeTaskBase::Tick 返回非Running状态时,触发StateCompleted。

    • Transition:有5次遍历机会,每次从激活状态数组中,倒序遍历检测可跳转的状态。如果没有可跳转的状态,状态树报错。注意:由Transition触发的EnterState,EnterState的返回值如果是非Running,会继续进行Transition迭代(一共5次),因此,EnterState根据实际需求合理返回数值。

四、StateTree和BehaviorTree区别

行为树与状态机之间的一大区别是,状态机通常在执行沿树向下推进时提交状态选择,而行为树则尝试查找合适的叶节点。

一般来说,即使已经选择一个状态,行为树也会继续执行状态选择逻辑。这是在状态之间过渡的唯一方法。

StateTree基于过渡按需运行状态选择过程。在第一个Tick上,会隐式过渡到根状态,这将选择要运行的第一个状态。选择该状态之后,过渡会指示何时及在何处执行选择逻辑。

五、多线程SharedInstanceData

  1. 由UStateTreeComponent::TickComponent进行驱动的StateTree的tick,引擎ActorComponent::TickComponent驱动的流程是:World在执行Tick的时候,触发了FNamedTaskThread线程去执行任务(FTickFunctionTask),任务FTickFunctionTask具体的工作内容就是执行ActorComponent的Tick函数。其中,FNamedTaskThread被指定在GameThread中执行。所以,对于由UStateTreeComponent来驱动的状态树,只有一份SharedInstanceData。

  2. UAITask_UseGameplayInteraction::TickTask驱动StateTree的tick,不确认此TickTask是否是多线程,如果是多线程,StateTree会为每个线程Copy一份SharedInstanceData,避免多线程下数据读写异常。

四、参考文档

[1] 官方文档: StateTree Overview

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是一个基于Python的实现: ``` # 导入相关库 import pandas as pd import numpy as np from sklearn.datasets import load_iris from sklearn.model_selection import train_test_split from sklearn.tree import DecisionTreeClassifier from sklearn.naive_bayes import GaussianNB from pomegranate import BayesianNetwork # 加载数据集 iris = load_iris() feature_names = iris.feature_names X = pd.DataFrame(iris.data, columns=feature_names) y = iris.target # 划分数据集 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) # 通过决策树进行分类 dt = DecisionTreeClassifier() dt.fit(X_train, y_train) dt_accuracy = dt.score(X_test, y_test) print("决策树的准确率:", dt_accuracy) # 通过朴素贝叶斯进行分类 nb = GaussianNB() nb.fit(X_train, y_train) nb_accuracy = nb.score(X_test, y_test) print("朴素贝叶斯的准确率:", nb_accuracy) # 通过贝叶斯信念网络进行分类 model = BayesianNetwork.from_samples(X_train) bnb_accuracy = 0 for i in range(X_test.shape[0]): bnb_accuracy += model.predict(X_test.iloc[i, :])[0].argmax() == y_test[i] bnb_accuracy /= X_test.shape[0] print("贝叶斯信念网络的准确率:", bnb_accuracy) ``` 在这个代码中,我们首先加载了鸢尾花数据集,然后将数据集划分为训练集和测试集。接着,我们分别使用决策树、朴素贝叶斯和贝叶斯信念网络模型对数据进行分类,并计算了每种模型的准确率。最后,我们将结果输出到屏幕上。 需要注意的是,为了实现贝叶斯信念网络模型,我们使用了Pomegranate库,它是一个Python库,用于构建和分析概率模型。同时,由于贝叶斯信念网络模型的预测结果是一个概率向量,因此我们需要使用argmax()函数来获取最大概率对应的类别。 以上代码仅供参考,具体实现还需要根据实际情况进行修改和优化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值