UE蓝图 序列(Sequence)节点和源码

本文详细介绍了UnrealEngine中UE蓝图的ExecutionSequence节点的功能、用法,包括节点的创建、连接、条件控制和实现原理,以及相关源码分析。涵盖了节点在游戏逻辑控制、状态管理和资源管理等方面的应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

系列文章目录

UE蓝图 Get节点和源码
UE蓝图 Set节点和源码
UE蓝图 Cast节点和源码
UE蓝图 分支(Branch)节点和源码
UE蓝图 入口(FunctionEntry)节点和源码
UE蓝图 返回结果(FunctionResult)节点和源码
UE蓝图 函数调用(CallFunction)节点和源码
UE蓝图 序列(Sequence)节点和源码



一、序列节点功能

在这里插入图片描述

UE(Unreal Engine)蓝图中的ExecutionSequence节点是一个非常重要的节点,用于控制执行序列中的不同分支。它主要用于生成两种类型的语句:KCST_PushState和KCST_UnconditionGoto。

KCST_PushState语句通过EmitPushExecState函数处理,它首先输出指令EX_PushExecutionFlow,然后写入执行序列节点的下一个分支的字节码偏移。这样,当这个执行流程结束时,会执行EX_PopExecutionFlow指令,从而取出这个偏移并执行接下来的字节码。

二、ExecutionSequence节点用法

UE蓝图中的ExecutionSequence节点主要用于控制执行序列中的不同分支。以下是ExecutionSequence节点的基本用法:

  1. 创建ExecutionSequence节点:在UE蓝图中,可以通过右键点击空白处弹出选择列表窗口,然后选择“Sequence”来创建一个ExecutionSequence节点。
  2. 连接其他节点:将需要执行的节点连接到ExecutionSequence节点上。可以根据需要添加多个节点,并按照执行的顺序将它们连接到ExecutionSequence节点上。
  3. 设置执行条件:ExecutionSequence节点可以根据条件选择执行不同的分支。可以通过添加Condition节点或其他条件判断节点来设置执行条件。
  4. 控制执行流程:ExecutionSequence节点会按照连接的节点的顺序执行它们。当遇到条件判断节点时,会根据条件的结果选择执行不同的分支。
  5. 使用KCST_PushState和KCST_UnconditionGoto:ExecutionSequence节点主要会生成KCST_PushState和KCST_UnconditionGoto两个Statement。这些Statement用于控制执行流程的跳转和状态管理。

三、序列使用场景

在Unreal Engine(UE)的蓝图中,序列节点(如ExecutionSequence节点)的使用场景非常广泛。这些节点主要用于控制游戏逻辑的流程,确保各个事件和动作按照预定的顺序执行。以下是一些常见的UE蓝图序列节点的使用场景:

  1. 初始化流程:在游戏对象的初始化过程中,可以使用序列节点来组织和管理初始化流程。例如,在角色创建时,可以使用序列节点来确保先加载角色的模型,然后设置角色的初始状态,最后为角色添加技能和装备。

  2. 任务与事件触发:在游戏任务或事件系统中,序列节点可以用于定义任务或事件的执行流程。例如,在任务执行过程中,可以使用序列节点来控制任务目标的完成顺序,或者在任务完成后触发特定的奖励或事件。

  3. 状态管理:序列节点可以用于实现游戏对象的状态管理。通过创建不同的状态转换逻辑,可以在不同状态之间进行切换,以满足游戏的各种需求。例如,在角色控制系统中,可以使用序列节点来管理角色的不同战斗状态、移动状态等。

  4. 动画与音效同步:在游戏动画和音效处理方面,序列节点可以用于控制动画和音效的播放顺序和同步。通过精心设计的序列节点,可以实现动画、音效与游戏逻辑的完美融合,提升游戏体验。

  5. 资源加载与释放:在游戏资源管理方面,序列节点可以用于控制资源的加载、释放和更新流程。通过合理地组织资源加载和释放流程,可以提高游戏的性能和响应速度。

四、实现原理

  • 创建输入输出引脚
void UK2Node_ExecutionSequence::AllocateDefaultPins()
{
	CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Execute);
	CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, GetPinNameGivenIndex(0));
	CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, GetPinNameGivenIndex(1));
}
  • 调用FKCHandler_ExecutionSequence.RegisterNets注册函数引脚
  • 调用Compile编译创建Statement
