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开发带来新的活力和组织方式,了解其工作原理能够帮助我们更好的运用,也能举一反三吸取成为自己的知识。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。