Переглянути джерело

feat: 添加 K2Node 说明

nicetry12138 1 рік тому
батько
коміт
5abd7e65bb

BIN
UE5/引擎开发记录/Image/005.png


BIN
UE5/引擎开发记录/Image/006.png


BIN
UE5/引擎开发记录/Image/007.png


+ 0 - 0
UE5/引擎开发记录/异步节点.md → UE5/引擎开发记录/自定义蓝图异步节点.md


+ 200 - 0
UE5/引擎开发记录/自定义蓝图节点.md

@@ -0,0 +1,200 @@
+# K2Node
+
+## 表现效果
+
+![](Image/005.png)
+
+以上图为例,一个蓝图节点从外观上看有四个部分
+
+1. 节点名称 `Node Title`
+2. 节点功能提示 `Tool Tip`
+3. 节点的输入节点
+4. 节点的输出节点
+
+所以 `UK2Node` 提供了一些函数能够表现出这些内容
+
+| 函数名 | 作用 |
+| --- | --- | 
+| AllocateDefaultPins() | 在这个函数中,你将为你的节点分配默认的输入和输出引脚 |
+| GetMenuActions() | 这个函数用于将你的自定义节点添加到蓝图编辑器的节点菜单中 |
+| GetNodeTitle() | 返回节点在蓝图编辑器中显示的标题 |
+| GetTooltipText() | 提供当鼠标悬停在节点上时显示的工具提示文本 |
+| GetMenuCategory() | 定义节点在菜单中的分类 |
+| ExpandNode() | 如果你的节点需要在编译时执行特殊的行为,你可以在这个函数中实现 |
+
+设置节点的 `Title` 和 `Tip`
+
+```cpp
+FText GetTooltipText() const override { return FText::FromString(TEXT("a custom node")); }
+FText GetNodeTitle(ENodeTitleType::Type TitleType) const override { return 	FText::FromString(TEXT("Get Variant Data By Type")); }
+```
+
+使用 `AllocateDefaultPins` 来创建节点的输入输出针脚
+
+```cpp
+#define EnumPinName TEXT("Enum Type")
+#define OutputPinName TEXT("Out Value")
+#define InputPinName TEXT("In Data")
+
+void UVariantNode::AllocateDefaultPins()
+{
+	// 获取关联的枚举类型
+	EnumType = FindObject<UEnum>(ANY_PACKAGE, *VariantHelper::EnumName);
+
+	// Create the output pin
+	CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Then);
+	CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Wildcard, OutputPinName);
+
+	// Create the input pin
+	CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Execute);
+	CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Byte, EnumType, EnumPinName);
+	CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_String, InputPinName);
+}
+```
+
+![](Image/007.png)
+
+重写 `GetMenuCategory` 可以设置节点所属类型
+
+```cpp
+FText GetMenuCategory() const override { return FText::FromString(TEXT("BlueprintVariantNodes")); }
+```
+
+### 功能实现
+
+![](Image/006.png)
+
+这个节点的作用能够根据输入的 `Enum Type` 类型,能够修改 `Out Value` 输出类型
+
+当输入针脚的值发生改变的时候会触发 `PinDefaultValueChanged` 函数,该函数定义在 `UK2Node` 的父类 `UEdGraphNode` 中
+
+```cpp
+UENUM(BlueprintType)
+enum class EPARMS_TYPE :uint8
+{
+	EPARMS_TYPE_DEFAULT = 0					UMETA(DisplayName = "Default"),
+
+	EPARMS_TYPE_BOOL = 2					UMETA(DisplayName = "Bool"),
+
+	EPARMS_TYPE_COLOR = 6					UMETA(DisplayName = "Color"),
+
+	EPARMS_TYPE_FLOAT = 10					UMETA(DisplayName = "Float"),
+}
+
+void UVariantNode::PinDefaultValueChanged(UEdGraphPin * Pin)
+{
+	if (Pin->PinName == EnumPinName)                   
+	{
+		// 获取当前枚举类型
+		EPARMS_TYPE Type = (EPARMS_TYPE)EnumType->GetValueByName(FName(*Pin->DefaultValue));
+
+		// 找到输出节点
+		UEdGraphPin* OutputPin = FindPin(OutputPinName);
+
+		// 修改输出节点的类型
+		switch (Type)
+		{
+		case EPARMS_TYPE::EPARMS_TYPE_BOOL:
+		{
+			OutputPin->PinType.PinSubCategoryObject = nullptr;
+			OutputPin->PinType.PinCategory = UEdGraphSchema_K2::PC_Boolean;
+		}
+		break;
+        
+		case EPARMS_TYPE::EPARMS_TYPE_COLOR:
+		{
+			OutputPin->PinType.PinSubCategoryObject = TBaseStructure<FColor>::Get();
+			OutputPin->PinType.PinCategory = UEdGraphSchema_K2::PC_Struct;
+		}
+		break;
+		case EPARMS_TYPE::EPARMS_TYPE_FLOAT:
+		{
+			OutputPin->PinType.PinSubCategoryObject = nullptr;
+			OutputPin->PinType.PinCategory = UEdGraphSchema_K2::PC_Float;
+		}
+		break;
+        }
+    }
+}
+```
+
+> 通过 `Pin->PinName` 可以判断是否是指定的针脚值被修改
+> 根据 `FindPin(OutputPinName)` 可以通过名称获得指定的针脚
+
+至于 `GetMenuActions` 函数就比较套路了,写法比较统一
+
+```cpp
+void UVariantNode::GetMenuActions(FBlueprintActionDatabaseRegistrar & ActionRegistrar) const
+{
+	UClass* ActionKey = GetClass();
+
+	if (ActionRegistrar.IsOpenForRegistration(ActionKey))
+	{
+		UBlueprintNodeSpawner* NodeSpawner = UBlueprintNodeSpawner::Create(GetClass());
+		check(NodeSpawner != nullptr);
+
+		ActionRegistrar.AddBlueprintAction(ActionKey, NodeSpawner);
+	}
+}
+```
+
+然后就是真正功能实现的地方 `ExpandNode` 函数
+
+```cpp
+// 绑定的函数的名称
+FName FunctionName;
+
+UEnum* CurrentEnumType = FindObject<UEnum>(ANY_PACKAGE, *VariantHelper::EnumName);
+EPARMS_TYPE Type = (EPARMS_TYPE)CurrentEnumType->GetValueByName(FName(*EnumPin->DefaultValue));
+
+// 绑定函数
+switch (Type)
+{
+case EPARMS_TYPE::EPARMS_TYPE_BOOL:
+    FunctionName = GET_FUNCTION_NAME_CHECKED(UVariantHelper, GetBool);
+    break;
+// ....
+// ....
+// ....
+}
+
+// 创建一个函数节点
+UK2Node_CallFunction* CallFuncNode = CompilerContext.SpawnIntermediateNode<UK2Node_CallFunction>(this, SourceGraph);
+CallFuncNode->FunctionReference.SetExternalMember(FunctionName, UVariantHelper::StaticClass());
+CallFuncNode->AllocateDefaultPins();
+
+// 执行操作替换
+CompilerContext.MovePinLinksToIntermediate(*ExecPin, *(CallFuncNode->GetExecPin()));
+CompilerContext.MovePinLinksToIntermediate(*ThenPin, *(CallFuncNode->GetThenPin()));
+
+// 输入和输出替换
+FName Name = UEdGraphSchema_K2::PC_String;
+CompilerContext.MovePinLinksToIntermediate(*InputPin, *(*CallFuncNode->Pins.FindByPredicate([Name](const UEdGraphPin* EdGraphPin)
+{
+    return EdGraphPin->PinType.PinCategory == Name;
+})));
+CompilerContext.MovePinLinksToIntermediate(*OutputPin, *(CallFuncNode->GetReturnValuePin()));
+
+// 断开自身所有链接
+BreakAllNodeLinks();
+```
+
+上面代码分为三个部分
+
+1. 根据指定参数类型选择需要绑定的函数 `GET_FUNCTION_NAME_CHECKED(UVariantHelper, GetBool)`
+2. 创建中间节点,并绑定指定函数 `CompilerContext.SpawnIntermediateNode<UK2Node_CallFunction>(this, SourceGraph)`
+3. 将本节点的针脚移动到新创建的中间节点 `CompilerContext.MovePinLinksToIntermediate`
+
+所以可以这么理解,继承 `UK2Node` 实现的节点仅用于显示,真正功能实现的是新创建的中间节点 `UK2Node_CallFunction`,然后将连接在 `UK2Node` 上的针脚移动到中间节点上,再有中间节点去执行指定的函数,得到最终的结果
+
+# KNode_CallFunction
+
+| 函数名 | 作用 |
+| --- | --- |
+| AllocateDefaultPins() | 为你的节点分配默认的输入和输出引脚 | 
+| GetMenuActions() | 将你的节点添加到蓝图编辑器的节点菜单中 | 
+| GetTooltipText() | 提供节点的工具提示文本 | 
+| GetMenuCategory() | 定义节点在菜单中的分类 | 
+| GetMenuActions() | 这个函数用于将你的自定义节点添加到蓝图编辑器的节点菜单中 | 
+| IsNodePure() | 是否是纯函数 |
+