for (int32 i = OutputPins.Num() - 1; i > 0; i--)
{
	FBlueprintCompiledStatement& PushExecutionState = Context.AppendStatementForNode(Node);
	PushExecutionState.Type = KCST_PushState;
	Context.GotoFixupRequestMap.Add(&PushExecutionState, OutputPins[i]);
}
// Immediately jump to the first pin
UEdGraphNode* NextNode = OutputPins[0]->LinkedTo[0]->GetOwningNode();
FBlueprintCompiledStatement& NextExecutionState = Context.AppendStatementForNode(Node);
NextExecutionState.Type = KCST_UnconditionalGoto;
Context.GotoFixupRequestMap.Add(&NextExecutionState, OutputPins[0]);

五、相关源码

源码文件:
K2Node_ExecutionSequence.h
K2Node_ExecutionSequence.cpp
相关类:
FKCHandler_ExecutionSequence
K2Node_ExecutionSequence


class FKCHandler_ExecutionSequence : public FNodeHandlingFunctor
{
public:
	FKCHandler_ExecutionSequence(FKismetCompilerContext& InCompilerContext)
		: FNodeHandlingFunctor(InCompilerContext)
	{
	}

	virtual void Compile(FKismetFunctionContext& Context, UEdGraphNode* Node) override
	{
		// Make sure that the input pin is connected and valid for this block
		FEdGraphPinType ExpectedPinType;
		ExpectedPinType.PinCategory = UEdGraphSchema_K2::PC_Exec;

		UEdGraphPin* ExecTriggeringPin = Context.FindRequiredPinByName(Node, UEdGraphSchema_K2::PN_Execute, EGPD_Input);
		if ((ExecTriggeringPin == nullptr) || !Context.ValidatePinType(ExecTriggeringPin, ExpectedPinType))
		{
			CompilerContext.MessageLog.Error(*LOCTEXT("NoValidExecutionPinForExecSeq_Error", "@@ must have a valid execution pin @@").ToString(), Node, ExecTriggeringPin);
			return;
		}
		else if (ExecTriggeringPin->LinkedTo.Num() == 0)
		{
			CompilerContext.MessageLog.Warning(*LOCTEXT("NodeNeverExecuted_Warning", "@@ will never be executed").ToString(), Node);
			return;
		}

		// Find the valid, connected output pins, and add them to the processing list
		TArray<UEdGraphPin*> OutputPins;
		for (UEdGraphPin* CurrentPin : Node->Pins)
		{
			if ((CurrentPin->Direction == EGPD_Output) && (CurrentPin->LinkedTo.Num() > 0) && (CurrentPin->PinName.ToString().StartsWith(UEdGraphSchema_K2::PN_Then.ToString())))
			{
				OutputPins.Add(CurrentPin);
			}
		}

		//@TODO: Sort the pins by the number appended to the pin!

		// Process the pins, if there are any valid entries
		if (OutputPins.Num() > 0)
		{
			if (Context.IsDebuggingOrInstrumentationRequired() && (OutputPins.Num() > 1))
			{
				const FString NodeComment = Node->NodeComment.IsEmpty() ? Node->GetName() : Node->NodeComment;

				// Assuming sequence X goes to A, B, C, we want to emit:
				//   X: push X1
				//      goto A
				//  X1: debug site
				//      push X2
				//      goto B
				//  X2: debug site
				//      goto C

				// A push statement we need to patch up on the next pass (e.g., push X1 before we know where X1 is)
				FBlueprintCompiledStatement* LastPushStatement = NULL;

				for (int32 i = 0; i < OutputPins.Num(); ++i)
				{
					// Emit the debug site and patch up the previous jump if we're on subsequent steps
					const bool bNotFirstIndex = i > 0;
					if (bNotFirstIndex)
					{
						// Emit a debug site
						FBlueprintCompiledStatement& DebugSiteAndJumpTarget = Context.AppendStatementForNode(Node);
						DebugSiteAndJumpTarget.Type = Context.GetBreakpointType();
						DebugSiteAndJumpTarget.Comment = NodeComment;
						DebugSiteAndJumpTarget.bIsJumpTarget = true;

						// Patch up the previous push jump target
						check(LastPushStatement);
						LastPushStatement->TargetLabel = &DebugSiteAndJumpTarget;
					}

					// Emit a push to get to the next step in the sequence, unless we're the last one or this is an instrumented build
					const bool bNotLastIndex = ((i + 1) < OutputPins.Num());
					if (bNotLastIndex)
					{
						FBlueprintCompiledStatement& PushExecutionState = Context.AppendStatementForNode(Node);
						PushExecutionState.Type = KCST_PushState;
						LastPushStatement = &PushExecutionState;
					}

					// Emit the goto to the actual state
					FBlueprintCompiledStatement& GotoSequenceLinkedState = Context.AppendStatementForNode(Node);
					GotoSequenceLinkedState.Type = KCST_UnconditionalGoto;
					Context.GotoFixupRequestMap.Add(&GotoSequenceLinkedState, OutputPins[i]);
				}

				check(LastPushStatement);
			}
			else
			{
				// Directly emit pushes to execute the remaining branches
				for (int32 i = OutputPins.Num() - 1; i > 0; i--)
				{
					FBlueprintCompiledStatement& PushExecutionState = Context.AppendStatementForNode(Node);
					PushExecutionState.Type = KCST_PushState;
					Context.GotoFixupRequestMap.Add(&PushExecutionState, OutputPins[i]);
				}

				// Immediately jump to the first pin
				UEdGraphNode* NextNode = OutputPins[0]->LinkedTo[0]->GetOwningNode();
				FBlueprintCompiledStatement& NextExecutionState = Context.AppendStatementForNode(Node);
				NextExecutionState.Type = KCST_UnconditionalGoto;
				Context.GotoFixupRequestMap.Add(&NextExecutionState, OutputPins[0]);
			}
		}
		else
		{
			FBlueprintCompiledStatement& NextExecutionState = Context.AppendStatementForNode(Node);
			NextExecutionState.Type = KCST_EndOfThread;
		}
	}
};

