StateTree 是一种 UE5 中新增的通用分层状态机,其组合了行为树中的 选择器(Selectors)与状态机中的 状态(States)和 过渡(Transitions)。用户能够创立十分高效、放弃灵便且颠三倒四的逻辑。
StateTree 蕴含以树结构布局的状态。状态抉择能够在树中的任意地位触发。相比行为树,其组织形式更为自在,灵便,能够在任意两个状态之间过渡。相比状态机,其树状的分层构造更加清晰和高效。
这样一个非常好用乏味的新货色天然未免让人想进入源码一探到底。
次要文件的组织
StateTree 的次要逻辑在 UE5 源码中大抵分为如下几个文件:
StateTree.h 次要蕴含 UStateTree 类,作为 StateTree 的定义
StateTreeComponent.h 次要蕴含 UStateTreeComponent 类 作为在 Actor 上运行指定 StateTree 的组件
StateTreeExecutionContext.h 次要蕴含 FStateTreeExecutionContext 作为更新和拜访 StateTree 的 helper 类 StateTree 的更新,状态切换和转换逻辑根本都在这个外面
其余的还有诸如 StateTree 中条件,工作,评估器的基类,用于编辑器资源创立的类等,在此不多加叙述
执行流程
开始
进入游戏后,对于启用了 UStateTreeComponent 组件的 Actor,在组件的 BeginPlay()期间,执行 FStateTreeExecutionContext 的 Start()函数。开启 StateTree 的执行,该函数次要执行初始化和状态抉择,一开始的状态抉择从根部开始,后续会具体讲到状态抉择的逻辑。以下为 Start()函数增加了具体的正文,能够帮忙各位了解。
// 开始状态树的执行,返回状态执行的状态
EStateTreeRunStatus FStateTreeExecutionContext::Start()
{
// 记录函数执行工夫
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(StateTree_Start);
// 如果上下文没有被正确初始化,则返回失败并记录日志
if (!IsValid())
{STATETREE_LOG(Warning, TEXT("%s: StateTree context is not initialized properly ('%s'using StateTree'%s')"),
ANSI_TO_TCHAR(__FUNCTION__), *GetNameSafe(&Owner), *GetFullNameSafe(&StateTree));
return EStateTreeRunStatus::Failed;
}
// 如果实例数据有效,则初始化
if (!InstanceData.IsValid())
{
const FStateTreeActiveStates Empty;
UpdateInstanceData(Empty, Empty);
if (!InstanceData.IsValid())
{STATETREE_LOG(Warning, TEXT("%s: Failed to initialize instance data on'%s'using StateTree'%s'. Try to recompile the StateTree asset."),
ANSI_TO_TCHAR(__FUNCTION__), *GetNameSafe(&Owner), *GetFullNameSafe(&StateTree));
return EStateTreeRunStatus::Failed;
}
}
// 获取共享实例数据
const TSharedPtr<FStateTreeInstanceData> SharedInstanceData = StateTree.GetSharedInstanceData();
check(SharedInstanceData.IsValid());
// 获取执行状态,并记录为指针以便稍后从新获取
FStateTreeExecutionState* Exec = &GetExecState(); // Using pointer as we will need to reacquire the exec later.
// 如果之前有状态正在运行,则进行
if (Exec->TreeRunStatus == EStateTreeRunStatus::Running)
{Stop();
}
// 调用所有评估器的 TreeStart 办法
StartEvaluators();
// 第一次调用 TickEvaluators
TickEvaluators(0.0f);
// 初始化为未设置状态
Exec->TreeRunStatus = EStateTreeRunStatus::Running;
Exec->ActiveStates.Reset();
Exec->LastTickStatus = EStateTreeRunStatus::Unset;
static const FStateTreeStateHandle RootState = FStateTreeStateHandle(0);
// 抉择状态, 从 RootState 开始
FStateTreeActiveStates NextActiveStates;
if (SelectState(*SharedInstanceData.Get(), RootState, NextActiveStates))
{if (NextActiveStates.Last() == FStateTreeStateHandle::Succeeded || NextActiveStates.Last() == FStateTreeStateHandle::Failed)
{
// 如果抉择的是终止状态,阐明状态执行实现
// 记录日志并更改状态
STATETREE_LOG(Warning, TEXT("%s: Tree %s at StateTree start on'%s'using StateTree'%s'."),
ANSI_TO_TCHAR(__FUNCTION__), NextActiveStates.Last() == FStateTreeStateHandle::Succeeded ? TEXT("succeeded") : TEXT("failed"), *GetNameSafe(&Owner), *GetFullNameSafe(&StateTree));
Exec->TreeRunStatus = NextActiveStates.Last() == FStateTreeStateHandle::Succeeded ? EStateTreeRunStatus::Succeeded : EStateTreeRunStatus::Failed;}
else
{
// 如果不是终止状态,进入抉择的状态
// Enter state 能够失败或胜利,并且与 Tick 中的矗立雷同
FStateTreeTransitionResult Transition;
Transition.TargetState = RootState;
Transition.CurrentActiveStates = Exec->ActiveStates;
Transition.CurrentRunStatus = Exec->LastTickStatus;
Transition.NextActiveStates = NextActiveStates;
// EnterState 将更新 Exec.ActiveStates
const EStateTreeRunStatus LastTickStatus = EnterState(Transition);
// 须要从新获取执行状态,因为 EnterState 可能会更改调配
Exec = &GetExecState();
Exec->LastTickStatus = LastTickStatus;
// 如果状态已实现,则在此时报告
if (Exec->LastTickStatus != EStateTreeRunStatus::Running)
{StateCompleted();
}
}
}
// 如果没有沉闷状态,则返回失败,这应该不会产生
if (Exec->ActiveStates.IsEmpty())
{STATETREE_LOG(Error, TEXT("%s: Failed to select initial state on'%s'using StateTree'%s'. This should not happen, check that the StateTree logic can always select a state at start."),
ANSI_TO_TCHAR(__FUNCTION__), *GetNameSafe(&Owner), *GetFullNameSafe(&StateTree));
Exec->TreeRunStatus = EStateTreeRunStatus::Failed;
}
// 返回状态执行的状态
return Exec->TreeRunStatus;
}
更新
StateTree 的更新逻辑次要在 FStateTreeExecutionContext 的 Tick() 函数里进行。该函数次要负责更新评估期和 tick 事件,依据条件触发状态转换等,以下为代码增加了正文帮忙了解。
EStateTreeRunStatus FStateTreeExecutionContext::Tick(const float DeltaTime)
{
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(StateTree_Tick); // 统计性能
// 判断状态树是否非法
if (!IsValid())
{
// 输入正告信息
STATETREE_LOG(Warning, TEXT("%s: StateTree context is not initialized properly ('%s'using StateTree'%s')"),
ANSI_TO_TCHAR(__FUNCTION__), *GetNameSafe(&Owner), *GetFullNameSafe(&StateTree));
return EStateTreeRunStatus::Failed; // 返回运行失败状态
}
// 判断实例数据是否非法
if (!InstanceData.IsValid())
{
// 输入错误信息
STATETREE_LOG(Error, TEXT("%s: Tick called on %s using StateTree %s with invalid instance data. Start() must be called before Tick()."),
ANSI_TO_TCHAR(__FUNCTION__), *GetNameSafe(&Owner), *GetFullNameSafe(&StateTree));
return EStateTreeRunStatus::Failed; // 返回运行失败状态
}
// 获取共享实例数据
const TSharedPtr<FStateTreeInstanceData> SharedInstanceData = StateTree.GetSharedInstanceData();
check(SharedInstanceData.IsValid()); // 断言共享实例数据是否无效
// 获取执行状态
FStateTreeExecutionState* Exec = &GetExecState();
// 将在工夫距离内增加的事件增加到 EventsToProcess 中以进行后续解决
EventsToProcess = InstanceData.GetEvents();
InstanceData.GetEvents().Reset();
// 如果状态已进行或未在运行,则间接返回其状态
if (Exec->TreeRunStatus != EStateTreeRunStatus::Running)
{return Exec->TreeRunStatus;}
// 更新阀门转换工夫
if (Exec->GatedTransitionIndex.IsValid())
{Exec->GatedTransitionTime -= DeltaTime;}
// 执行全局预计器
TickEvaluators(DeltaTime);
// 如果上次的状态是运行中,则对活动状态进行工作解决,并告诉状态曾经实现
if (Exec->LastTickStatus == EStateTreeRunStatus::Running)
{
// 对活动状态的工作进行解决
Exec->LastTickStatus = TickTasks(DeltaTime);
// 如果该状态实现后不再运行,告诉状态曾经实现
if (Exec->LastTickStatus != EStateTreeRunStatus::Running)
{StateCompleted();
}
}
// 将 EventsToProcess 中的事件增加到实例数据的事件列表中以供后续解决
EventsToProcess.Append(InstanceData.GetEvents());
// 在肯定的迭代次数内触发条件转换或状态实现 / 失败转换,并处理事件
static constexpr int32 MaxIterations = 5;
for (int32 Iter = 0; Iter < MaxIterations; Iter++)
{
// 触发条件转换或状态实现 / 失败转换,并获取转换后果
FStateTreeTransitionResult Transition;
if (TriggerTransitions(*SharedInstanceData.Get(), Transition))
{
// 确认状态转换后,重置事件列表,并开始状态退出操作
InstanceData.GetEvents().Reset();
ExitState(Transition);
// 如果要转换到的状态为终止状态,设置运行状态为胜利或失败,并返回该状态
if (Transition.NextActiveStates.Last() == FStateTreeStateHandle::Succeeded || Transition.NextActiveStates.Last() == FStateTreeStateHandle::Failed)
{Exec->TreeRunStatus = Transition.NextActiveStates.Last() == FStateTreeStateHandle::Succeeded ? EStateTreeRunStatus::Succeeded : EStateTreeRunStatus::Failed;
Exec->ActiveStates.Reset();
return Exec->TreeRunStatus;
}
// 将状态退出期间积攒的事件增加到 EventsToProcess 中以进行后续解决
EventsToProcess.Append(InstanceData.GetEvents());
InstanceData.GetEvents().Reset();
// 进入新状态并解决其工作
const EStateTreeRunStatus LastTickStatus = EnterState(Transition);
Exec = &GetExecState();
Exec->LastTickStatus = LastTickStatus;
// 如果该状态实现后不再运行,告诉状态曾经实现
if (Exec->LastTickStatus != EStateTreeRunStatus::Running)
{StateCompleted();
}
}
// 如果曾经找到了运行状态,则间接退出
if (Exec->LastTickStatus == EStateTreeRunStatus::Running)
{break;}
}
// 如果活动状态为空,返回运行失败状态
if (Exec->ActiveStates.IsEmpty())
{STATETREE_LOG(Error, TEXT("%s: Failed to select state on'%s'using StateTree'%s'. This should not happen, state completion transition is likely missing."),
ANSI_TO_TCHAR(__FUNCTION__), *GetNameSafe(&Owner), *GetFullNameSafe(&StateTree));
Exec->TreeRunStatus = EStateTreeRunStatus::Failed;
return Exec->TreeRunStatus;
}
// 重置事件列表并返回以后状态
EventsToProcess.Reset();
return Exec->TreeRunStatus;
}
状态抉择
状态抉择的次要逻辑在 FStateTreeExecutionContext 的 SelectState()里。在抉择过程中,将对每个状态的进入条件(Enter Conditions)求值。如果通过,抉择将后退到该状态的子状态(如果可用)。如果没有子状态可用,将激活以后状态。抉择状态将激活从根到叶状态的所有状态。抉择某个状态时,所选状态及其所有父状态都将激活。为所有活动状态执行所有工作,执行形式为从根开始,直至所选的状态。以下为 SelectState 函数增加了正文。
/**
- 抉择要转换到的下一个状态,并更新激活状态汇合以反映新状态。
- @param SharedInstanceData 共享实例数据。
- @param NextState 要抉择的下一个状态。
- @param OutNewActiveState 输入的新激活状态汇合。
- @return 如果胜利抉择并转移了状态,则返回 true,否则返回 false。
*/
bool FStateTreeExecutionContext::SelectState(
FStateTreeInstanceData& SharedInstanceData, // 共享实例数据
const FStateTreeStateHandle NextState, // 要抉择的下一个状态
FStateTreeActiveStates& OutNewActiveState) // 输入的新激活状态汇合
{
const FStateTreeExecutionState& Exec = GetExecState(); // 获取以后的执行状态
// 如果要抉择的下一个状态有效,则返回失败
if (!NextState.IsValid())
{return false;}
OutNewActiveState = Exec.ActiveStates; // 将以后激活状态汇合复制到输入汇合中
TStaticArray<FStateTreeStateHandle, FStateTreeActiveStates::MaxStates> InBetweenStates; // 存储中间状态的数组
int32 NumInBetweenStates = 0; // 中间状态数量
int32 CommonActiveAncestorIndex = INDEX_NONE; // 下一个状态与以后激活状态的公共先人在 OutNewActiveState 中的索引
FStateTreeStateHandle CurrState = NextState; // 从待抉择状态开始向上查找公共先人
while (CurrState.IsValid())
{InBetweenStates[NumInBetweenStates++] = CurrState; // 将以后状态增加到中间状态数组中
CommonActiveAncestorIndex = OutNewActiveState.IndexOfReverse(CurrState); // 查找该状态在以后激活状态中的地位
if (CommonActiveAncestorIndex != INDEX_NONE) // 找到公共先人,退出循环
{break;}
if (NumInBetweenStates == InBetweenStates.Num()) // 中间状态数量超过限度,返回失败
{STATETREE_LOG(Error, TEXT("%s: Too many parent states when selecting state'%s'from'%s'.'%s'using StateTree'%s'."),
ANSI_TO_TCHAR(__FUNCTION__), *GetSafeStateName(NextState), *GetStateStatusString(Exec), *GetNameSafe(&Owner), *GetFullNameSafe(&StateTree));
return false;
}
CurrState = StateTree.States[CurrState.Index].Parent; // 迭代查找父状态
}
OutNewActiveState.SetNum(FMath::Max(0, CommonActiveAncestorIndex)); // 移除公共先人以下的所有状态
// 将中间状态(从下标 NumInBetweenStates-1 到 1,以反向程序)增加到新的激活状态汇合中
bool bActiveStatesOverflow = false; // 激活状态汇合是否已满标记
for (int32 Index = NumInBetweenStates - 1; Index > 0; Index--)
{bActiveStatesOverflow |= !OutNewActiveState.Push(InBetweenStates[Index]); // 增加中间状态到输入激活状态汇合中,如果汇合已满则标记为 true
}
if (bActiveStatesOverflow) // 如果增加中间状态时呈现汇合溢出,则返回失败
{STATETREE_LOG(Error, TEXT("%s: Reached max execution depth when trying to select state %s from'%s'.'%s'using StateTree'%s'."),
ANSI_TO_TCHAR(__FUNCTION__), *GetSafeStateName(NextState), *GetStateStatusString(Exec), *GetNameSafe(&Owner), *GetFullNameSafe(&StateTree));
return false;
}
// 执行状态转移
return SelectStateInternal(SharedInstanceData, NextState, OutNewActiveState);
}
以下是 SelcetStateInternal 的代码
bool FStateTreeExecutionContext::SelectStateInternal(FStateTreeInstanceData& SharedInstanceData, const FStateTreeStateHandle NextState, FStateTreeActiveStates& OutNewActiveState)
{
// 记录运行工夫的统计量。CSV_SCOPED_TIMING_STAT_EXCLUSIVE(StateTree_SelectState);
const FStateTreeExecutionState& Exec = GetExecState();
// 如果要抉择的状态有效,则输入错误信息并返回 false。if (!NextState.IsValid())
{STATETREE_LOG(Error, TEXT("%s: Trying to select invalid state from'%s'.'%s'using StateTree'%s'."),
ANSI_TO_TCHAR(__FUNCTION__), *GetStateStatusString(Exec), *GetNameSafe(&Owner), *GetFullNameSafe(&StateTree));
return false;
}
// 获取要抉择的状态。const FCompactStateTreeState& State = StateTree.States[NextState.Index];
// 查看该状态是否能够进入。if (TestAllConditions(SharedInstanceData, State.EnterConditionsBegin, State.EnterConditionsNum))
{
// 将要抉择的状态增加到输入激活状态汇合中,如果增加失败则输入错误信息并返回 false。if (!OutNewActiveState.Push(NextState))
{STATETREE_LOG(Error, TEXT("%s: Reached max execution depth when trying to select state %s from'%s'.'%s'using StateTree'%s'."),
ANSI_TO_TCHAR(__FUNCTION__), *GetSafeStateName(NextState), *GetStateStatusString(Exec), *GetNameSafe(&Owner), *GetFullNameSafe(&StateTree));
return false;
}
// 如果该状态有链接状态,则尝试转换到链接状态。if (State.LinkedState.IsValid())
{if (SelectStateInternal(SharedInstanceData, State.LinkedState, OutNewActiveState))
{
// 如果胜利,则返回 true。return true;
}
}
// 如果没有链接状态,并且该状态有子状态,则尝试抉择一个子状态。else if (State.HasChildren())
{for (uint16 ChildState = State.ChildrenBegin; ChildState < State.ChildrenEnd; ChildState = StateTree.States[ChildState].GetNextSibling())
{if (SelectStateInternal(SharedInstanceData, FStateTreeStateHandle(ChildState), OutNewActiveState))
{
// 如果胜利,则返回 true。return true;
}
}
}
// 如果没有链接状态并且没有子状态,则将该状态作为以后状态进行抉择。else
{
// Select this state.
return true;
}
// 弹出增加到输入激活状态汇合中的状态。OutNewActiveState.Pop();}
// 如果没有抉择任何状态,则返回 false。return false;
}
状态切换
StateTree 的状态切换逻辑次要在 FStateTreeExecutionContext::TriggerTransitions 里,该函数将从以后状态开始自底向上查问自根节点的所有可能的转换,同时对于带有提早的转换进行非凡解决,以下是增加了正文的代码
/**
- 该函数用于触发状态树的转换。
- @param SharedInstanceData 状态树实例的数据。
- @param OutTransition 存储状态树转换后果的变量。
- @return 如果胜利触发了状态转换,则返回 true;否则返回 false 并持续更新以后状态节点。
*/
bool FStateTreeExecutionContext::TriggerTransitions(FStateTreeInstanceData& SharedInstanceData, FStateTreeTransitionResult& OutTransition)
{
// 应用计时器统计函数运行工夫
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(StateTree_TriggerTransition);
FStateTreeExecutionState& Exec = GetExecState();
// 依据上一次执行的后果,确定该采取什么类型的触发条件
EStateTreeTransitionTrigger CompletionTrigger = EStateTreeTransitionTrigger::None;
if (Exec.LastTickStatus == EStateTreeRunStatus::Succeeded)
{CompletionTrigger = EStateTreeTransitionTrigger::OnStateSucceeded;}
else if (Exec.LastTickStatus == EStateTreeRunStatus::Failed)
{CompletionTrigger = EStateTreeTransitionTrigger::OnStateFailed;}
// 定义一个 lambda 表达式,用于查看是否存在给定的事件类型
auto HasEvent = [this](const FGameplayTag QueriedTag)
{if (EventsToProcess.IsEmpty())
{return false;}
// 判断是否存在对应事件类型的事件对象
return EventsToProcess.ContainsByPredicate([QueriedTag](const FStateTreeEvent& Event)
{return Event.Tag == QueriedTag;});
};
// 从以后状态向根节点遍历,查看所有可能产生的转移条件
for (int32 StateIndex = Exec.ActiveStates.Num() - 1; StateIndex >= 0; StateIndex--)
{
// 获取以后状态节点
const FCompactStateTreeState& State = StateTree.States[Exec.ActiveStates[StateIndex].Index];
// 遍历以后状态中的每个转移条件
for (uint8 i = 0; i < State.TransitionsNum; i++)
{
// 获取以后转移条件对象
const int16 TransitionIndex = State.TransitionsBegin + i;
const FCompactStateTransition& Transition = StateTree.Transitions[TransitionIndex];
// 判断是否须要查看以后条件
const bool bShouldCheck = EnumHasAnyFlags(Transition.Trigger, CompletionTrigger) // 确认实现触发器是否与以后触发条件匹配
|| Transition.Trigger == EStateTreeTransitionTrigger::OnTick // 是否为每次状态树运行都应该查看的转移条件
|| (Transition.Trigger == EStateTreeTransitionTrigger::OnEvent && HasEvent(Transition.EventTag)); // 是否与事件相干
// 如果须要查看以后条件,并且满足所有条件,则进行转移
if (bShouldCheck && TestAllConditions(SharedInstanceData, Transition.ConditionsBegin, Transition.ConditionsNum))
{
// 如果这是一个门控转移,则执行非凡解决逻辑
if (Transition.GateDelay > 0)
{
// 如果以后转移条件被门控且还在限度工夫内,则期待
if ((int32)Exec.GatedTransitionIndex.Get() != TransitionIndex)
{
// 设置属性并开始转移解决
Exec.GatedTransitionIndex = FStateTreeIndex16(TransitionIndex);
Exec.GatedTransitionTime = FMath::RandRange(0.0f, Transition.GateDelay * 0.1f); // TODO: we need variance too.
BeginGatedTransition(Exec);
STATETREE_LOG(Verbose, TEXT("Gated transition triggered from'%s'(%s) ->'%s'%.1fs"), *GetSafeStateName(Exec.ActiveStates.Last()), *State.Name.ToString(), *GetSafeStateName(Transition.State), Exec.GatedTransitionTime);
}
// 持续放弃状态更新,直到咱们尝试触发转换
if (Exec.GatedTransitionTime > 0.0f)
{return false;}
STATETREE_LOG(Verbose, TEXT("Passed gated transition from'%s'(%s) ->'%s'"), *GetSafeStateName(Exec.ActiveStates.Last()), *State.Name.ToString(), *GetSafeStateName(Transition.State));
}
// 依据不同的转移类型做对应解决
if (Transition.Type == EStateTreeTransitionType::GotoState || Transition.Type == EStateTreeTransitionType::NextState)
{
// 更新转换后果的以后活动状态列表与指标状态
OutTransition.CurrentActiveStates = Exec.ActiveStates;
OutTransition.TargetState = Transition.State;
OutTransition.NextActiveStates.Reset();
// 尝试抉择下一个活动状态
if (SelectState(SharedInstanceData, Transition.State, OutTransition.NextActiveStates))
{STATETREE_LOG(Verbose, TEXT("Transition on state'%s'(%s) -[%s]-> state'%s'"), *GetSafeStateName(Exec.ActiveStates.Last()), *State.Name.ToString(), *GetSafeStateName(Transition.State), *GetSafeStateName(OutTransition.NextActiveStates.Last()));
return true;
}
}
else if (Transition.Type == EStateTreeTransitionType::NotSet)
{
// 对于不设置转移类型,间接更新以后状态并持续运行
//(能够应用该类型的条件屏蔽父级状态节点中的某些转移条件)return false;
}
else if (Transition.Type == EStateTreeTransitionType::Succeeded)
{
// 如果触发了胜利转移,则将以后状态设置为胜利状态,并进行状态树的执行
STATETREE_LOG(Verbose, TEXT("Stop tree execution from state'%s'(%s): Succeeded"), *GetSafeStateName(Exec.ActiveStates.Last()), *State.Name.ToString());
OutTransition.CurrentActiveStates = Exec.ActiveStates;
OutTransition.TargetState = FStateTreeStateHandle::Succeeded;
OutTransition.NextActiveStates = FStateTreeActiveStates(FStateTreeStateHandle::Succeeded);
return true;
}
else
{
// 如果触发了失败转移,则将以后状态设置为失败状态,并进行状态树的执行
STATETREE_LOG(Verbose, TEXT("Stop tree execution from state'%s'(%s): Failed"), *GetSafeStateName(Exec.ActiveStates.Last()), *State.Name.ToString());
OutTransition.CurrentActiveStates = Exec.ActiveStates;
OutTransition.TargetState = FStateTreeStateHandle::Failed;
OutTransition.NextActiveStates = FStateTreeActiveStates(FStateTreeStateHandle::Failed);
return true;
}
}
else if ((int32)Exec.GatedTransitionIndex.Get() == TransitionIndex)
{
// 如果以后转移被提早,然而查看失败,则重置限度条件
Exec.GatedTransitionIndex = FStateTreeIndex16::Invalid;
Exec.GatedTransitionTime = 0.0f;
}
}
}
// 如果上一次状态树运行的后果不为“正在运行”,则返回到根节点并从新开始状态树的实例化
if (Exec.LastTickStatus != EStateTreeRunStatus::Running)
{static const FStateTreeStateHandle RootState = FStateTreeStateHandle(0);
OutTransition.CurrentActiveStates = Exec.ActiveStates;
OutTransition.TargetState = RootState;
OutTransition.NextActiveStates.Reset();
SelectState(SharedInstanceData, RootState, OutTransition.NextActiveStates);
STATETREE_LOG(Verbose, TEXT("Restart tree execution from state'%s'"), *GetSafeStateName(RootState));
return true;
}
// 如果没有转移条件满足,则持续更新以后状态节点
return false;
}
EnterState
接下来是负责进入状态逻辑的 FStateTreeExecutionContext::EnterState 函数,其代码如下:
EStateTreeRunStatus FStateTreeExecutionContext::EnterState(const FStateTreeTransitionResult& Transition)
{
// 记录进入状态的工夫
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(StateTree_EnterState);
// 如果下一个激活状态为空,间接返回运行失败
if (Transition.NextActiveStates.IsEmpty())
{return EStateTreeRunStatus::Failed;}
// 调配新工作,即依据以后和下一个激活状态更新实例数据
UpdateInstanceData(Transition.CurrentActiveStates, Transition.NextActiveStates);
// 获取执行状态并对其进行更新
FStateTreeExecutionState& Exec = GetExecState();
Exec.StateChangeCount++;
// "在指标分支上" 示意该状态是以后转移的指标状态或其子状态。// 之前处于活动状态且仍放弃活动状态但不在指标分支上的状态不会被调用 EnterState。也就是说,一个转移被视为“从此状态从新打算”。bool bOnTargetBranch = false;
// 将转移后果复制一份
FStateTreeTransitionResult CurrentTransition = Transition;
// 设置状态树运行状态为运行中
EStateTreeRunStatus Result = EStateTreeRunStatus::Running;
// 重置 EnterState 失败工作索引和激活状态列表
Exec.EnterStateFailedTaskIndex = FStateTreeIndex16::Invalid; // This will make all tasks to be accepted.
Exec.ActiveStates.Reset();
// 依据 Transition.NextActiveStates 遍历所有下一个激活状态
for (int32 Index = 0; Index < Transition.NextActiveStates.Num() && Result != EStateTreeRunStatus::Failed; Index++)
{
// 获取以后激活状态的句柄与前一个激活状态的句柄
const FStateTreeStateHandle CurrentHandle = Transition.NextActiveStates[Index];
const FStateTreeStateHandle PreviousHandle = Transition.CurrentActiveStates.GetStateSafe(Index);
// 获取以后状态的压缩存储构造
const FCompactStateTreeState& State = StateTree.States[CurrentHandle.Index];
// 将以后状态的句柄增加到激活状态列表中
if (!Exec.ActiveStates.Push(CurrentHandle))
{
// 如果无奈增加,示意达到了最大执行深度,记录错误信息并退出遍历
STATETREE_LOG(Error, TEXT("%s: Reached max execution depth when trying to enter state'%s'.'%s'using StateTree'%s'."),
ANSI_TO_TCHAR(__FUNCTION__), *GetStateStatusString(Exec), *GetNameSafe(&Owner), *GetFullNameSafe(&StateTree));
break;
}
// 如果状态是链接类型,则更新其属性
if (State.Type == EStateTreeStateType::Linked)
{UpdateLinkedStateParameters(State, InstanceStructIndex);
InstanceStructIndex++;
}
// 如果状态是子树类型,则更新其参数
else if (State.Type == EStateTreeStateType::Subtree)
{UpdateSubtreeStateParameters(State);
}
// 记录以后状态是否在指标分支上,并确定以后状态是否须要进入状态
bOnTargetBranch = bOnTargetBranch || CurrentHandle == Transition.TargetState;
const bool bWasActive = PreviousHandle == CurrentHandle;
const bool bIsEnteringState = !bWasActive || bOnTargetBranch;
// 更新转移后果的以后状态和状态扭转类型
CurrentTransition.CurrentState = CurrentHandle;
CurrentTransition.ChangeType = bWasActive ? EStateTreeStateChangeType::Sustained : EStateTreeStateChangeType::Changed;
// 打印进入状态日志
STATETREE_CLOG(bIsEnteringState, Log, TEXT("%*sEnter state'%s'%s"), Index*UE::StateTree::DebugIndentSize, TEXT(""), *DebugGetStatePath(Transition.NextActiveStates, Index), *UEnum::GetValueAsString(CurrentTransition.ChangeType));
// 激活以后状态中的工作
for (int32 TaskIndex = State.TasksBegin; TaskIndex < (State.TasksBegin + State.TasksNum); TaskIndex++)
{
// 获取当前任务并与其状态中的数据关联
const FStateTreeTaskBase& Task = StateTree.Nodes[TaskIndex].Get<FStateTreeTaskBase>();
SetNodeDataView(Task, InstanceStructIndex, InstanceObjectIndex);
// 复制绑定属性
if (Task.BindingsBatch.IsValid())
{StateTree.PropertyBindings.CopyTo(DataViews, Task.BindingsBatch, DataViews[Task.DataViewIndex.Get()]);
}
// 判断是否须要调用 EnterState 函数
const bool bShouldCallStateChange = CurrentTransition.ChangeType == EStateTreeStateChangeType::Changed
|| (CurrentTransition.ChangeType == EStateTreeStateChangeType::Sustained && Task.bShouldStateChangeOnReselect);
if (bIsEnteringState && bShouldCallStateChange)
{
// 打印工作进入状态日志
STATETREE_LOG(Verbose, TEXT("%*s Notify Task'%s'"), Index*UE::StateTree::DebugIndentSize, TEXT(""), *Task.Name.ToString());
// 调用工作的 EnterState 函数并记录返回后果
QUICK_SCOPE_CYCLE_COUNTER(StateTree_Task_EnterState);
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(StateTree_Task_EnterState);
const EStateTreeRunStatus Status = Task.EnterState(*this, CurrentTransition);
// 如果工作进入状态失败,则记录失败索引,并进行遍历
if (Status == EStateTreeRunStatus::Failed)
{Exec.EnterStateFailedTaskIndex = FStateTreeIndex16(TaskIndex);
Result = Status;
break;
}
}
}
}
return Result;
}
ExitState
有了 EnterState 天然也要有 ExitState:
void FStateTreeExecutionContext::ExitState(const FStateTreeTransitionResult& Transition)
{
// 记录函数运行工夫的计时器
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(StateTree_ExitState);
// 查看以后须要退出的状态是否为空,若为空则间接返回
if (Transition.CurrentActiveStates.IsEmpty())
{return;}
// 重置转换提早计时器
FStateTreeExecutionState& Exec = GetExecState();
Exec.GatedTransitionIndex = FStateTreeIndex16::Invalid;
Exec.GatedTransitionTime = 0.0f;
// 标记以后状态是否在指标分支上
bool bOnTargetBranch = false;
// 存储须要退出的状态以及须要调用 ExitState()函数的工作相干数据
FStateTreeStateHandle ExitedStates[FStateTreeActiveStates::MaxStates];
EStateTreeStateChangeType ExitedStateChangeType[FStateTreeActiveStates::MaxStates];
int32 ExitedStateActiveIndex[FStateTreeActiveStates::MaxStates];
int32 NumExitedStates = 0;
// 在待退出的所有状态上执行 ExitState()操作,并标记是否在指标状态分支上
check(Exec.FirstTaskStructIndex.IsValid() && Exec.FirstTaskObjectIndex.IsValid());
int32 InstanceStructIndex = Exec.FirstTaskStructIndex.Get();
int32 InstanceObjectIndex = Exec.FirstTaskObjectIndex.Get();
for (int32 Index = 0; Index < Transition.CurrentActiveStates.Num(); Index++)
{
// 获取以后状态和指标状态
const FStateTreeStateHandle CurrentHandle = Transition.CurrentActiveStates[Index];
const FStateTreeStateHandle NextHandle = Transition.NextActiveStates.GetStateSafe(Index);
const FCompactStateTreeState& State = StateTree.States[CurrentHandle.Index];
// 更新 Linked 类型的状态参数
if (State.Type == EStateTreeStateType::Linked)
{UpdateLinkedStateParameters(State, InstanceStructIndex);
InstanceStructIndex++;
}
// 更新 Subtree 类型的状态参数
else if (State.Type == EStateTreeStateType::Subtree)
{UpdateSubtreeStateParameters(State);
}
// 判断以后状态是否在指标分支上
const bool bRemainsActive = NextHandle == CurrentHandle;
bOnTargetBranch = bOnTargetBranch || NextHandle == Transition.TargetState;
const EStateTreeStateChangeType ChangeType = bRemainsActive ? EStateTreeStateChangeType::Sustained : EStateTreeStateChangeType::Changed;
// 如果以后状态不在指标分支上,设置须要退出的状态及相干数据
if (!bRemainsActive || bOnTargetBranch)
{check(NumExitedStates < FStateTreeActiveStates::MaxStates);
ExitedStates[NumExitedStates] = CurrentHandle;
ExitedStateChangeType[NumExitedStates] = ChangeType;
ExitedStateActiveIndex[NumExitedStates] = Index;
NumExitedStates++;
}
// 执行以后状态的工作,包含拷贝属性和执行工作实现回调
for (int32 TaskIndex = State.TasksBegin; TaskIndex < (State.TasksBegin + State.TasksNum); TaskIndex++)
{const FStateTreeTaskBase& Task = StateTree.Nodes[TaskIndex].Get<FStateTreeTaskBase>();
SetNodeDataView(Task, InstanceStructIndex, InstanceObjectIndex);
// 拷贝绑定属性
if (Task.BindingsBatch.IsValid())
{StateTree.PropertyBindings.CopyTo(DataViews, Task.BindingsBatch, DataViews[Task.DataViewIndex.Get()]);
}
}
}
// 反向执行须要退出的状态及工作回调
FStateTreeTransitionResult CurrentTransition = Transition;
for (int32 Index = NumExitedStates - 1; Index >= 0; Index--)
{const FStateTreeStateHandle CurrentHandle = ExitedStates[Index];
const FCompactStateTreeState& State = StateTree.States[CurrentHandle.Index];
// 设置以后 Transition 的状态、状态变动类型等参数,并打印信息
CurrentTransition.CurrentState = CurrentHandle;
CurrentTransition.ChangeType = ExitedStateChangeType[Index];
STATETREE_LOG(Log, TEXT("%*sExit state'%s'%s"), Index*UE::StateTree::DebugIndentSize, TEXT(""), *DebugGetStatePath(Transition.CurrentActiveStates, ExitedStateActiveIndex[Index]), *UEnum::GetValueAsString(CurrentTransition.ChangeType));
// 执行该状态下的所有工作
for (int32 TaskIndex = (State.TasksBegin + State.TasksNum) - 1; TaskIndex >= State.TasksBegin; TaskIndex--)
{// 如果 EnterState()被调用,则执行工作实现回调函数
if (TaskIndex <= Exec.EnterStateFailedTaskIndex.Get())
{const FStateTreeTaskBase& Task = StateTree.Nodes[TaskIndex].Get<FStateTreeTaskBase>();
// 在状态变动时应该调用工作实现回调函数,须要满足两个条件:以后状态产生了更改,或者以后状态与上一个状态雷同(示意从新进入该状态),且该工作的 bShouldStateChangeOnReselect 参数为 true
const bool bShouldCallStateChange = CurrentTransition.ChangeType == EStateTreeStateChangeType::Changed
|| (CurrentTransition.ChangeType == EStateTreeStateChangeType::Sustained && Task.bShouldStateChangeOnReselect);
if (bShouldCallStateChange)
{STATETREE_LOG(Verbose, TEXT("%*s Notify Task'%s'"), Index*UE::StateTree::DebugIndentSize, TEXT(""), *Task.Name.ToString());
// 调用工作实现回调函数
{QUICK_SCOPE_CYCLE_COUNTER(StateTree_Task_ExitState);
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(StateTree_Task_ExitState);
Task.ExitState(*this, CurrentTransition);
}
}
}
}
}
}
最初当然还有 Stop 函数,不过都是一些重置和终止的操作,在此就不做过多介绍了。
总结
作为 UE5 新引入的 AI 架构,StateTree 无疑会为 AI 开发带来新的生机和组织形式,理解其工作原理可能帮忙咱们更好的使用,也能触类旁通汲取成为本人的常识。