|
@@ -0,0 +1,127 @@
|
|
|
|
|
+# 引擎开发记录
|
|
|
|
|
+
|
|
|
|
|
+## CustomChunk
|
|
|
|
|
+
|
|
|
|
|
+在计算机编程中,`Thunk` 是一个特殊的函数,其主要目的是延迟计算或调用,将一个多参数函数转换为一个单参数的延迟执行版本
|
|
|
|
|
+
|
|
|
|
|
+在 `Unreal` 中 `Thunk` 用于将蓝图中的事件或者函数调用转换为 C++ 中的函数调用。 Thunk 函数充当中间层,确保蓝图和 C++ 之间的通信和数据传递能够正常运行
|
|
|
|
|
+
|
|
|
|
|
+`CustomThunk` 顾名思义就是自定义 `Thunk` 函数,用于告诉引擎这个函数不是自动生成的,而是开发者提供的
|
|
|
|
|
+
|
|
|
|
|
+这种自定义的 `Thunk` 函数允许开发者直接与**蓝图虚拟机的堆栈**进行交互,从而可以处理更复杂的逻辑,比如**动态参数类型**的处理、**泛型编程**或者在运行时进行特定的**内存操作**
|
|
|
|
|
+
|
|
|
|
|
+CustomThunk 定义的函数,通常需要通过 `DECLARE_FUNCTION` 宏中以 `exec` 前缀来声明。这个 `exec` 是需要遵守的约定,因为 `Unreal` 的蓝图虚拟机在处理函数调用时,会查找以 `exec` 为前缀的函数来执行,相当于引擎内部的约定
|
|
|
|
|
+
|
|
|
|
|
+声明 `CustomThunk` 函数需要使用 `DECLARE_FUNCTION`,定义函数使用宏 `DEFINE_FUNCTION`
|
|
|
|
|
+
|
|
|
|
|
+```cpp
|
|
|
|
|
+// .h 文件
|
|
|
|
|
+UFUNCTION(BlueprintCallable, CustomThunk, Category = "Utilities|Instanced Struct", meta = (CustomStructureParam = "Value", BlueprintInternalUseOnly="true", NativeMakeFunc))
|
|
|
|
|
+static FInstancedStruct MakeInstancedStruct(const int32& Value);
|
|
|
|
|
+DECLARE_FUNCTION(execMakeInstancedStruct);
|
|
|
|
|
+
|
|
|
|
|
+// .cpp 文件
|
|
|
|
|
+FInstancedStruct UStructUtilsFunctionLibrary::MakeInstancedStruct(const int32& Value)
|
|
|
|
|
+{
|
|
|
|
|
+ // We should never hit this! stubs to avoid NoExport on the class.
|
|
|
|
|
+ checkNoEntry();
|
|
|
|
|
+ return {};
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+DEFINE_FUNCTION(UStructUtilsFunctionLibrary::execMakeInstancedStruct)
|
|
|
|
|
+{
|
|
|
|
|
+ //
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+> `CustomStructureParam` 允许名为 `Value` 的参数接受多种不同类型的数据
|
|
|
|
|
+> `AutoCreateRefTerm` 会自动为标记为通配符的结构体参数创建引用术语
|
|
|
|
|
+
|
|
|
|
|
+通过蓝图虚拟机的堆栈可以获得函数参数等信息
|
|
|
|
|
+
|
|
|
|
|
+```cpp
|
|
|
|
|
+DEFINE_FUNCTION(UStructUtilsFunctionLibrary::execMakeInstancedStruct)
|
|
|
|
|
+{
|
|
|
|
|
+ Stack.MostRecentPropertyAddress = nullptr;
|
|
|
|
|
+ Stack.MostRecentPropertyContainer = nullptr;
|
|
|
|
|
+ Stack.StepCompiledIn<FStructProperty>(nullptr); // 从蓝图虚拟机的堆栈中获取下一个 FStructProperty 类型的属性,这通常是函数的参数
|
|
|
|
|
+
|
|
|
|
|
+ const FStructProperty* ValueProp = CastField<FStructProperty>(Stack.MostRecentProperty); // 尝试将获取到的属性转换为 FStructProperty 类型,这代表结构体
|
|
|
|
|
+ const void* ValuePtr = Stack.MostRecentPropertyAddress; // 获取目标结构体的地址
|
|
|
|
|
+
|
|
|
|
|
+ // ...
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+如果函数存在多个参数,那就需要多次调用 `Stack.StepCompiledIn` 直到参数全部获取,当然也 Unreal 也提供了一些宏来方便的获取参数
|
|
|
|
|
+
|
|
|
|
|
+以 `UAnimationAttributeBlueprintLibrary::execSetAttributeKey` 为例
|
|
|
|
|
+
|
|
|
|
|
+```cpp
|
|
|
|
|
+UFUNCTION(BlueprintCallable, CustomThunk, Category = "Animation|Attributes", meta=(CustomStructureParam="Value", BlueprintInternalUseOnly="true"))
|
|
|
|
|
+static bool SetAttributeKey(TScriptInterface<IAnimationDataController> AnimationDataController, const FAnimationAttributeIdentifier& AttributeIdentifier, float Time, const int32& Value);
|
|
|
|
|
+
|
|
|
|
|
+DEFINE_FUNCTION(UAnimationAttributeBlueprintLibrary::execSetAttributeKey)
|
|
|
|
|
+{
|
|
|
|
|
+ P_GET_TINTERFACE(IAnimationDataController, AnimationDataController);
|
|
|
|
|
+ P_GET_STRUCT_REF(FAnimationAttributeIdentifier, AttributeIdentifier);
|
|
|
|
|
+ P_GET_PROPERTY(FFloatProperty, TimeInterval);
|
|
|
|
|
+
|
|
|
|
|
+ Stack.MostRecentProperty = nullptr;
|
|
|
|
|
+ Stack.MostRecentPropertyAddress = nullptr;
|
|
|
|
|
+
|
|
|
|
|
+ Stack.StepCompiledIn<FStructProperty>(nullptr);
|
|
|
|
|
+
|
|
|
|
|
+ const FStructProperty* ItemProperty = CastField<FStructProperty>(Stack.MostRecentProperty);
|
|
|
|
|
+ void* ItemDataPtr = Stack.MostRecentPropertyAddress;
|
|
|
|
|
+
|
|
|
|
|
+ // ...
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+由于函数的前几个参数明确了类型 `IAnimationDataController`、`FAnimationAttributeIdentifier`、`float`,分别使用 `P_GET_TINTERFACE`、`P_GET_STRUCT_REF` 和 `P_GET_PROPERTY` 来获取对应的数据
|
|
|
|
|
+
|
|
|
|
|
+> `P_GET_TINTERFACE` 获取接口(Interface)类型属性
|
|
|
|
|
+> `P_GET_STRUCT_REF` 获取结构体(STRUCT)类型属性
|
|
|
|
|
+
|
|
|
|
|
+最后一个传递的是 `CustomStructureParam` 标记的参数,可以接受任意类型,这里期望的是一个结构体,所以使用 `FStructProperty` 来接受,由于提前不知道结构体的类型,所以不能使用 `P_GET_STRUCT_REF`
|
|
|
|
|
+
|
|
|
|
|
+根据 `FStructProperty` 可以得到结构体的属性列表和值,进而可以根据需要进行后续操作
|
|
|
|
|
+
|
|
|
|
|
+```cpp
|
|
|
|
|
+DEFINE_FUNCTION(UJsonBlueprintFunctionLibrary::execGetField)
|
|
|
|
|
+{
|
|
|
|
|
+ P_GET_STRUCT_REF(FJsonObjectWrapper, JsonObject);
|
|
|
|
|
+ P_GET_PROPERTY(FStrProperty, FieldName);
|
|
|
|
|
+
|
|
|
|
|
+ Stack.StepCompiledIn<FProperty>(nullptr);
|
|
|
|
|
+ FProperty* ValueProp = Stack.MostRecentProperty;
|
|
|
|
|
+ void* ValuePtr = Stack.MostRecentPropertyAddress;
|
|
|
|
|
+
|
|
|
|
|
+ P_FINISH;
|
|
|
|
|
+
|
|
|
|
|
+ if (!ValueProp || !ValuePtr)
|
|
|
|
|
+ {
|
|
|
|
|
+ // 错误警告
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ bool bResult;
|
|
|
|
|
+ if (FieldName.IsEmpty())
|
|
|
|
|
+ {
|
|
|
|
|
+ // 特殊处理
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ P_NATIVE_BEGIN
|
|
|
|
|
+ bResult = JsonFieldToProperty(FieldName, JsonObject, ValueProp, ValuePtr);
|
|
|
|
|
+ P_NATIVE_END
|
|
|
|
|
+
|
|
|
|
|
+ *static_cast<bool*>(RESULT_PARAM) = bResult;
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+继续以 `execMakeInstancedStruct` 为例,需要注意的是 `P_FINISH`、`P_NATIVE_BEGIN`、`P_NATIVE_END` 和 `RESULT_PARAM`
|
|
|
|
|
+
|
|
|
|
|
+- `P_FINISH`: 宏之前为获取函数参数的代码块
|
|
|
|
|
+- `P_NATIVE_BEGIN` 和 `P_NATIVE_END` : 宏包裹着真正执行该函数功能的被调用函数,若是泛型蓝图节点,此处调用该函数的泛型版本(泛型函数)
|
|
|
|
|
+- `RESULT_PARAM`: 表示函数返回值
|
|
|
|
|
+
|