UE蓝图 返回结果(FunctionResult)节点和源码

系列文章目录

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



一、FunctionResult节点功能

在这里插入图片描述

FunctionResult节点是UE蓝图中用于返回函数执行结果的重要节点之一。它可以帮助您将函数的返回值传递给调用者,并在需要时进行调试和测试。FunctionResult节点通常位于函数的末尾。


二、FunctionResult节点用法

以下是使用FunctionResult节点的一般步骤:

  1. 创建FunctionResult节点:在蓝图中,定义了函数的返回参数后,FunctionResult节点会自动创建显示。
  2. 连接输入引脚:如果函数需要输出参数,你可以将相应的引脚连接到FunctionResult节点的输入引脚上。
  3. 使用返回值:你可以将FunctionResult节点的输出引脚连接到其他节点,以便基于函数的返回值执行进一步的操作。

三、使用场景

UE(Unreal Engine)的蓝图系统中的FunctionResult节点具有多种应用场景,主要涉及到需要获取函数返回值并进行进一步处理的情况。以下是一些常见的应用场景示例:

  1. 条件判断

    • 当你的游戏逻辑需要根据某个函数的返回值来做出决策时,可以使用FunctionResult节点来存储这个返回值,并将其连接到条件判断节点(如Branch节点)上。例如,一个函数可能返回一个布尔值来表示某个条件是否满足,你可以使用FunctionResult节点来捕获这个布尔值,并根据其值来执行不同的逻辑分支。
  2. 变量赋值与状态更新

    • 在游戏运行过程中,你可能需要更新某些变量的值或游戏状态。这些变量或状态可能由某个函数的返回值决定。通过FunctionResult节点,你可以获取函数的返回值,并将其赋给蓝图中的变量或用于更新游戏状态。
  3. 事件响应

    • 在UE中,事件是驱动游戏逻辑的重要机制。当某个事件发生时(如用户点击按钮、碰撞检测等),你可能会调用一个函数来处理这个事件,并根据函数的返回值来执行后续操作。FunctionResult节点可以帮助你捕获这些返回值,并据此作出响应。
  4. 数据传递与处理

    • 在复杂的游戏逻辑中,数据可能需要在不同的节点和函数之间传递。FunctionResult节点可以作为数据传递的桥梁,将一个函数的返回值传递给另一个函数或节点进行进一步处理。这种传递可以是数值、对象引用、字符串等各种数据类型。
  5. 异步操作

    • 在UE中,有些函数可能是异步执行的,即它们不会立即返回结果,而是在一段时间后通过回调或事件来通知结果。在这种情况下,你可以使用FunctionResult节点来等待异步操作完成并获取其结果。这通常涉及到使用延迟节点(Delay)和事件调度(Event Dispatch)来管理异步逻辑。

需要注意的是,虽然FunctionResult节点在蓝图系统中非常有用,但它只适用于能够返回值的函数。对于没有返回值的函数(返回类型为void),FunctionResult节点将不会有任何作用。此外,如果函数有多个返回值,你可能需要使用多个FunctionResult节点或者使用结构体(Struct)来封装多个返回值。

总之,FunctionResult节点在UE蓝图系统中广泛应用于条件判断、变量赋值、事件响应、数据传递和异步操作等场景,帮助开发者更加灵活地管理和处理函数的返回值。

四、实现过程

  • 创建输入引脚
  • 调用FKCHandler_FunctionResult.RegisterNet注册返回值引脚
