系列文章目录
UE蓝图 Get节点和源码
UE蓝图 Set节点和源码
UE蓝图 Cast节点和源码
UE蓝图 分支(Branch)节点和源码
文章目录
- 系列文章目录
- 一、分支节点功能
- 二、分支节点用法
- 三、分支节点使用场景
- 四、分支节点实现过程
- 五、分支节点相关源码
一、分支节点功能
在Unreal Engine(UE)的蓝图中,Branch
节点是一个非常重要的的逻辑控制节点,它允许你根据某个条件(Condition)的真假来执行不同的逻辑路径。这个节点在编程中相当于一个if-else语句,用于控制程序的流程。
在UE蓝图中使用Branch
节点时,你通常会连接一个布尔值(Boolean)到节点的Condition引脚。这个布尔值可以是来自其他节点的输出,也可以是一个固定的值(如true或false)。然后,你可以在IfTrue
和IfFalse
两个输出引脚上连接你想要执行的操作或逻辑。
- 如果Condition引脚接收到的值为true,那么从
IfTrue
引脚引出的逻辑将会被执行。 - 如果Condition引脚接收到的值为false,那么从
IfFalse
引脚引出的逻辑将会被执行。
这样,你就可以根据不同的条件来动态地改变你的游戏或应用程序的行为。这在制作交互式游戏、响应玩家输入、处理不同的事件场景等方面都非常有用。
二、分支节点用法
在Unreal Engine(UE)的蓝图中使用分支节点,你需要按照以下步骤进行操作:
-
创建分支节点:首先,在蓝图中右击,选择“流程控制” -> “分支”来创建一个分支节点。这将添加一个具有条件(Condition)输入和两个输出引脚(True和False)的节点。
-
连接条件输入:接下来,你需要将一个布尔值或表达式连接到分支节点的
Condition
输入引脚。这个布尔值或表达式的结果将决定分支节点执行哪个分支。 -
设置True分支:在分支节点的
True
输出引脚上,你可以连接任何你希望在条件为真时执行的逻辑或操作。这可以是一个函数调用、变量设置、另一个逻辑节点等。 -
设置False分支:同样,在分支节点的
False
输出引脚上,你可以连接在条件为假时应该执行的逻辑或操作。 -
编译和测试:完成节点设置后,确保你的蓝图没有编译错误,并在游戏中测试分支节点的行为。你可以通过改变连接到
Condition
引脚的值来验证分支是否正确执行。 -
调试和优化:如果分支节点的行为不符合预期,使用UE的调试工具来查找问题并进行相应的调整。
记住,分支节点的行为完全依赖于Condition
引脚的输入值。确保你的条件表达式正确无误,并且连接到正确的引脚。此外,分支节点通常用于控制程序的流程,因此确保你的逻辑流程符合你的游戏或应用程序的需求。
三、分支节点使用场景
Unreal Engine(UE)蓝图中的分支节点(Branch Node)具有广泛的应用场景,主要用于实现条件逻辑,让游戏或应用程序的行为能够根据不同的条件进行变化。以下是一些具体的应用场景示例:
-
玩家交互与决策:在游戏中,分支节点可以用于处理玩家的交互和决策。例如,玩家面对一个选择时,分支节点可以根据玩家的选择(由输入事件触发)来执行不同的游戏逻辑,如不同的对话路径、任务结果或游戏事件。
-
AI行为树:在AI行为树中,分支节点用于决定AI角色的行为。根据环境状态、目标优先级或其他条件,分支节点可以帮助AI选择最合适的行动方案,如巡逻、搜寻、攻击或逃跑。
-
游戏事件与状态管理:在游戏逻辑中,分支节点可用于处理游戏事件和状态的变化。例如,根据玩家的当前状态(如健康值、能量水平或武器状态)来执行不同的游戏逻辑,如允许或禁止某些操作,或触发特定的事件。
-
条件性任务执行:在任务或剧情流程中,分支节点允许开发者根据任务条件(如任务状态、玩家行为或环境变量)来执行不同的任务步骤或结果。这有助于创建更丰富、更灵活的剧情体验。
-
网络和多玩家交互:在多人在线游戏中,分支节点可以用于处理网络状态、玩家状态或其他玩家的行为。根据这些条件,游戏可以执行不同的逻辑,如同步玩家状态、处理网络延迟或处理玩家间的交互。
-
动态环境交互:在动态环境中,分支节点可用于处理与环境的交互。例如,根据物体的状态(如是否被破坏、是否被拾取等)来执行不同的逻辑,如改变环境状态、播放音效或触发其他事件。
在这些场景中,分支节点的灵活性使得开发者能够根据游戏或应用程序的具体需求来创建复杂的逻辑结构,从而实现更加丰富和动态的用户体验。通过组合不同的节点类型和条件判断,开发者可以构建出各种复杂的条件分支逻辑。
四、分支节点实现过程
- 创建输入输出引脚
public void allocateDefaultPins() {
createPin(EGPD_Input, EdGraphSchemaK2.PC_Exec, EdGraphSchemaK2.PN_Execute);
EdGraphPin conditionPin = createPin(EGPD_Input, EdGraphSchemaK2.PC_Boolean, EdGraphSchemaK2.PN_Condition);
EdGraphPin truePin = createPin(EGPD_Output, EdGraphSchemaK2.PC_Exec, EdGraphSchemaK2.PN_Then);
EdGraphPin falsePin = createPin(EGPD_Output, EdGraphSchemaK2.PC_Exec, EdGraphSchemaK2.PN_Else);
super.allocateDefaultPins();
}
-
调用KCHandler_Branch 注册net
-
编译创建分支Statement
// First skip the if, if the term is false
FBlueprintCompiledStatement& SkipIfGoto = Context.AppendStatementForNode(Node);
SkipIfGoto.Type = KCST_GotoIfNot;
SkipIfGoto.LHS = *CondTerm;
Context.GotoFixupRequestMap.Add(&SkipIfGoto, ElsePin);
// Now go to the If branch
FBlueprintCompiledStatement& GotoThen = Context.AppendStatementForNode(Node);
GotoThen.Type = KCST_UnconditionalGoto;
GotoThen.LHS = *CondTerm;
Context.GotoFixupRequestMap.Add(&GotoThen, ThenPin);
五、分支节点相关源码
源码文件:
K2Node_IfThenElse.h
K2Node_IfThenElse.cpp
相关类:
KCHandler_Branch
UK2Node_IfThenElse
class FKCHandler_Branch : public FNodeHandlingFunctor
{
public:
FKCHandler_Branch(FKismetCompilerContext& InCompilerContext)
: FNodeHandlingFunctor(InCompilerContext)
{
}
virtual void Compile(FKismetFunctionContext& Context, UEdGraphNode* Node) override
{
// For imperative nodes, make sure the exec function was actually triggered and not just included due to an output data dependency
FEdGraphPinType ExpectedExecPinType;
ExpectedExecPinType.PinCategory = UEdGraphSchema_K2::PC_Exec;
FEdGraphPinType ExpectedBoolPinType;
ExpectedBoolPinType.PinCategory = UEdGraphSchema_K2::PC_Boolean;
UEdGraphPin* ExecTriggeringPin = Context.FindRequiredPinByName(Node, UEdGraphSchema_K2::PN_Execute, EGPD_Input);
if ((ExecTriggeringPin == NULL) || !Context.ValidatePinType(ExecTriggeringPin, ExpectedExecPinType))
{
CompilerContext.MessageLog.Error(*LOCTEXT("NoValidExecutionPinForBranch_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;
}
// Generate the output impulse from this node
UEdGraphPin* CondPin = Context.FindRequiredPinByName(Node, UEdGraphSchema_K2::PN_Condition, EGPD_Input);
UEdGraphPin* ThenPin = Context.FindRequiredPinByName(Node, UEdGraphSchema_K2::PN_Then, EGPD_Output);
UEdGraphPin* ElsePin = Context.FindRequiredPinByName(Node, UEdGraphSchema_K2::PN_Else, EGPD_Output);
if (Context.ValidatePinType(ThenPin, ExpectedExecPinType) &&
Context.ValidatePinType(ElsePin, ExpectedExecPinType) &&
Context.ValidatePinType(CondPin, ExpectedBoolPinType))
{
UEdGraphPin* PinToTry = FEdGraphUtilities::GetNetFromPin(CondPin);
FBPTerminal** CondTerm = Context.NetMap.Find(PinToTry);
if (CondTerm != NULL) //
{
// First skip the if, if the term is false
{
FBlueprintCompiledStatement& SkipIfGoto = Context.AppendStatementForNode(Node);
SkipIfGoto.Type = KCST_GotoIfNot;
SkipIfGoto.LHS = *CondTerm;
Context.GotoFixupRequestMap.Add(&SkipIfGoto, ElsePin);
}
// Now go to the If branch
{
FBlueprintCompiledStatement& GotoThen = Context.AppendStatementForNode(Node);
GotoThen.Type = KCST_UnconditionalGoto;
GotoThen.LHS = *CondTerm;
Context.GotoFixupRequestMap.Add(&GotoThen, ThenPin);
}
}
else
{
CompilerContext.MessageLog.Error(*LOCTEXT("ResolveTermPassed_Error", "Failed to resolve term passed into @@").ToString(), CondPin);
}
}
}
};
UK2Node_IfThenElse::UK2Node_IfThenElse(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
void UK2Node_IfThenElse::AllocateDefaultPins()
{
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Execute);
UEdGraphPin* ConditionPin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Boolean, UEdGraphSchema_K2::PN_Condition);
K2Schema->SetPinAutogeneratedDefaultValue(ConditionPin, TEXT("true"));
UEdGraphPin* TruePin = CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Then);
TruePin->PinFriendlyName =LOCTEXT("true", "true");
UEdGraphPin* FalsePin = CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Else);
FalsePin->PinFriendlyName = LOCTEXT("false", "false");
Super::AllocateDefaultPins();
}
FText UK2Node_IfThenElse::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
return LOCTEXT("Branch", "Branch");
}
FLinearColor UK2Node_IfThenElse::GetNodeTitleColor() const
{
return GetDefault<UGraphEditorSettings>()->ExecBranchNodeTitleColor;
}
FSlateIcon UK2Node_IfThenElse::GetIconAndTint(FLinearColor& OutColor) const
{
static FSlateIcon Icon("EditorStyle", "GraphEditor.Branch_16x");
return Icon;
}
FText UK2Node_IfThenElse::GetTooltipText() const
{
return LOCTEXT("BrancStatement_Tooltip", "Branch Statement\nIf Condition is true, execution goes to True, otherwise it goes to False");
}
UEdGraphPin* UK2Node_IfThenElse::GetThenPin() const
{
UEdGraphPin* Pin = FindPin(UEdGraphSchema_K2::PN_Then);
check(Pin);
return Pin;
}
UEdGraphPin* UK2Node_IfThenElse::GetElsePin() const
{
UEdGraphPin* Pin = FindPin(UEdGraphSchema_K2::PN_Else);
check(Pin);
return Pin;
}
UEdGraphPin* UK2Node_IfThenElse::GetConditionPin() const
{
UEdGraphPin* Pin = FindPin(UEdGraphSchema_K2::PN_Condition);
check(Pin);
return Pin;
}
FNodeHandlingFunctor* UK2Node_IfThenElse::CreateNodeHandler(FKismetCompilerContext& CompilerContext) const
{
return new FKCHandler_Branch(CompilerContext);
}
void UK2Node_IfThenElse::GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const
{
// actions get registered under specific object-keys; the idea is that
// actions might have to be updated (or deleted) if their object-key is
// mutated (or removed)... here we use the node's class (so if the node
// type disappears, then the action should go with it)
UClass* ActionKey = GetClass();
// to keep from needlessly instantiating a UBlueprintNodeSpawner, first
// check to make sure that the registrar is looking for actions of this type
// (could be regenerating actions for a specific asset, and therefore the
// registrar would only accept actions corresponding to that asset)
if (ActionRegistrar.IsOpenForRegistration(ActionKey))
{
UBlueprintNodeSpawner* NodeSpawner = UBlueprintNodeSpawner::Create(GetClass());
check(NodeSpawner != nullptr);
ActionRegistrar.AddBlueprintAction(ActionKey, NodeSpawner);
}
}
FText UK2Node_IfThenElse::GetMenuCategory() const
{
return FEditorCategoryUtils::GetCommonCategory(FCommonEditorCategory::FlowControl);
}