系列文章目录
UE蓝图 Get节点和源码
UE蓝图 Set节点和源码
UE蓝图 Cast节点和源码
UE蓝图 分支(Branch)节点和源码
UE蓝图 入口(FunctionEntry)节点和源码
UE蓝图 返回结果(FunctionResult)节点和源码
UE蓝图 函数调用(CallFunction)节点和源码
UE蓝图 函数调用(CallFunction)节点和源码
UE蓝图 序列(Sequence)节点和源码
文章目录
- 系列文章目录
- 一、序列节点功能
- 二、ExecutionSequence节点用法
- 三、序列使用场景
- 四、实现原理
- 五、相关源码
一、序列节点功能
UE(Unreal Engine)蓝图中的ExecutionSequence节点是一个非常重要的节点,用于控制执行序列中的不同分支。它主要用于生成两种类型的语句:KCST_PushState和KCST_UnconditionGoto。
KCST_PushState语句通过EmitPushExecState函数处理,它首先输出指令EX_PushExecutionFlow,然后写入执行序列节点的下一个分支的字节码偏移。这样,当这个执行流程结束时,会执行EX_PopExecutionFlow指令,从而取出这个偏移并执行接下来的字节码。
二、ExecutionSequence节点用法
UE蓝图中的ExecutionSequence节点主要用于控制执行序列中的不同分支。以下是ExecutionSequence节点的基本用法:
- 创建ExecutionSequence节点:在UE蓝图中,可以通过右键点击空白处弹出选择列表窗口,然后选择“Sequence”来创建一个ExecutionSequence节点。
- 连接其他节点:将需要执行的节点连接到ExecutionSequence节点上。可以根据需要添加多个节点,并按照执行的顺序将它们连接到ExecutionSequence节点上。
- 设置执行条件:ExecutionSequence节点可以根据条件选择执行不同的分支。可以通过添加Condition节点或其他条件判断节点来设置执行条件。
- 控制执行流程:ExecutionSequence节点会按照连接的节点的顺序执行它们。当遇到条件判断节点时,会根据条件的结果选择执行不同的分支。
- 使用KCST_PushState和KCST_UnconditionGoto:ExecutionSequence节点主要会生成KCST_PushState和KCST_UnconditionGoto两个Statement。这些Statement用于控制执行流程的跳转和状态管理。
三、序列使用场景
在Unreal Engine(UE)的蓝图中,序列节点(如ExecutionSequence节点)的使用场景非常广泛。这些节点主要用于控制游戏逻辑的流程,确保各个事件和动作按照预定的顺序执行。以下是一些常见的UE蓝图序列节点的使用场景:
-
初始化流程:在游戏对象的初始化过程中,可以使用序列节点来组织和管理初始化流程。例如,在角色创建时,可以使用序列节点来确保先加载角色的模型,然后设置角色的初始状态,最后为角色添加技能和装备。
-
任务与事件触发:在游戏任务或事件系统中,序列节点可以用于定义任务或事件的执行流程。例如,在任务执行过程中,可以使用序列节点来控制任务目标的完成顺序,或者在任务完成后触发特定的奖励或事件。
-
状态管理:序列节点可以用于实现游戏对象的状态管理。通过创建不同的状态转换逻辑,可以在不同状态之间进行切换,以满足游戏的各种需求。例如,在角色控制系统中,可以使用序列节点来管理角色的不同战斗状态、移动状态等。
-
动画与音效同步:在游戏动画和音效处理方面,序列节点可以用于控制动画和音效的播放顺序和同步。通过精心设计的序列节点,可以实现动画、音效与游戏逻辑的完美融合,提升游戏体验。
-
资源加载与释放:在游戏资源管理方面,序列节点可以用于控制资源的加载、释放和更新流程。通过合理地组织资源加载和释放流程,可以提高游戏的性能和响应速度。
四、实现原理
- 创建输入引脚
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);
}
}