virtual void RegisterNet(FKismetFunctionContext& Context, UEdGraphPin* Net) override
{
	// Do not register as a default any Pin that comes from being Split
	if (Net->ParentPin == nullptr)
	{
		FString NetPinName = Net->PinName.ToString();
		for (FBPTerminal& ResultTerm : Context.Results)
		{
			if ((ResultTerm.Name == NetPinName) && (ResultTerm.Type == Net->PinType))
			{
				Context.NetMap.Add(Net, &ResultTerm);
				return;
			}
		}
		FBPTerminal* Term = new FBPTerminal();
		Context.Results.Add(Term);
		Term->CopyFromPin(Net, MoveTemp(NetPinName));
		Context.NetMap.Add(Net, Term);
	}
}
  • 调用Compile编译创建Statement
	virtual void Compile(FKismetFunctionContext& Context, UEdGraphNode* Node) override
	{
		static const FBoolConfigValueHelper ExecutionAfterReturn(TEXT("Kismet"), TEXT("bExecutionAfterReturn"), GEngineIni);

		if (ExecutionAfterReturn)
		{
			// for backward compatibility only
			FKCHandler_VariableSet::Compile(Context, Node);
		}
		else
		{
			GenerateAssigments(Context, Node);

			if (Context.IsDebuggingOrInstrumentationRequired() && Node)
			{
				FBlueprintCompiledStatement& TraceStatement = Context.AppendStatementForNode(Node);
				TraceStatement.Type = Context.GetWireTraceType();
				TraceStatement.Comment = Node->NodeComment.IsEmpty() ? Node->GetName() : Node->NodeComment;
			}

			// always go to return
			FBlueprintCompiledStatement& GotoStatement = Context.AppendStatementForNode(Node);
			GotoStatement.Type = KCST_GotoReturn;
		}
	}

五、相关源码

源码文件:
K2Node_FunctionResult.h
K2Node_FunctionResult.cpp
相关类:
FKCHandler_FunctionResult
K2Node_FunctionResult
在这里插入图片描述


class FKCHandler_FunctionResult : public FKCHandler_VariableSet
{
public:
	FKCHandler_FunctionResult(FKismetCompilerContext& InCompilerContext)
		: FKCHandler_VariableSet(InCompilerContext)
	{
	}

	virtual void RegisterNet(FKismetFunctionContext& Context, UEdGraphPin* Net) override
	{
		// Do not register as a default any Pin that comes from being Split
		if (Net->ParentPin == nullptr)
		{
			FString NetPinName = Net->PinName.ToString();
			for (FBPTerminal& ResultTerm : Context.Results)
			{
				if ((ResultTerm.Name == NetPinName) && (ResultTerm.Type == Net->PinType))
				{
					Context.NetMap.Add(Net, &ResultTerm);
					return;
				}
			}
			FBPTerminal* Term = new FBPTerminal();
			Context.Results.Add(Term);
			Term->CopyFromPin(Net, MoveTemp(NetPinName));
			Context.NetMap.Add(Net, Term);
		}
	}

	virtual void Compile(FKismetFunctionContext& Context, UEdGraphNode* Node) override
	{
		static const FBoolConfigValueHelper ExecutionAfterReturn(TEXT("Kismet"), TEXT("bExecutionAfterReturn"), GEngineIni);

		if (ExecutionAfterReturn)
		{
			// for backward compatibility only
			FKCHandler_VariableSet::Compile(Context, Node);
		}
		else
		{
			GenerateAssigments(Context, Node);

			if (Context.IsDebuggingOrInstrumentationRequired() && Node)
			{
				FBlueprintCompiledStatement& TraceStatement = Context.AppendStatementForNode(Node);
				TraceStatement.Type = Context.GetWireTraceType();
				TraceStatement.Comment = Node->NodeComment.IsEmpty() ? Node->GetName() : Node->NodeComment;
			}

			// always go to return
			FBlueprintCompiledStatement& GotoStatement = Context.AppendStatementForNode(Node);
			GotoStatement.Type = KCST_GotoReturn;
		}
	}

	virtual bool RequiresRegisterNetsBeforeScheduling() const override
	{
		return true;
	}
};

#if WITH_EDITORONLY_DATA
namespace
{
	void GatherFunctionResultNodeForLocalization(const UObject* const Object, FPropertyLocalizationDataGatherer& PropertyLocalizationDataGatherer, const EPropertyLocalizationGathererTextFlags GatherTextFlags)
	{
		const UK2Node_FunctionResult* const FunctionResultNode = CastChecked<UK2Node_FunctionResult>(Object);

		// Function Result (aka, Return) nodes always report their values as being the default
		// but we still need to gather them as they are the only place the values are defined
		const FString PathToObject = FunctionResultNode->GetPathName();
		for (const UEdGraphPin* Pin : FunctionResultNode->Pins)
		{
			if (!Pin->DefaultTextValue.IsEmpty())
			{
				PropertyLocalizationDataGatherer.GatherTextInstance(Pin->DefaultTextValue, FString::Printf(TEXT("%s.%s"), *PathToObject, *Pin->GetName()), /*bIsEditorOnly*/true);
			}
		}

		PropertyLocalizationDataGatherer.GatherLocalizationDataFromObject(FunctionResultNode, GatherTextFlags);
	}
}
#endif

