首页 > 编程语言 >UE5新功能StateTree源码解析

UE5新功能StateTree源码解析

时间:2023-04-22 15:46:34浏览次数:49  
标签:状态 const Exec Transition State 源码 UE5 StateTree

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开发带来新的活力和组织方式,了解其工作原理能够帮助我们更好的运用,也能举一反三吸取成为自己的知识。

标签:状态,const,Exec,Transition,State,源码,UE5,StateTree
From: https://www.cnblogs.com/moonmagician/p/17343176.html

相关文章

  • 04:基础入门-WEB源码拓展
    前言:WEB源码在安全测试中是非常重要的信息来源,可以用来代码审计漏洞也可以用来做信息突破口,其中WEB源码有很多技术需要简明分析。比如:获取某ASP源码后可以采用默认数据库下载为突破,获取某其他脚本源码漏洞可以进行代码审计挖掘或分析其业务逻辑等,总之源码的获取将为后期的安全......
  • Visual Studio Code开发常用的工具栏选项,查看源码技巧以及【vscode常用的快捷键】
    一、开发常用的工具栏选项1、当前打开的文件快速在左侧资源树中定位:其实打开了当前的文件已经有在左侧资源树木定位了,只是颜色比较浅2、打开太多文件的时候,可以关闭3、设置查看当前类或文件的结构OUTLINE相当于idea查看当前类或接口的结构Structure二、查看源码技巧:(1)Ctr+鼠标......
  • Vue3快速上手+俩种创建方式+主要源码讲解
    一.Vue3快速上手2020年9月19日凌晨,尤雨溪正式发布了Vue.js3.0版本,代号:OnePiece。此框架新的主要版本提供了更好的性能、更小的捆绑包体积、更好的TypeScript集成、用于处溪理大规模用例的新API,并为框架未来的长期迭代奠定了坚实的基础。3.0版本的开发周期长达两年多,期间......
  • boot-admin整合flowable官方editor-app源码进行BPMN2-0建模(续)
    boot-admin整合flowable官方editor-app源码进行BPMN2-0建模(续)书接上回项目源码仓库github项目源码仓库giteeboot-admin是一款采用前后端分离模式、基于SpringCloud微服务架构的SaaS后台管理框架。系统内置基础管理、权限管理、运行管理、定义管理、代码生成器和办公管理6个功......
  • cesium源码编译调试及调用全过程
    完整记录一次cesium源码从下载、打包、调用、调试的全过程。本文使用软件或API版本:VSCodeNode:12.18.3cesium版本:1.94总体步骤:下载源码执行npminstall和npmstart启动web服务打包源码(打包前可以先将申请到的cesium的token更改到ion.js文件中的默认值中)运行测试html页面......
  • 动态线程池DynamicTP源码分析
    一、简述dynamic-tp是一个轻量级的动态线程池插件,它是一个基于配置中心的动态线程池,线程池的参数可以通过配置中心配置进行动态的修改,目前支持的配置中心有Apollo,Nacos和Zookeeper,同时dynamic-tp支持线程池的监控和报警,具体特性如下:基于Spring框架,现只支持SpringBoot项目使用,......
  • 关于互助游戏系统开发项目方案讲解(成熟源码)
    区块链是一种去中心化的分布式账本技术,是比币实现的技术基础。区块链数据是分散在网络中的各个节点上,每个节点都有完整的数据副本,通过算法的共识来保证数据的一致性和可信性搭建lovei130908。functiontryMul(uint256a,uint256b)internalpurereturns(bool,uint......
  • DRF的权限组件(源码分析)
    DRF的权限组件(源码分析)1.创建用户表fromdjango.dbimportmodels#Createyourmodelshere.classUserInfo(models.Model):role_choice=((1,'CEO'),(2,'CTO'),(3,'CFO'))role=models.SmallIntegerField(verbose_name='类型&......
  • Spring源码系列(补充):详解ApplicationContext
    前言在之前的文章中,我们已经对Spring源码中的一些核心概念进行了分析。由于篇幅限制,我们并没有详细解释ApplicationContext类所继承的父接口及其作用。因此,本文将单独为ApplicationContext进行详细说明,包括其继承的父接口及其作用。ApplicationContext父接口MessageSource大家......
  • 在idea中查看源码时 download source failed的处理办法
    检查ideamaven配置切换路径BuildTools->Maven->Importing勾选前两个执行maven命令:mvndependency:resolve-Dclassifier=sources执行完后,再次打开源码类就能看到源码了。......