系列文章目录
UE蓝图 Cast节点和源码
文章目录
- 系列文章目录
- Cast节点功能
- 一、Cast节点用法
- 二、Cast节点使用场景
- 三、Cast节点实现步骤
- 四、Cast节点源码
Cast节点功能
在Unreal Engine(UE)中,Cast节点是一种蓝图系统中的节点,用于执行类型转换操作。Cast节点用于检测一个对象是否可以被转换(或“投射”)为另一种类型,并在转换成功时返回转换后的对象。
具体来说,当你在蓝图中使用Cast节点时,你正在尝试将一个对象转换(或投射)为另一种类型。例如,如果你有一个“玩家角色”对象,并且你想检查它是否是一个特定的“MyCharacter”类型的实例,你可以使用“Get Player Character”节点来获取玩家角色,然后使用“Cast To MyCharacter”节点来尝试将其转换为“MyCharacter”类型。如果转换成功,你就可以访问该对象的特定变量、函数和事件。
上图对应的代码如下:
bpfv__CallFunc_GetParentActor_ReturnValue__pf = AActor::GetParentActor();
bpfv__K2Node_DynamicCast_As____pf = Cast<ACharacter>(bpfv__CallFunc_GetParentActor_ReturnValue__pf);
bpfv__K2Node_DynamicCast_bSuccess__pf = (bpfv__K2Node_DynamicCast_As____pf != nullptr);;
if (!bpfv__K2Node_DynamicCast_bSuccess__pf)
{
__CurrentState = -1;
break;
}
一、Cast节点用法
当然可以。UE Cast节点在Unreal Engine的蓝图系统中的具体用法主要涉及以下步骤:
-
获取对象引用:首先,你需要有一个对象引用,这通常是通过其他蓝图节点获取的。例如,你可能有一个指向某个游戏对象(如玩家角色、敌人等)的引用。
-
添加Cast节点:在蓝图中,你需要添加一个Cast节点。这个节点通常位于“类型转换”类别下。添加后,你将看到两个主要的输出端:一个是转换后的对象引用,另一个是表示转换是否成功的布尔值(
bool
)。 -
配置Cast节点:在Cast节点的属性窗口中,你需要指定目标类型,即你希望将对象转换成的类型。这通常是一个类名,比如
MyCharacter
或MyComponent
。 -
连接对象引用:将你想要转换的对象引用连接到Cast节点的输入端。这通常是通过从获取对象引用的节点拖一条线到Cast节点的输入端来完成的。
-
使用转换后的对象:Cast节点有两个输出端。第一个输出端输出转换后的对象引用。如果转换成功,这个输出端将是非空的(即有效的)。你可以将这个输出端连接到需要该类型对象的任何节点上。
-
检查转换是否成功:Cast节点的第二个输出端是一个布尔值,表示转换是否成功。你可以将这个输出端连接到条件逻辑节点(如
Branch
节点)上,以便在转换成功时执行一段逻辑,在转换失败时执行另一段逻辑。 -
处理转换失败的情况:如果转换失败(即对象不能被转换为目标类型),你可能需要执行一些备选逻辑。这可以通过使用Cast节点的第二个输出端(布尔值)来实现,将其连接到一个条件节点,以便在转换失败时执行特定的操作。
二、Cast节点使用场景
UE Cast节点在蓝图系统中有多种应用场景,这些场景主要涉及到需要动态类型转换或检查的地方。以下是一些常见的UE Cast节点应用场景:
-
继承与多态处理:在面向对象编程中,Cast节点常用于处理继承关系。例如,当你有一个基类指针或引用,并且你想调用仅在派生类中定义的函数时,你需要先使用Cast节点将其转换为派生类类型。
-
接口实现检查:如果一个对象实现了某个接口,你可以使用Cast节点来检查这个对象是否确实实现了该接口,并据此执行相应的操作。
-
组件查询与访问:在UE中,组件通常附加到实体(如Actor)上。你可以使用Cast节点来尝试将实体转换为特定的组件类型,以便直接访问该组件的属性和方法。
-
动态类型识别:在某些情况下,你可能不知道对象的确切类型,但需要根据其类型执行不同的逻辑。通过使用Cast节点,你可以尝试将对象转换为多种可能的类型,并根据转换是否成功来决定执行哪段逻辑。
-
事件处理:在处理事件或委托时,你可能会接收到一个通用类型的参数。使用Cast节点,你可以将这个通用参数转换为更具体的类型,以便在事件处理逻辑中使用。
-
资源加载与转换:在加载资源时,资源可能以通用或基类形式被加载。在使用资源之前,你可能需要将其转换为正确的派生类型。
-
错误处理与调试:在开发过程中,Cast节点可以帮助你验证对象是否如你所期望的那样被正确创建和管理。如果Cast失败,这可能表明存在编程错误或资源管理问题。
三、Cast节点实现步骤
- 创建输入输出引脚
// Input - Execution Pin
CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Execute);
// Output - Execution Pins
CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_CastSucceeded);
CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_CastFailed);
// Input - Source type Pin
CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Wildcard, UObject::StaticClass(), UEdGraphSchema_K2::PN_ObjectToCast);
CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Object, *TargetType, *CastResultPinName);
UEdGraphPin* BoolSuccessPin = CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Boolean, UK2Node_DynamicCastImpl::CastSuccessPinName);
- 注册Net
FBPTerminal* Term = Context.CreateLocalTerminalFromPinAutoChooseScope(Net, Context.NetNameMap->MakeValidName(Net));
Context.NetMap.Add(Net, Term);
- 编译
创建转换Statement
// Cast Statement
FBlueprintCompiledStatement& CastStatement = Context.AppendStatementForNode(Node);
CastStatement.Type = CastOpType;
CastStatement.LHS = *CastResultTerm;
CastStatement.RHS.Add(ClassTerm);
CastStatement.RHS.Add(*ObjectToCast);
四、Cast节点源码
void UK2Node_DynamicCast::AllocateDefaultPins()
{
const bool bReferenceObsoleteClass = TargetType && TargetType->HasAnyClassFlags(CLASS_NewerVersionExists);
if (bReferenceObsoleteClass)
{
Message_Error(FString::Printf(TEXT("Node '%s' references obsolete class '%s'"), *GetPathName(), *TargetType->GetPathName()));
}
ensure(!bReferenceObsoleteClass);
const UEdGraphSchema_K2* K2Schema = Cast<UEdGraphSchema_K2>(GetSchema());
check(K2Schema != nullptr);
if (!K2Schema->DoesGraphSupportImpureFunctions(GetGraph()))
{
bIsPureCast = true;
}
if (!bIsPureCast)
{
// Input - Execution Pin
CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Execute);
// Output - Execution Pins
CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_CastSucceeded);
CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_CastFailed);
}
// Input - Source type Pin
CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Wildcard, UObject::StaticClass(), UEdGraphSchema_K2::PN_ObjectToCast);
// Output - Data Pin
if (TargetType)
{
const FString CastResultPinName = UEdGraphSchema_K2::PN_CastedValuePrefix + TargetType->GetDisplayNameText().ToString();
if (TargetType->IsChildOf(UInterface::StaticClass()))
{
CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Interface, *TargetType, *CastResultPinName);
}
else
{
CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Object, *TargetType, *CastResultPinName);
}
}
UEdGraphPin* BoolSuccessPin = CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Boolean, UK2Node_DynamicCastImpl::CastSuccessPinName);
BoolSuccessPin->bHidden = !bIsPureCast;
Super::AllocateDefaultPins();
}
void FKCHandler_DynamicCast::RegisterNets(FKismetFunctionContext& Context, UEdGraphNode* Node)
{
FNodeHandlingFunctor::RegisterNets(Context, Node);
if (const UK2Node_DynamicCast* DynamicCastNode = Cast<UK2Node_DynamicCast>(Node))
{
UEdGraphPin* BoolSuccessPin = DynamicCastNode->GetBoolSuccessPin();
// this is to support backwards compatibility (when a cast node is generating code, but has yet to be reconstructed)
// @TODO: remove this at some point, when backwards compatibility isn't a concern
if (BoolSuccessPin == nullptr)
{
// Create a term to determine if the cast was successful or not
FBPTerminal* BoolTerm = Context.CreateLocalTerminal();
BoolTerm->Type.PinCategory = UEdGraphSchema_K2::PC_Boolean;
BoolTerm->Source = Node;
BoolTerm->Name = Context.NetNameMap->MakeValidName(Node, TEXT("CastSuccess"));
BoolTermMap.Add(Node, BoolTerm);
}
}
}
void FKCHandler_DynamicCast::RegisterNet(FKismetFunctionContext& Context, UEdGraphPin* Net)
{
FBPTerminal* Term = Context.CreateLocalTerminalFromPinAutoChooseScope(Net, Context.NetNameMap->MakeValidName(Net));
Context.NetMap.Add(Net, Term);
}
void FKCHandler_DynamicCast::Compile(FKismetFunctionContext& Context, UEdGraphNode* Node)
{
const UK2Node_DynamicCast* DynamicCastNode = CastChecked<UK2Node_DynamicCast>(Node);
if (DynamicCastNode->TargetType == NULL)
{
CompilerContext.MessageLog.Error(*LOCTEXT("BadCastNoTargetType_Error", "Node @@ has an invalid target type, please delete and recreate it").ToString(), Node);
}
// Self Pin
UEdGraphPin* SourceObjectPin = DynamicCastNode->GetCastSourcePin();
UEdGraphPin* PinToTry = FEdGraphUtilities::GetNetFromPin(SourceObjectPin);
FBPTerminal** ObjectToCast = Context.NetMap.Find(PinToTry);
if (!ObjectToCast)
{
ObjectToCast = Context.LiteralHackMap.Find(PinToTry);
if (!ObjectToCast || !(*ObjectToCast))
{
CompilerContext.MessageLog.Error(*LOCTEXT("InvalidConnectionOnNode_Error", "Node @@ has an invalid connection on @@").ToString(), Node, SourceObjectPin);
return;
}
}
// Output pin
const UEdGraphPin* CastOutputPin = DynamicCastNode->GetCastResultPin();
if( !CastOutputPin )
{
CompilerContext.MessageLog.Error(*LOCTEXT("InvalidDynamicCastClass_Error", "Node @@ has an invalid target class").ToString(), Node);
return;
}
FBPTerminal** CastResultTerm = Context.NetMap.Find(CastOutputPin);
if (!CastResultTerm || !(*CastResultTerm))
{
CompilerContext.MessageLog.Error(*LOCTEXT("InvalidDynamicCastClass_CompilerError", "Node @@ has an invalid target class. (Inner compiler error?)").ToString(), Node);
return;
}
// Create a literal term from the class specified in the node
FBPTerminal* ClassTerm = Context.CreateLocalTerminal(ETerminalSpecification::TS_Literal);
ClassTerm->Name = DynamicCastNode->TargetType->GetName();
ClassTerm->bIsLiteral = true;
ClassTerm->Source = Node;
ClassTerm->ObjectLiteral = DynamicCastNode->TargetType;
ClassTerm->Type.PinCategory = UEdGraphSchema_K2::PC_Class;
UClass const* const InputObjClass = Cast<UClass>((*ObjectToCast)->Type.PinSubCategoryObject.Get());
UClass const* const OutputObjClass = Cast<UClass>((*CastResultTerm)->Type.PinSubCategoryObject.Get());
const bool bIsOutputInterface = ((OutputObjClass != NULL) && OutputObjClass->HasAnyClassFlags(CLASS_Interface));
const bool bIsInputInterface = ((InputObjClass != NULL) && InputObjClass->HasAnyClassFlags(CLASS_Interface));
EKismetCompiledStatementType CastOpType = KCST_DynamicCast;
if (bIsInputInterface)
{
if (bIsOutputInterface)
{
CastOpType = KCST_CrossInterfaceCast;
}
else
{
CastOpType = KCST_CastInterfaceToObj;
}
}
else if (bIsOutputInterface)
{
CastOpType = KCST_CastObjToInterface;
}
if (KCST_MetaCast == CastType)
{
if (bIsInputInterface || bIsOutputInterface)
{
CompilerContext.MessageLog.Error(*LOCTEXT("InvalidClassDynamicCastClass_Error", "Node @@ has an invalid target class. Interfaces are not supported.").ToString(), Node);
return;
}
CastOpType = KCST_MetaCast;
}
// Cast Statement
FBlueprintCompiledStatement& CastStatement = Context.AppendStatementForNode(Node);
CastStatement.Type = CastOpType;
CastStatement.LHS = *CastResultTerm;
CastStatement.RHS.Add(ClassTerm);
CastStatement.RHS.Add(*ObjectToCast);
FBPTerminal** BoolSuccessTerm = nullptr;
if (UEdGraphPin* BoolSuccessPin = DynamicCastNode->GetBoolSuccessPin())
{
BoolSuccessTerm = Context.NetMap.Find(BoolSuccessPin);
}
else
{
BoolSuccessTerm = BoolTermMap.Find(DynamicCastNode);
}
check(BoolSuccessTerm != nullptr);
// Check result of cast statement
FBlueprintCompiledStatement& CheckResultStatement = Context.AppendStatementForNode(Node);
CheckResultStatement.Type = KCST_ObjectToBool;
CheckResultStatement.LHS = *BoolSuccessTerm;
CheckResultStatement.RHS.Add(*CastResultTerm);
UEdGraphPin* SuccessExecPin = DynamicCastNode->GetValidCastPin();
bool const bIsPureCast = (SuccessExecPin == nullptr);
if (!bIsPureCast)
{
UEdGraphPin* FailurePin = DynamicCastNode->GetInvalidCastPin();
check(FailurePin != nullptr);
// Failure condition...skip to the failed output
FBlueprintCompiledStatement& FailCastGoto = Context.AppendStatementForNode(Node);
FailCastGoto.Type = KCST_GotoIfNot;
FailCastGoto.LHS = *BoolSuccessTerm;
Context.GotoFixupRequestMap.Add(&FailCastGoto, FailurePin);
// Successful cast...hit the success output node
FBlueprintCompiledStatement& SuccessCastGoto = Context.AppendStatementForNode(Node);
SuccessCastGoto.Type = KCST_UnconditionalGoto;
SuccessCastGoto.LHS = *BoolSuccessTerm;
Context.GotoFixupRequestMap.Add(&SuccessCastGoto, SuccessExecPin);
}
}