UK2Node_FunctionResult::UK2Node_FunctionResult(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
#if WITH_EDITORONLY_DATA
	{ static const FAutoRegisterLocalizationDataGatheringCallback AutomaticRegistrationOfLocalizationGatherer(UK2Node_FunctionResult::StaticClass(), &GatherFunctionResultNodeForLocalization); }
#endif
}

FText UK2Node_FunctionResult::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
	if (ENodeTitleType::MenuTitle == TitleType)
	{
		return NSLOCTEXT("K2Node", "ReturnNodeMenuTitle", "Add Return Node...");
	}
	return NSLOCTEXT("K2Node", "ReturnNode", "Return Node");
}

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

	if (UFunction* const Function = FunctionReference.ResolveMember<UFunction>(GetBlueprintClassFromNode()))
	{
		CreatePinsForFunctionEntryExit(Function, /*bIsFunctionEntry=*/ false);
	}

	Super::AllocateDefaultPins();

	FFillDefaultPinValueHelper::FillAll(this);
}

bool UK2Node_FunctionResult::CanCreateUserDefinedPin(const FEdGraphPinType& InPinType, EEdGraphPinDirection InDesiredDirection, FText& OutErrorMessage)
{
	bool bResult = Super::CanCreateUserDefinedPin(InPinType, InDesiredDirection, OutErrorMessage);
	if (bResult)
	{
		if(InDesiredDirection == EGPD_Output)
		{
			OutErrorMessage = NSLOCTEXT("K2Node", "AddOutputPinError", "Cannot add output pins to function result node!");
			bResult = false;
		}
	}
	return bResult;
}

UEdGraphPin* UK2Node_FunctionResult::CreatePinFromUserDefinition(const TSharedPtr<FUserPinInfo> NewPinInfo)
{
	UEdGraphPin* Pin = CreatePin(EGPD_Input, NewPinInfo->PinType, NewPinInfo->PinName);
	CastChecked<UEdGraphSchema_K2>(GetSchema())->SetPinAutogeneratedDefaultValue(Pin, NewPinInfo->PinDefaultValue);
	return Pin;
}

void UK2Node_FunctionResult::FixupPinStringDataReferences(FArchive* SavingArchive)
{
	Super::FixupPinStringDataReferences(SavingArchive);
	if (SavingArchive)
	{
		UpdateUserDefinedPinDefaultValues();
	}
}

FNodeHandlingFunctor* UK2Node_FunctionResult::CreateNodeHandler(FKismetCompilerContext& CompilerContext) const
{
	return new FKCHandler_FunctionResult(CompilerContext);
}

FText UK2Node_FunctionResult::GetTooltipText() const
{
	return NSLOCTEXT("K2Node", "ReturnNodeTooltip", "The node terminates the function's execution. It returns output parameters.");
}

void UK2Node_FunctionResult::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);
	}
}

bool UK2Node_FunctionResult::IsCompatibleWithGraph(UEdGraph const* Graph) const
{
	auto K2Schema = Cast<const UEdGraphSchema_K2>(Graph ? Graph->GetSchema() : nullptr);
	const bool bIsConstructionScript = (K2Schema != nullptr) ? K2Schema->IsConstructionScript(Graph) : false;
	const bool bIsCompatible = (K2Schema != nullptr) ? (EGraphType::GT_Function == K2Schema->GetGraphType(Graph)) : false;
	return bIsCompatible && !bIsConstructionScript && Super::IsCompatibleWithGraph(Graph);
}

TArray<UK2Node_FunctionResult*> UK2Node_FunctionResult::GetAllResultNodes() const
{
	TArray<UK2Node_FunctionResult*> AllResultNodes;
	if (auto Graph = GetGraph())
	{
		Graph->GetNodesOfClass(AllResultNodes);
	}
	return AllResultNodes;
}