UK2Node_ExecutionSequence::UK2Node_ExecutionSequence(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
}

void UK2Node_ExecutionSequence::AllocateDefaultPins()
{
	CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Execute);

	// Add two default pins
	CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, GetPinNameGivenIndex(0));
	CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, GetPinNameGivenIndex(1));

	Super::AllocateDefaultPins();
}

FText UK2Node_ExecutionSequence::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
	return NSLOCTEXT("K2Node", "Sequence", "Sequence");
}

FSlateIcon UK2Node_ExecutionSequence::GetIconAndTint(FLinearColor& OutColor) const
{
	static FSlateIcon Icon("EditorStyle", "GraphEditor.Sequence_16x");
	return Icon;
}

FLinearColor UK2Node_ExecutionSequence::GetNodeTitleColor() const
{
	return FLinearColor::White;
}

FText UK2Node_ExecutionSequence::GetTooltipText() const
{
	return NSLOCTEXT("K2Node", "ExecutePinInOrder_Tooltip", "Executes a series of pins in order");
}

FName UK2Node_ExecutionSequence::GetUniquePinName()
{
	FName NewPinName;
	int32 i = 0;
	while (true)
	{
		NewPinName = GetPinNameGivenIndex(i++);
		if (!FindPin(NewPinName))
		{
			break;
		}
	}

	return NewPinName;
}

void UK2Node_ExecutionSequence::AddInputPin()
{
	Modify();
	CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, GetUniquePinName());
}

void UK2Node_ExecutionSequence::InsertPinIntoExecutionNode(UEdGraphPin* PinToInsertBefore, EPinInsertPosition Position)
{
	Modify();

	int32 DesiredPinIndex = Pins.Find(PinToInsertBefore);
	if (DesiredPinIndex != INDEX_NONE)
	{
		if (Position == EPinInsertPosition::After)
		{
			DesiredPinIndex = DesiredPinIndex + 1;
		}

		FCreatePinParams Params;
		Params.Index = DesiredPinIndex;
		CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, GetUniquePinName(), Params);

		// refresh names on the pin list:
		int32 ThenIndex = 0;
		for (int32 Idx = 0; Idx < Pins.Num(); ++Idx)
		{
			UEdGraphPin* PotentialPin = Pins[Idx];
			if (UEdGraphSchema_K2::IsExecPin(*PotentialPin) && (PotentialPin->Direction == EGPD_Output))
			{
				PotentialPin->PinName = GetPinNameGivenIndex(ThenIndex);
				++ThenIndex;
			}
		}
	}
}

void UK2Node_ExecutionSequence::RemovePinFromExecutionNode(UEdGraphPin* TargetPin) 
{
	UK2Node_ExecutionSequence* OwningSeq = Cast<UK2Node_ExecutionSequence>( TargetPin->GetOwningNode() );
	if (OwningSeq)
	{
		OwningSeq->Pins.Remove(TargetPin);
		TargetPin->MarkPendingKill();

		// Renumber the pins so the numbering is compact
		int32 ThenIndex = 0;
		for (int32 i = 0; i < OwningSeq->Pins.Num(); ++i)
		{
			UEdGraphPin* PotentialPin = OwningSeq->Pins[i];
			if (UEdGraphSchema_K2::IsExecPin(*PotentialPin) && (PotentialPin->Direction == EGPD_Output))
			{
				PotentialPin->PinName = GetPinNameGivenIndex(ThenIndex);
				++ThenIndex;
			}
		}
	}
}