void UK2Node_FunctionResult::PostPlacedNewNode()
{
	Super::PostPlacedNewNode();

	// adhere to the function's inherited signature (if there is one)
	SyncWithEntryNode();
	// reflect any user added outputs (tracked by pre-existing result nodes)
	SyncWithPrimaryResultNode();
}

void UK2Node_FunctionResult::PostPasteNode()
{
	Super::PostPasteNode();

	// adhere to the function's inherited signature (if there is one)
	SyncWithEntryNode();
	// reflect any user added outputs (tracked by pre-existing result nodes)
	SyncWithPrimaryResultNode();
	// reflect editability of node in pins
	MakePinsEditable();
}

bool UK2Node_FunctionResult::CanUserDeleteNode() const
{
	bool bCanDelete = true;
	if (!bIsEditable)
	{
		if (UEdGraph* Graph = GetGraph())
		{
			bCanDelete = false;
			for (UEdGraphNode* Node : Graph->Nodes)
			{
				UK2Node_FunctionResult* ResultNode = Cast<UK2Node_FunctionResult>(Node);
				if (ResultNode && ResultNode != this)
				{
					bCanDelete = true;
					break;
				}
			}
		}
	}
	return bCanDelete;
}

void UK2Node_FunctionResult::SyncWithEntryNode()
{
	bool bWasSignatureMismatched = false;
	if (UEdGraph* Graph = GetGraph())
	{
		for (UEdGraphNode* Node : Graph->Nodes)
		{
			if (UK2Node_FunctionEntry* EntryNode = Cast<UK2Node_FunctionEntry>(Node))
			{
				bWasSignatureMismatched = !EntryNode->FunctionReference.IsSameReference(FunctionReference) || (!EntryNode->bIsEditable && UserDefinedPins.Num() > 0);

				// If the entry is editable, so is the result
				bIsEditable = EntryNode->bIsEditable;
				FunctionReference = EntryNode->FunctionReference;
				break;
			}
		}
	}

	if (bWasSignatureMismatched)
	{
		// to handle pasting of a result node from one function into another;
		// if the new function is not editable (like for one that is overidden), 
		// then we shouldn't have userdefined pins
		if (!bIsEditable)
		{
			// iterate backwards so we can remove items from the list as we go
			for (int32 UserPinIndex = UserDefinedPins.Num() - 1; UserPinIndex >= 0; --UserPinIndex)
			{
				RemoveUserDefinedPin(UserDefinedPins[UserPinIndex]);
			}
		}
		
		ReconstructNode();
	}
}

void UK2Node_FunctionResult::SyncWithPrimaryResultNode()
{
	UK2Node_FunctionResult* PrimaryNode = nullptr;
	TArray<UK2Node_FunctionResult*> AllResultNodes = GetAllResultNodes();
	for (auto ResultNode : AllResultNodes)
	{
		if (ResultNode && (this != ResultNode))
		{
			PrimaryNode = ResultNode;
			break;
		}
	}

	if (PrimaryNode)
	{
		FunctionReference = PrimaryNode->FunctionReference;
		bIsEditable = PrimaryNode->bIsEditable;

		// Temporary array that will contain our list of Old Pins that are no longer part of the return signature
		TArray< TSharedPtr<FUserPinInfo> > OldPins = UserDefinedPins;

		// Temporary array that will contain our list of Signature Pins that need to be added
		TArray< TSharedPtr<FUserPinInfo> > SignaturePins = PrimaryNode->UserDefinedPins;

		for (int OldIndex = OldPins.Num() - 1; OldIndex >= 0; --OldIndex)
		{
			TSharedPtr<FUserPinInfo> OldPin = OldPins[OldIndex];

			if (!OldPin.IsValid())
			{
				OldPins.RemoveAt(OldIndex);
			}
			else
			{
				for (int SignatureIndex = SignaturePins.Num() - 1; SignatureIndex >= 0; --SignatureIndex)
				{
					TSharedPtr<FUserPinInfo> SignaturePin = SignaturePins[SignatureIndex];
					if (!SignaturePin.IsValid())
					{
						SignaturePins.RemoveAt(SignatureIndex);
					}
					else if (OldPin->PinName == SignaturePin->PinName &&
						OldPin->PinType == SignaturePin->PinType &&
						OldPin->DesiredPinDirection == SignaturePin->DesiredPinDirection)
					{
						// We have a match between our Signature pins and our Old Pins,
						// so we can leave the old pin as is by removing it from both temporary lists.
						OldPins.RemoveAt(OldIndex);
						SignaturePins.RemoveAt(SignatureIndex);
						break;
					}
				}
			}
		}

		// Remove old pins that are not part of the primary node signature
		for (TSharedPtr<FUserPinInfo> OldPinToRemove : OldPins)
		{
			RemoveUserDefinedPin(OldPinToRemove);
		}

		// Add pins that don't exist yet but are part of the primary node signature
		for (TSharedPtr<FUserPinInfo> SignaturePinToAdd : SignaturePins)
		{
			TSharedPtr<FUserPinInfo> NewPinInfo = MakeShareable(new FUserPinInfo());
			NewPinInfo->PinName = SignaturePinToAdd->PinName;
			NewPinInfo->PinType = SignaturePinToAdd->PinType;
			NewPinInfo->DesiredPinDirection = SignaturePinToAdd->DesiredPinDirection;
			UserDefinedPins.Add(NewPinInfo);
		}

		ReconstructNode();
	}
}