bool UK2Node_ExecutionSequence::CanRemoveExecutionPin() const
{
	int32 NumOutPins = 0;

	for (int32 i = 0; i < Pins.Num(); ++i)
	{
		UEdGraphPin* PotentialPin = Pins[i];
		if (UEdGraphSchema_K2::IsExecPin(*PotentialPin) && (PotentialPin->Direction == EGPD_Output))
		{
			NumOutPins++;
		}
	}

	return (NumOutPins > 2);
}

FName UK2Node_ExecutionSequence::GetPinNameGivenIndex(int32 Index) const
{
	return *FString::Printf(TEXT("%s_%d"), *UEdGraphSchema_K2::PN_Then.ToString(), Index);
}

void UK2Node_ExecutionSequence::ReallocatePinsDuringReconstruction(TArray<UEdGraphPin*>& OldPins)
{
	Super::AllocateDefaultPins();

	// Create the execution input pin
	CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Execute);

	// Create a new pin for each old execution output pin, and coerce the names to match on both sides
	int32 ExecOutPinCount = 0;
	for (int32 i = 0; i < OldPins.Num(); ++i)
	{
		UEdGraphPin* TestPin = OldPins[i];
		if (UEdGraphSchema_K2::IsExecPin(*TestPin) && (TestPin->Direction == EGPD_Output))
		{
			const FName NewPinName(GetPinNameGivenIndex(ExecOutPinCount));
			ExecOutPinCount++;

			// Make sure the old pin and new pin names match
			TestPin->PinName = NewPinName;

			// Create the new output pin to match
			CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, NewPinName);
		}
	}
}

UEdGraphPin* UK2Node_ExecutionSequence::GetThenPinGivenIndex(const int32 Index) 
{
	return FindPin(GetPinNameGivenIndex(Index));
}

FNodeHandlingFunctor* UK2Node_ExecutionSequence::CreateNodeHandler(FKismetCompilerContext& CompilerContext) const
{
	return new FKCHandler_ExecutionSequence(CompilerContext);
}
}
### UE5 蓝图中延迟节点的使用方法 在Unreal Engine 5 (UE5) 中,蓝图提供了多种方式来处理异步操作时间延迟。为了实现更复杂的逻辑控制,尤其是涉及到多个对象的同时处理或特定条件下的延时触发,开发者通常会选择自定义异步蓝图节点。 #### 自定义异步蓝图节点 `SetTimer` 的创建流程 当标准的Delay节点无法满足项目需求时,可以通过C++编写自定义的异步蓝图节点。以`SetTimer`为例,这个节点允许设置定时器、支持取消功能,并能同时管理多个目标对象的操作[^1]: ```cpp // 定义一个新的计时器句柄用于存储每个激活的计时器 FTimerHandle TimerHandle; // 设置一个一次性计时器,在指定的时间间隔后执行给定的任务 GetWorld()->GetTimerManager().SetTimer(TimerHandle, this, &AYourClass::OnTimeExpired, TimeSeconds, false); void AYourClass::OnTimeExpired() { // 这里放置计时结束后要执行的动作代码... } ``` 上述例子展示了如何利用C++为蓝图添加新的异步行为——即设定一个固定时间段后的回调函数。这种方式不仅限于简单的等待效果;还可以扩展成更加复杂的功能模块,比如带有进度条显示的倒计时机制等。 #### 替代方案:隐藏Actor模拟Delay 对于某些场景下遇到的Delay节点不生效的情况,有一种变通的方法是先将待销毁的对象设为不可见状态(`SetActorHiddenInGame`)而不是立即摧毁它(`DestroyActor`)。这样做的好处是可以确保所有依赖此对象存在的后续动作能够顺利完成后再彻底移除该实体[^4]: ```blueprint Event BeginPlay ├── Set Actor Hidden In Game (NewHidden=True) └── Sequence ├── Your Delay Logic Here └── Destroy Actor ``` 这种方法特别适用于那些需要精确控制资源释放时机的游戏开发环节,如角色死亡动画播放完毕再消失、物品拾取反馈完成后清理等场合。
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shandongwill

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值