void UK2Node_FunctionResult::MakePinsEditable()
{
	// only do this step if this node is editable
	if (IsEditable())
	{
		// for each pin, excluding the 'exec' pin
		for (int PinIdx = 1; PinIdx < Pins.Num(); ++PinIdx)
		{
			UEdGraphPin* Pin = Pins[PinIdx];
			if (!UserDefinedPinExists(Pin->GetFName()))
			{
				UserDefinedPins.Add(MakeShared<FUserPinInfo>(*Pin));
			}
		}
	}
}

void UK2Node_FunctionResult::ValidateNodeDuringCompilation(class FCompilerResultsLog& MessageLog) const
{
	Super::ValidateNodeDuringCompilation(MessageLog);

	auto AllResultNodes = GetAllResultNodes();
	UK2Node_FunctionResult* OtherResult = AllResultNodes.Num() ? AllResultNodes[0] : nullptr;
	if (OtherResult && (OtherResult != this))
	{
		for (auto Pin : Pins)
		{
			auto OtherPin = OtherResult->FindPin(Pin->PinName);
			if (!OtherPin || (OtherPin->PinType != Pin->PinType))
			{
				MessageLog.Error(*NSLOCTEXT("K2Node", "FunctionResult_DifferentReturnError", "Return nodes don't match each other: @@, @@").ToString(), this, OtherResult);
				break;
			}
		}
	}
}

  • 31
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论
UE蓝图是虚幻引擎中的一种视觉化编程工具,它为开发者提供了一种更易于理解和操作的编写游戏逻辑的方式。而UE蓝图节点也是UE蓝图的基本单位,不同的蓝图节点可以完成不同的任务或操作,因此了解UE蓝图节点大全是非常重要的。 UE蓝图节点大全中包含了几百种不同的节点,这些节点可以分为几类,在使用中对应着不同的功能,例如流程控制节点、数学运算节点、变量操作节点、数组操作节点、各类数据类型节点、事件处理节点等等。流程控制节点用于控制程序流程,包括分支节点、循环节点、序列节点、触发器节点等。数学运算节点用于对数操作和计算,包括加、减、乘、除、取余、绝对、三角函数等。变量操作节点用于控制变量的读取和保存,包括设置变量、获取变量、赋节点、数组节点等。各类数据类型节点用于处理各种数据类型,包括字符串、布尔、结构体、枚举、对象等。事件处理节点用于处理特定事件的发生,如输入事件、触发器事件、碰撞事件等。 在UE蓝图开发过程中,了解UE蓝图节点大全可以让开发者更快速和准确地编写代码,并且节省时间和精力,提高游戏开发效率。不过,对于新手来说,掌握所有的蓝图节点可能需要一定的时间和经验,因此建议在学习时重点掌握常用节点,逐渐熟悉和扩展自己的节点库。
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

shandongwill

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

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

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

打赏作者

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

抵扣说明:

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

余额充值