vault backup: 2024-10-12 17:19:45
@@ -0,0 +1,72 @@
|
||||
# AdvancedDisplay
|
||||
|
||||
- **功能描述:** 把函数的一些参数折叠起来不显示,需要手动点开下拉箭头来展开编辑。
|
||||
- **使用位置:** UFUNCTION
|
||||
- **引擎模块:** Blueprint
|
||||
- **元数据类型:** strings="a,b,c"
|
||||
- **常用程度:** ★★★★★
|
||||
|
||||
把函数的一些参数折叠起来不显示,需要手动点开下拉箭头来展开编辑。
|
||||
|
||||
AdvancedDisplay同时支持两种格式,一是用"Parameter1, Parameter2, ..”来显式的指定需要折叠的参数名字,适用于要折叠的参数不连续或者处在函数参数列表中中央的情况下。二是”N”指定一个数字序号,第N之后的所有参数将显示为高级引脚。
|
||||
|
||||
## 测试代码:
|
||||
|
||||
```cpp
|
||||
UFUNCTION(BlueprintCallable, meta = (AdvancedDisplay = "2"))
|
||||
static int32 MyFunc_HasAdvancedDisplay_WithOrder(int32 First, int32 Second, int32 Third, int32 Fourth, int32 Fifth) { return 0; }
|
||||
UFUNCTION(BlueprintCallable, meta = (AdvancedDisplay = "Fourth,Fifth"))
|
||||
static int32 MyFunc_HasAdvancedDisplay_WithName(int32 First, int32 Second, int32 Third, int32 Fourth, int32 Fifth) { return 0; }
|
||||
|
||||
UFUNCTION(BlueprintCallable, meta = ())
|
||||
static int32 MyFunc_NoAdvancedDisplay(int32 First, int32 Second, int32 Third, int32 Fourth, int32 Fifth) { return 0; }
|
||||
```
|
||||
|
||||
## 蓝图效果:
|
||||
|
||||

|
||||
|
||||
源码中典型的例子是PrintString,在第2个参数后的其他参数就都折叠了起来。
|
||||
|
||||
```cpp
|
||||
UFUNCTION(BlueprintCallable, meta=(WorldContext="WorldContextObject", CallableWithoutWorldContext, Keywords = "log print", AdvancedDisplay = "2", DevelopmentOnly), Category="Development")
|
||||
static ENGINE_API void PrintString(const UObject* WorldContextObject, const FString& InString = FString(TEXT("Hello")), bool bPrintToScreen = true, bool bPrintToLog = true, FLinearColor TextColor = FLinearColor(0.0f, 0.66f, 1.0f), float Duration = 2.f, const FName Key = NAME_None);
|
||||
```
|
||||
|
||||
## 原理:
|
||||
|
||||
AdvancedDisplay使得被标注的函数参数增加EPropertyFlags.AdvancedDisplay的标记,从而使得其被折叠起来。这个逻辑是在UHT对函数进行解析的时候设置的。
|
||||
|
||||
```cpp
|
||||
//支持参数名字和数字序号两种模式
|
||||
if (_metaData.TryGetValue(UhtNames.AdvancedDisplay, out string? foundString))
|
||||
{
|
||||
_parameterNames = foundString.ToString().Split(',', StringSplitOptions.RemoveEmptyEntries);
|
||||
for (int index = 0, endIndex = _parameterNames.Length; index < endIndex; ++index)
|
||||
{
|
||||
_parameterNames[index] = _parameterNames[index].Trim();
|
||||
}
|
||||
if (_parameterNames.Length == 1)
|
||||
{
|
||||
_bUseNumber = Int32.TryParse(_parameterNames[0], out _numberLeaveUnmarked);
|
||||
}
|
||||
}
|
||||
|
||||
//设置EPropertyFlags.AdvancedDisplay
|
||||
private static void UhtFunctionParser::ParseParameterList(UhtParsingScope topScope, UhtPropertyParseOptions options)
|
||||
{
|
||||
UhtAdvancedDisplayParameterHandler advancedDisplay = new(topScope.ScopeType.MetaData);
|
||||
|
||||
topScope.TokenReader.RequireList(')', ',', false, () =>
|
||||
{
|
||||
topScope.HeaderParser.GetCachedPropertyParser().Parse(topScope, disallowFlags, options, propertyCategory,
|
||||
(UhtParsingScope topScope, UhtProperty property, ref UhtToken nameToken, UhtLayoutMacroType layoutMacroType) =>
|
||||
{
|
||||
property.PropertyFlags |= EPropertyFlags.Parm;
|
||||
if (advancedDisplay.CanMarkMore() && advancedDisplay.ShouldMarkParameter(property.EngineName))
|
||||
{
|
||||
property.PropertyFlags |= EPropertyFlags.AdvancedDisplay;
|
||||
}
|
||||
}
|
||||
|
||||
```
|
After Width: | Height: | Size: 68 KiB |
@@ -0,0 +1,67 @@
|
||||
# AllowPrivateAccess
|
||||
|
||||
- **功能描述:** 允许一个在C++中private的属性,可以在蓝图中访问。
|
||||
- **使用位置:** UPROPERTY
|
||||
- **元数据类型:** bool
|
||||
- **关联项:** [BlueprintProtected](../BlueprintProtected/BlueprintProtected.md)
|
||||
- **常用程度:** ★★★★★
|
||||
|
||||
允许一个在C++中private的属性,可以在蓝图中访问。
|
||||
|
||||
AllowPrivateAccess的意义是允许这个属性在C++是private的,不允许C++子类访问,但又允许其暴露到蓝图中访问。
|
||||
|
||||
## 测试代码:
|
||||
|
||||
```cpp
|
||||
public:
|
||||
//CPF_BlueprintVisible | CPF_ZeroConstructor | CPF_IsPlainOldData | CPF_NoDestructor | CPF_HasGetValueTypeHash | CPF_NativeAccessSpecifierPublic
|
||||
UPROPERTY(BlueprintReadWrite)
|
||||
int32 MyNativeInt_NativePublic;
|
||||
private:
|
||||
//CPF_ZeroConstructor | CPF_IsPlainOldData | CPF_NoDestructor | CPF_HasGetValueTypeHash | CPF_NativeAccessSpecifierPrivate
|
||||
//error : BlueprintReadWrite should not be used on private members
|
||||
UPROPERTY()
|
||||
int32 MyNativeInt_NativePrivate;
|
||||
|
||||
//(AllowPrivateAccess = TRUE, Category = MyFunction_Access, ModuleRelativePath = Function/MyFunction_Access.h)
|
||||
//CPF_BlueprintVisible | CPF_ZeroConstructor | CPF_IsPlainOldData | CPF_NoDestructor | CPF_HasGetValueTypeHash | CPF_NativeAccessSpecifierPrivate
|
||||
UPROPERTY(BlueprintReadWrite, meta = (AllowPrivateAccess = true))
|
||||
int32 MyNativeInt_NativePrivate_AllowPrivateAccess;
|
||||
```
|
||||
|
||||
在MyNativeInt_NativePrivate上如果尝试加上BlueprintReadWrite或BlueprintReadOnly都会触发UHT编译报错。
|
||||
|
||||
## 蓝图里的效果:
|
||||
|
||||
默认情况下MyNativeInt_NativePrivate_AllowPrivateAccess在蓝图里的访问权限和MyNativeInt_NativePublic一致。
|
||||
|
||||
如果读者想要修改改属性在蓝图中的访问权限,则可以配合加上BlueprintProtected和BlueprintPrivate。
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
UHT在识别属性的BlueprintReadWrite或BlueprintReadOnly标识符的时候,会同时检测是否有AllowPrivateAccess,没有的话会触发报错。
|
||||
|
||||
因此AllowPrivateAccess的意义其实只是在阻止UHT的报错,这层检测报错过了之后,属性上的BlueprintReadWrite或BlueprintReadOnly就会被识别并发挥作用,从而可以在蓝图中访问。
|
||||
|
||||
```cpp
|
||||
private static void BlueprintReadWriteSpecifier(UhtSpecifierContext specifierContext)
|
||||
{
|
||||
bool allowPrivateAccess = context.MetaData.TryGetValue(UhtNames.AllowPrivateAccess, out string? privateAccessMD) && !privateAccessMD.Equals("false", StringComparison.OrdinalIgnoreCase);
|
||||
if (specifierContext.AccessSpecifier == UhtAccessSpecifier.Private && !allowPrivateAccess)
|
||||
{
|
||||
context.MessageSite.LogError("BlueprintReadWrite should not be used on private members");
|
||||
}
|
||||
}
|
||||
|
||||
private static void BlueprintReadOnlySpecifier(UhtSpecifierContext specifierContext)
|
||||
{
|
||||
bool allowPrivateAccess = context.MetaData.TryGetValue(UhtNames.AllowPrivateAccess, out string? privateAccessMD) && !privateAccessMD.Equals("false", StringComparison.OrdinalIgnoreCase);
|
||||
if (specifierContext.AccessSpecifier == UhtAccessSpecifier.Private && !allowPrivateAccess)
|
||||
{
|
||||
context.MessageSite.LogError("BlueprintReadOnly should not be used on private members");
|
||||
}
|
||||
}
|
||||
|
||||
```
|
After Width: | Height: | Size: 150 KiB |
@@ -0,0 +1,82 @@
|
||||
# BlueprintAutocast
|
||||
|
||||
- **功能描述:** 告诉蓝图系统这个函数是用来支持从A类型到B类型的自动转换。
|
||||
- **使用位置:** UFUNCTION
|
||||
- **引擎模块:** Blueprint
|
||||
- **元数据类型:** bool
|
||||
- **常用程度:** ★
|
||||
|
||||
告诉蓝图系统这个函数是用来支持从A类型到B类型的自动转换。
|
||||
|
||||
所谓自动转换指的是从A类型的Pin拖拉到B类型的Pin时,蓝图会在其中自动的生成类型转换节点。
|
||||
|
||||
这种转换函数必须是BlueprintPure,因为其实是被动调用的,不带主动执行节点。
|
||||
|
||||
## 测试代码:
|
||||
|
||||
```cpp
|
||||
USTRUCT(BlueprintType)
|
||||
struct FAutoCastFrom
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere)
|
||||
int32 X = 0;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere)
|
||||
int32 Y = 0;
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FAutoCastTo
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere)
|
||||
int32 Sum = 0;
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FNoAutoCastTo
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere)
|
||||
int32 Sum = 0;
|
||||
};
|
||||
|
||||
UCLASS(Blueprintable, BlueprintType)
|
||||
class INSIDER_API UMyFunction_AutoCast :public UBlueprintFunctionLibrary
|
||||
{
|
||||
public:
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UFUNCTION(BlueprintPure, meta = (BlueprintAutocast))
|
||||
static FAutoCastTo Conv_MyTestAutoCast(const FAutoCastFrom& InValue);
|
||||
};
|
||||
|
||||
//转换函数也经常配合CompactNodeTitle使用。
|
||||
UFUNCTION(BlueprintPure, Category="Widget", meta = (CompactNodeTitle = "->", BlueprintAutocast))
|
||||
static UMG_API FInputEvent GetInputEventFromKeyEvent(const FKeyEvent& Event);
|
||||
```
|
||||
|
||||
## 示例效果:
|
||||
|
||||
支持自动转换的FAutoCastTo就在拖拉连线的时候就会自动生成节点,而没有自动转换函数的FNoAutoCastTo就会产生报错。
|
||||
|
||||

|
||||
|
||||
## 原理代码:
|
||||
|
||||
从这可以看出,该函数必须是static,C++中的Public函数,标上BlueprintPure,拥有返回值,且有一个输入参数。引擎里类型的自动转换关系是靠FAutocastFunctionMap来维护的。
|
||||
|
||||
```cpp
|
||||
static bool IsAutocastFunction(const UFunction* Function)
|
||||
{
|
||||
const FName BlueprintAutocast(TEXT("BlueprintAutocast"));
|
||||
return Function
|
||||
&& Function->HasMetaData(BlueprintAutocast)
|
||||
&& Function->HasAllFunctionFlags(FUNC_Static | FUNC_Native | FUNC_Public | FUNC_BlueprintPure)
|
||||
&& Function->GetReturnProperty()
|
||||
&& GetFirstInputProperty(Function);
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 162 KiB |
@@ -0,0 +1,18 @@
|
||||
# BlueprintGetter
|
||||
|
||||
- **功能描述:** 采用一个自定义的get函数来读取。
|
||||
如果没有设置BlueprintSetter或BlueprintReadWrite, 则会默认设置BlueprintReadOnly.
|
||||
|
||||
- **使用位置:** UFUNCTION, UPROPERTY
|
||||
|
||||
- **引擎模块:** Blueprint
|
||||
|
||||
- **元数据类型:** string="abc"
|
||||
|
||||
- **关联项:**
|
||||
|
||||
UFUNCTION:[BlueprintGetter](../../Specifier/UFUNCTION/Blueprint/BlueprintGetter.md)
|
||||
|
||||
UPROPERTY:[BlueprintGetter](../../Specifier/UPROPERTY/Blueprint/BlueprintGetter/BlueprintGetter.md)
|
||||
|
||||
- **常用程度:** ★★★
|
@@ -0,0 +1,23 @@
|
||||
# BlueprintInternalUseOnly
|
||||
|
||||
- **功能描述:** 标明该元素是作为蓝图系统的内部调用或使用,不暴露出来在用户层面直接定义或使用。
|
||||
|
||||
- **使用位置:** UFUNCTION, USTRUCT
|
||||
|
||||
- **引擎模块:** Blueprint
|
||||
|
||||
- **元数据类型:** bool
|
||||
|
||||
- **关联项:**
|
||||
|
||||
Meta:[BlueprintType](BlueprintType.md), [BlueprintInternalUseOnlyHierarchical](BlueprintInternalUseOnlyHierarchical.md)
|
||||
|
||||
UFUNCTION:[BlueprintInternalUseOnly](../../Specifier/UFUNCTION/UHT/BlueprintInternalUseOnly/BlueprintInternalUseOnly.md)
|
||||
|
||||
USTRUCT:[BlueprintInternalUseOnly](../../Specifier/USTRUCT/Blueprint/BlueprintInternalUseOnly/BlueprintInternalUseOnly.md)
|
||||
|
||||
- **常用程度:** ★★★
|
||||
|
||||
也可以用在USTRUCT上,标明该结构不可用来定义新BP变量,但可作为别的类的成员变量暴露和变量传递。
|
||||
|
||||
用在UFUNCTION上时:此函数是一个内部实现细节,用于实现另一个函数或节点。其从未直接在蓝图图表中公开。
|
@@ -0,0 +1,19 @@
|
||||
# BlueprintInternalUseOnlyHierarchical
|
||||
|
||||
- **功能描述:** 标明该结构及其子类都不暴露给用户定义和使用,均只能在蓝图系统内部使用
|
||||
|
||||
- **使用位置:** USTRUCT
|
||||
|
||||
- **引擎模块:** Blueprint
|
||||
|
||||
- **元数据类型:** bool
|
||||
|
||||
- **关联项:**
|
||||
|
||||
Meta:[BlueprintInternalUseOnly](BlueprintInternalUseOnly.md), [BlueprintType](BlueprintType.md)
|
||||
|
||||
USTRUCT:[BlueprintInternalUseOnlyHierarchical ](../../Specifier/USTRUCT/Blueprint/BlueprintInternalUseOnlyHierarchical.md)
|
||||
|
||||
- **常用程度:** ★
|
||||
|
||||
指明一个不向最终用户公开的BlueprintType类型的结构以及其派生的结构。
|
@@ -0,0 +1,19 @@
|
||||
# BlueprintPrivate
|
||||
|
||||
- **功能描述:** 指定该函数或属性只能在本类中被调用或读写,类似C++中的private的作用域限制。不可在别的蓝图类里访问。
|
||||
- **使用位置:** UFUNCTION, UPROPERTY
|
||||
- **元数据类型:** bool
|
||||
- **关联项:** [BlueprintProtected](../BlueprintProtected/BlueprintProtected.md)
|
||||
- **常用程度:** ★★
|
||||
|
||||
在函数细节面板上可以设置函数的访问权限:
|
||||
|
||||

|
||||
|
||||
造成的结果就是在函数上增加BlueprintPrivate=“true”
|
||||
|
||||
在细节面板上可以设置属性的
|
||||
|
||||

|
||||
|
||||
结果也是在属性上增加BlueprintPrivate=“true”
|
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 21 KiB |
@@ -0,0 +1,331 @@
|
||||
# BlueprintProtected
|
||||
|
||||
- **功能描述:** 指定该函数或属性只能在本类以及子类中被调用或读写,类似C++中的protected作用域限制。不可在别的蓝图类里访问。
|
||||
- **使用位置:** UFUNCTION, UPROPERTY
|
||||
- **引擎模块:** Blueprint
|
||||
- **元数据类型:** bool
|
||||
- **关联项:** [BlueprintPrivate](../BlueprintPrivate/BlueprintPrivate.md), [AllowPrivateAccess](../AllowPrivateAccess/AllowPrivateAccess.md)
|
||||
- **常用程度:** ★★★
|
||||
|
||||
作用在函数上:
|
||||
|
||||
标记该函数只能在本类以及子类中被调用,类似C++中的protected函数的作用域限制。不可在别的蓝图类里调用。
|
||||
|
||||
作用在属性上时,标明该属性只能在本类或派生类里进行读写,但不能在别的蓝图类里访问。
|
||||
|
||||
指定该函数或属性只能在本类以及子类中被调用或读写,类似C++中的protected函数的作用域限制。不可在别的蓝图类里访问。
|
||||
|
||||
## 测试代码:
|
||||
|
||||
```cpp
|
||||
UCLASS(Blueprintable, BlueprintType)
|
||||
class INSIDER_API AMyFunction_Access :public AActor
|
||||
{
|
||||
public:
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
//(BlueprintProtected = true, ModuleRelativePath = Function/MyFunction_Access.h)
|
||||
//FUNC_Final | FUNC_Native | FUNC_Public | FUNC_BlueprintCallable
|
||||
UFUNCTION(BlueprintCallable, meta = (BlueprintProtected = "true"))
|
||||
void MyNative_HasProtected() {}
|
||||
|
||||
//(BlueprintPrivate = true, ModuleRelativePath = Function/MyFunction_Access.h)
|
||||
//FUNC_Final | FUNC_Native | FUNC_Public | FUNC_BlueprintCallable
|
||||
UFUNCTION(BlueprintCallable, meta = (BlueprintPrivate = "true"))
|
||||
void MyNative_HasPrivate() {}
|
||||
public:
|
||||
//FUNC_Final | FUNC_Native | FUNC_Public | FUNC_BlueprintCallable
|
||||
UFUNCTION(BlueprintCallable)
|
||||
void MyNative_NativePublic() {}
|
||||
protected:
|
||||
//FUNC_Final | FUNC_Native | FUNC_Protected | FUNC_BlueprintCallable
|
||||
UFUNCTION(BlueprintCallable)
|
||||
void MyNative_NativeProtected() {}
|
||||
private:
|
||||
//FUNC_Final | FUNC_Native | FUNC_Private | FUNC_BlueprintCallable
|
||||
UFUNCTION(BlueprintCallable)
|
||||
void MyNative_NativePrivate() {}
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## 测试效果:
|
||||
|
||||
蓝图中的子类(BPA_Access_Base继承自AMyFunction_Access )效果:
|
||||
|
||||
可见BlueprintProtected可以被子类调用,但是BlueprintPrivate只能在本类(C++类中定义的只能在C++中调用,蓝图中定义的只能在蓝图本类中调用)。而在C++中用protected或private修饰的函数会相应的增加FUNC_Protected和FUNC_Private,但是实际上并不会发生作用。因为机制的设计目的就是如此(详见后文解释)。
|
||||
|
||||
而在BPA_Access_Base中直接定义的MyBPProtected和MyBPPrivate通过在函数细节面板上直接设置AccessSpecifier,可以在本类都可以调用,但是MyBPPrivate在更加的蓝图子类无法被调用。
|
||||
|
||||

|
||||
|
||||
蓝图中的子类(BPA_Access_Child继承自BPA_Access_Base)效果:
|
||||
|
||||
可见MyNative函数的访问一样。而MyBPPrivate则不能被调用了,这和我们预想的规则一样。
|
||||
|
||||

|
||||
|
||||
而在外部类中(BPA_Access_Other,继承自Actor),通过BPA_Access_Base或BPA_Access_Child对象实例访问函数的时候,发现带有BlueprintProtected和BlueprintPrivate都不能被调用。BP的函数也只有AccessSpecifier为默认Public的可以调用。这个规则也很符合预期。
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
在蓝图右键上是否可以选择该函数的过滤逻辑:
|
||||
|
||||
如果是static函数,则总是可以。否则必须没有BlueprintProtected或BlueprintPrivate才可以是Public可以被选择出来的。
|
||||
|
||||
如果是Private,则外部类必须是定义的类本身。
|
||||
|
||||
如果是Protected,则外部类只需要是定义的类或子类。
|
||||
|
||||
```cpp
|
||||
static bool BlueprintActionFilterImpl::IsFieldInaccessible(FBlueprintActionFilter const& Filter, FBlueprintActionInfo& BlueprintAction)
|
||||
{
|
||||
bool const bIsProtected = Field.HasMetaData(FBlueprintMetadata::MD_Protected);
|
||||
bool const bIsPrivate = Field.HasMetaData(FBlueprintMetadata::MD_Private);
|
||||
bool const bIsPublic = !bIsPrivate && !bIsProtected;
|
||||
|
||||
if( !bIsPublic )
|
||||
{
|
||||
UClass const* ActionOwner = BlueprintAction.GetOwnerClass();
|
||||
for (UBlueprint const* Blueprint : FilterContext.Blueprints)
|
||||
{
|
||||
UClass const* BpClass = GetAuthoritativeBlueprintClass(Blueprint);
|
||||
if (!ensureMsgf(BpClass != nullptr
|
||||
, TEXT("Unable to resolve IsFieldInaccessible() - Blueprint (%s) missing an authoratative class (skel: %s, generated: %s, parent: %s)")
|
||||
, *Blueprint->GetName()
|
||||
, Blueprint->SkeletonGeneratedClass ? *Blueprint->SkeletonGeneratedClass->GetName() : TEXT("[NULL]")
|
||||
, Blueprint->GeneratedClass ? *Blueprint->GeneratedClass->GetName() : TEXT("[NULL]")
|
||||
, Blueprint->ParentClass ? *Blueprint->ParentClass->GetName() : TEXT("[NULL]")))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// private functions are only accessible from the class they belong to
|
||||
if (bIsPrivate && !IsClassOfType(BpClass, ActionOwner, /*bNeedsExactMatch =*/true))
|
||||
{
|
||||
bIsFilteredOut = true;
|
||||
break;
|
||||
}
|
||||
else if (bIsProtected && !IsClassOfType(BpClass, ActionOwner))
|
||||
{
|
||||
bIsFilteredOut = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool UEdGraphSchema_K2::ClassHasBlueprintAccessibleMembers(const UClass* InClass) const
|
||||
{
|
||||
// @TODO Don't show other blueprints yet...
|
||||
UBlueprint* ClassBlueprint = UBlueprint::GetBlueprintFromClass(InClass);
|
||||
if (!InClass->HasAnyClassFlags(CLASS_Deprecated | CLASS_NewerVersionExists) && (ClassBlueprint == NULL))
|
||||
{
|
||||
// Find functions
|
||||
for (TFieldIterator<UFunction> FunctionIt(InClass, EFieldIteratorFlags::IncludeSuper); FunctionIt; ++FunctionIt)
|
||||
{
|
||||
UFunction* Function = *FunctionIt;
|
||||
const bool bIsBlueprintProtected = Function->GetBoolMetaData(FBlueprintMetadata::MD_Protected);
|
||||
const bool bHidden = FObjectEditorUtils::IsFunctionHiddenFromClass(Function, InClass);
|
||||
if (UEdGraphSchema_K2::CanUserKismetCallFunction(Function) && !bIsBlueprintProtected && !bHidden)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Find vars
|
||||
for (TFieldIterator<FProperty> PropertyIt(InClass, EFieldIteratorFlags::IncludeSuper); PropertyIt; ++PropertyIt)
|
||||
{
|
||||
FProperty* Property = *PropertyIt;
|
||||
if (CanUserKismetAccessVariable(Property, InClass, CannotBeDelegate))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
```
|
||||
|
||||
在BP中定义的函数如果通过AccessSpecifier设置为Protected或Private,也会相应把该函数加上FUNC_Protected或FUNC_Private。从而实际上影响该函数的作用域。但源码中很多判断会先判断是否Native函数,如果是就不继续做限制。因此我们可以理解这是UE机制的有意为之,故意不把C++里的protected和private作用域算进去,而要求你必须自己手动显式的写上BlueprintProtected或BlueprintPrivate,这样避免有时的模糊不清。
|
||||
|
||||
```cpp
|
||||
bool UEdGraphSchema_K2::CanFunctionBeUsedInGraph(const UClass* InClass, const UFunction* InFunction, const UEdGraph* InDestGraph, uint32 InAllowedFunctionTypes, bool bInCalledForEach, FText* OutReason) const
|
||||
{
|
||||
const bool bIsNotNative = !FBlueprintEditorUtils::IsNativeSignature(InFunction);
|
||||
if(bIsNotNative)
|
||||
{
|
||||
// Blueprint functions visibility flags can be enforced in blueprints - native functions
|
||||
// are often using these flags to only hide functionality from other native functions:
|
||||
const bool bIsProtected = (InFunction->FunctionFlags & FUNC_Protected) != 0;
|
||||
}
|
||||
|
||||
bool UK2Node_CallFunction::IsActionFilteredOut(FBlueprintActionFilter const& Filter)
|
||||
{
|
||||
bool bIsFilteredOut = false;
|
||||
for(UEdGraph* TargetGraph : Filter.Context.Graphs)
|
||||
{
|
||||
bIsFilteredOut |= !CanPasteHere(TargetGraph);
|
||||
}
|
||||
|
||||
if(const UFunction* TargetFunction = GetTargetFunction())
|
||||
{
|
||||
const bool bIsProtected = (TargetFunction->FunctionFlags & FUNC_Protected) != 0;
|
||||
const bool bIsPrivate = (TargetFunction->FunctionFlags & FUNC_Private) != 0;
|
||||
const UClass* OwningClass = TargetFunction->GetOwnerClass();
|
||||
if( (bIsProtected || bIsPrivate) && !FBlueprintEditorUtils::IsNativeSignature(TargetFunction) && OwningClass)
|
||||
{
|
||||
OwningClass = OwningClass->GetAuthoritativeClass();
|
||||
// we can filter private and protected blueprints that are unrelated:
|
||||
bool bAccessibleInAll = true;
|
||||
for (const UBlueprint* Blueprint : Filter.Context.Blueprints)
|
||||
{
|
||||
UClass* AuthoritativeClass = Blueprint->GeneratedClass;
|
||||
if(!AuthoritativeClass)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if(bIsPrivate)
|
||||
{
|
||||
bAccessibleInAll = bAccessibleInAll && AuthoritativeClass == OwningClass;
|
||||
}
|
||||
else if(bIsProtected)
|
||||
{
|
||||
bAccessibleInAll = bAccessibleInAll && AuthoritativeClass->IsChildOf(OwningClass);
|
||||
}
|
||||
}
|
||||
|
||||
if(!bAccessibleInAll)
|
||||
{
|
||||
bIsFilteredOut = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bIsFilteredOut;
|
||||
}
|
||||
```
|
||||
|
||||
# 作用在属性上:
|
||||
|
||||
作用在属性上时,标明该属性只能在本类或派生类里进行读写,但不能在别的蓝图类里访问。
|
||||
|
||||
测试代码:
|
||||
|
||||
```cpp
|
||||
UCLASS(Blueprintable, BlueprintType)
|
||||
class INSIDER_API AMyFunction_Access :public AActor
|
||||
{
|
||||
public:
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
//(BlueprintProtected = true, Category = MyFunction_Access, ModuleRelativePath = Function/MyFunction_Access.h)
|
||||
//CPF_BlueprintVisible | CPF_ZeroConstructor | CPF_IsPlainOldData | CPF_NoDestructor | CPF_HasGetValueTypeHash | CPF_NativeAccessSpecifierPublic
|
||||
UPROPERTY(BlueprintReadWrite,meta = (BlueprintProtected = "true"))
|
||||
int32 MyNativeInt_HasProtected;
|
||||
|
||||
//(BlueprintPrivate = true, Category = MyFunction_Access, ModuleRelativePath = Function/MyFunction_Access.h)
|
||||
//CPF_BlueprintVisible | CPF_ZeroConstructor | CPF_IsPlainOldData | CPF_NoDestructor | CPF_HasGetValueTypeHash | CPF_NativeAccessSpecifierPublic
|
||||
UPROPERTY(BlueprintReadWrite,meta = (BlueprintPrivate = "true"))
|
||||
int32 MyNativeInt_HasPrivate;
|
||||
|
||||
public:
|
||||
//CPF_BlueprintVisible | CPF_ZeroConstructor | CPF_IsPlainOldData | CPF_NoDestructor | CPF_HasGetValueTypeHash | CPF_NativeAccessSpecifierPublic
|
||||
UPROPERTY(BlueprintReadWrite)
|
||||
int32 MyNativeInt_NativePublic;
|
||||
protected:
|
||||
//CPF_BlueprintVisible | CPF_ZeroConstructor | CPF_IsPlainOldData | CPF_NoDestructor | CPF_Protected | CPF_HasGetValueTypeHash | CPF_NativeAccessSpecifierProtected
|
||||
UPROPERTY(BlueprintReadOnly)
|
||||
int32 MyNativeInt_NativeProtected;
|
||||
private:
|
||||
//CPF_Edit | CPF_ZeroConstructor | CPF_IsPlainOldData | CPF_NoDestructor | CPF_HasGetValueTypeHash | CPF_NativeAccessSpecifierPrivate
|
||||
//error : BlueprintReadWrite should not be used on private members
|
||||
UPROPERTY(EditAnywhere)
|
||||
int32 MyNativeInt_NativePrivate;
|
||||
};
|
||||
```
|
||||
|
||||
蓝图效果:
|
||||
|
||||
在其子类BPA_Access_Base测试,发现除了MyNativeInt_HasPrivate都可以访问。这符合逻辑,毕竟Private的含义就是只有在本类才可以访问。
|
||||
|
||||
而在本蓝图类定义的MyBPIntPrivate因为勾上了Private,会导致该属性增加了BlueprintPrivate = true的meta,但因为是本类里定义的,所以在本类里也依然可以读写访问。
|
||||
|
||||

|
||||
|
||||
继续在蓝图中的子类(BPA_Access_Child继承自BPA_Access_Base)效果:
|
||||
|
||||
Protected的属性依然都可以访问,但是MyBPIntPrivate属性因为是Private的,因此都不能读写,如果强制粘贴节点,会在编译的时候报错。Private的含义是只在本类中才可以访问。
|
||||
|
||||

|
||||
|
||||
而在外部类中(BPA_Access_Other,继承自Actor),通过BPA_Access_Base或BPA_Access_Child对象实例访问属性的时候:带有BlueprintProtected和BlueprintPrivate都不能访问。而C++中的protected修饰并无影响。
|
||||
|
||||
而MyBPIntPrivate因为是Private所以不能访问。
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
在源码里搜索CPF_NativeAccessSpecifierProtected,发现并无使用的地方。
|
||||
|
||||
而CPF_NativeAccessSpecifierPrivate只在IsPropertyPrivate中引用,后者也只在蓝图编译检测线程安全的时候被检测到。因此CPF_NativeAccessSpecifierPrivate也实际上并无真正的被用来做作用域的限制。
|
||||
|
||||
综合二者,这也是在C++中protected和private并不在蓝图中造成影响的原因。但UHT会阻止private变量上的BlueprintReadWrite或BlueprintReadOnly,造成事实上的无法在蓝图中访问,达成了无法在蓝图子类里访问C++基类private变量的效果。
|
||||
|
||||
因此实际上在蓝图中的变量作用域控制,采用的元数据BlueprintProtected 和BlueprintPrivate,在蓝图右键能否创建属性读写节点的逻辑在上面的BlueprintActionFilterImpl::IsFieldInaccessible函数中体现。而编译的时候判断一个属性是否可读写的逻辑在IsPropertyWritableInBlueprint和IsPropertyReadableInBlueprint这两个函数,如果最终的状态结果是Private,则说明不可访问。在UK2Node_VariableGet和UK2Node_VariableSet的ValidateNodeDuringCompilation,会检测出来并报错。
|
||||
|
||||
```cpp
|
||||
bool FBlueprintEditorUtils::IsPropertyPrivate(const FProperty* Property)
|
||||
{
|
||||
return Property->HasAnyPropertyFlags(CPF_NativeAccessSpecifierPrivate) || Property->GetBoolMetaData(FBlueprintMetadata::MD_Private);
|
||||
}
|
||||
|
||||
FBlueprintEditorUtils::EPropertyWritableState FBlueprintEditorUtils::IsPropertyWritableInBlueprint(const UBlueprint* Blueprint, const FProperty* Property)
|
||||
{
|
||||
if (Property)
|
||||
{
|
||||
if (!Property->HasAnyPropertyFlags(CPF_BlueprintVisible))
|
||||
{
|
||||
return EPropertyWritableState::NotBlueprintVisible;
|
||||
}
|
||||
if (Property->HasAnyPropertyFlags(CPF_BlueprintReadOnly))
|
||||
{
|
||||
return EPropertyWritableState::BlueprintReadOnly;
|
||||
}
|
||||
if (Property->GetBoolMetaData(FBlueprintMetadata::MD_Private))
|
||||
{
|
||||
const UClass* OwningClass = Property->GetOwnerChecked<UClass>();
|
||||
if (OwningClass->ClassGeneratedBy.Get() != Blueprint)
|
||||
{
|
||||
return EPropertyWritableState::Private;
|
||||
}
|
||||
}
|
||||
}
|
||||
return EPropertyWritableState::Writable;
|
||||
}
|
||||
|
||||
FBlueprintEditorUtils::EPropertyReadableState FBlueprintEditorUtils::IsPropertyReadableInBlueprint(const UBlueprint* Blueprint, const FProperty* Property)
|
||||
{
|
||||
if (Property)
|
||||
{
|
||||
if (!Property->HasAnyPropertyFlags(CPF_BlueprintVisible))
|
||||
{
|
||||
return EPropertyReadableState::NotBlueprintVisible;
|
||||
}
|
||||
if (Property->GetBoolMetaData(FBlueprintMetadata::MD_Private))
|
||||
{
|
||||
const UClass* OwningClass = Property->GetOwnerChecked<UClass>();
|
||||
if (OwningClass->ClassGeneratedBy.Get() != Blueprint)
|
||||
{
|
||||
return EPropertyReadableState::Private;
|
||||
}
|
||||
}
|
||||
}
|
||||
return EPropertyReadableState::Readable;
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 38 KiB |
After Width: | Height: | Size: 86 KiB |
After Width: | Height: | Size: 275 KiB |
After Width: | Height: | Size: 445 KiB |
After Width: | Height: | Size: 154 KiB |
After Width: | Height: | Size: 157 KiB |
@@ -0,0 +1,18 @@
|
||||
# BlueprintSetter
|
||||
|
||||
- **功能描述:** 采用一个自定义的set函数来读取。
|
||||
会默认设置BlueprintReadWrite.
|
||||
|
||||
- **使用位置:** UFUNCTION, UPROPERTY
|
||||
|
||||
- **引擎模块:** Blueprint
|
||||
|
||||
- **元数据类型:** string="abc"
|
||||
|
||||
- **关联项:**
|
||||
|
||||
UFUNCTION:[BlueprintSetter](../../Specifier/UFUNCTION/Blueprint/BlueprintSetter.md)
|
||||
|
||||
UPROPERTY:[BlueprintSetter](../../Specifier/UPROPERTY/Blueprint/BlueprintSetter.md)
|
||||
|
||||
- **常用程度:** ★★★
|
@@ -0,0 +1,107 @@
|
||||
# BlueprintThreadSafe
|
||||
|
||||
- **功能描述:** 用在类上或函数上,标记类里的函数都是线程安全的。
|
||||
这样就可以在动画蓝图等非游戏线程被调用了。
|
||||
- **使用位置:** UCLASS, UFUNCTION
|
||||
- **引擎模块:** Blueprint
|
||||
- **元数据类型:** bool
|
||||
- **限制类型:** 从实践上,类一般是BlueprintFunctionLibrary
|
||||
- **关联项:** [NotBlueprintThreadSafe](../NotBlueprintThreadSafe.md)
|
||||
- **常用程度:** ★★★
|
||||
|
||||
动画蓝图的AimGraph默认是开启线程安全Update的。设置在ClassSettings里(默认是打开的)
|
||||
|
||||

|
||||
|
||||
可参考官方文档的**CPU Thread Usage and Performance这一节**
|
||||
|
||||
[Graphing in Animation Blueprints](https://docs.unrealengine.com/5.3/en-US/graphing-in-animation-blueprints-in-unreal-engine/#cputhreadusageandperformance)
|
||||
|
||||
因此AimGraph里的函数要求都得是线程安全的。你的C++函数或者是蓝图里蓝图库里的函数都需要手动标记为ThreadSafe,默认不带ThreadSafe标记的都是不线程安全的。
|
||||
|
||||
在蓝图里,如果在蓝图函数面板中勾上ThreadSafe,这个函数的对象会设置bThreadSafe=True,从而在编译生成的BlueprintGeneratedClass上面设置(BlueprintThreadSafe = true)
|
||||
|
||||

|
||||
|
||||
## 测试蓝图函数库:
|
||||
|
||||
同样的函数,一个打开ThreadSafe,一个没有。没有的那个函数在动画蓝图的AnimGraph里使用的时候,在编译的时候就会触发警告。
|
||||
|
||||

|
||||
|
||||
测试结果:
|
||||
|
||||

|
||||
|
||||
## 在C++里,C++的测试代码:
|
||||
|
||||
```cpp
|
||||
//(BlueprintThreadSafe = , IncludePath = Class/Blueprint/MyClass_ThreadSafe.h, ModuleRelativePath = Class/Blueprint/MyClass_ThreadSafe.h)
|
||||
UCLASS(meta=(BlueprintThreadSafe))
|
||||
class INSIDER_API UMyBlueprintFunctionLibrary_ThreadSafe : public UBlueprintFunctionLibrary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UFUNCTION(BlueprintPure)
|
||||
static float MyFunc_ClassThreadSafe_Default(float value) {return value+100;}
|
||||
|
||||
//(ModuleRelativePath = Class/Blueprint/MyClass_ThreadSafe.h, NotBlueprintThreadSafe = )
|
||||
UFUNCTION(BlueprintPure,meta=(NotBlueprintThreadSafe))
|
||||
static float MyFunc_ClassThreadSafe_FuncNotThreadSafe(float value) {return value+100;}
|
||||
};
|
||||
|
||||
UCLASS()
|
||||
class INSIDER_API UMyBlueprintFunctionLibrary_NoThreadSafe : public UBlueprintFunctionLibrary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
//(BlueprintThreadSafe = , ModuleRelativePath = Class/Blueprint/MyClass_ThreadSafe.h)
|
||||
UFUNCTION(BlueprintPure,meta=(BlueprintThreadSafe))
|
||||
static float MyFunc_ClassDefault_FuncThreadSafe(float value) {return value+100;}
|
||||
|
||||
//(ModuleRelativePath = Class/Blueprint/MyClass_ThreadSafe.h, NotBlueprintThreadSafe = )
|
||||
UFUNCTION(BlueprintPure,meta=(NotBlueprintThreadSafe))
|
||||
static float MyFunc_ClassDefault_FuncNotThreadSafe(float value) {return value+100;}
|
||||
};
|
||||
|
||||
UCLASS()
|
||||
class INSIDER_API UMyBlueprintFunctionLibrary_Default : public UBlueprintFunctionLibrary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UFUNCTION(BlueprintPure)
|
||||
static float MyFunc_ClassDefault_FuncDefault(float value) {return value+100;}
|
||||
};
|
||||
```
|
||||
|
||||
## 动画蓝图的测试效果:
|
||||
|
||||

|
||||
|
||||
## 解析原理:
|
||||
|
||||
```cpp
|
||||
bool FBlueprintEditorUtils::HasFunctionBlueprintThreadSafeMetaData(const UFunction* InFunction)
|
||||
{
|
||||
if(InFunction)
|
||||
{
|
||||
const bool bHasThreadSafeMetaData = InFunction->HasMetaData(FBlueprintMetadata::MD_ThreadSafe);
|
||||
const bool bHasNotThreadSafeMetaData = InFunction->HasMetaData(FBlueprintMetadata::MD_NotThreadSafe);
|
||||
const bool bClassHasThreadSafeMetaData = InFunction->GetOwnerClass() && InFunction->GetOwnerClass()->HasMetaData(FBlueprintMetadata::MD_ThreadSafe);
|
||||
|
||||
// Native functions need to just have the correct class/function metadata
|
||||
const bool bThreadSafeNative = InFunction->HasAnyFunctionFlags(FUNC_Native) && (bHasThreadSafeMetaData || (bClassHasThreadSafeMetaData && !bHasNotThreadSafeMetaData));
|
||||
|
||||
// Script functions get their flag propagated from their entry point, and dont pay heed to class metadata
|
||||
const bool bThreadSafeScript = !InFunction->HasAnyFunctionFlags(FUNC_Native) && bHasThreadSafeMetaData;
|
||||
|
||||
return bThreadSafeNative || bThreadSafeScript;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
```
|
||||
|
||||
可以从逻辑上看出,如果在UCLASS上带上了BlueprintThreadSafe,则其内部的函数就默认是线程安全,除非特意手动加上NotBlueprintThreadSafe来排除。而如果UCLASS上没有标记,则需一个个手动的在UFUNCTION上标记BlueprintThreadSafe。两种方式都可以。
|
||||
|
||||
注意UCLASS(meta=(NotBlueprintThreadSafe))这种是没有被识别判断的,因此并没有什么意义。
|
After Width: | Height: | Size: 42 KiB |
After Width: | Height: | Size: 35 KiB |
After Width: | Height: | Size: 205 KiB |
After Width: | Height: | Size: 340 KiB |
After Width: | Height: | Size: 6.2 KiB |
@@ -0,0 +1,25 @@
|
||||
# BlueprintType
|
||||
|
||||
- **功能描述:** 表明可以作为一个蓝图变量
|
||||
|
||||
- **使用位置:** UCLASS, UENUM, UINTERFACE, USTRUCT
|
||||
|
||||
- **引擎模块:** Blueprint
|
||||
|
||||
- **元数据类型:** bool
|
||||
|
||||
- **关联项:**
|
||||
|
||||
UCLASS:[Blueprintable](../../Specifier/UCLASS/Blueprint/Blueprintable/Blueprintable.md), [NotBlueprintable](../../Specifier/UCLASS/Blueprint/NotBlueprintable.md), [BlueprintType](../../Specifier/UCLASS/Blueprint/BlueprintType/BlueprintType.md), [NotBlueprintType](../../Specifier/UCLASS/Blueprint/NotBlueprintType.md)
|
||||
|
||||
Meta:[BlueprintInternalUseOnly](BlueprintInternalUseOnly.md), [BlueprintInternalUseOnlyHierarchical](BlueprintInternalUseOnlyHierarchical.md)
|
||||
|
||||
UENUM:[BlueprintType](../../Specifier/UENUM/BlueprintType.md)
|
||||
|
||||
UFUNCTION:[BlueprintInternalUseOnly](../../Specifier/UFUNCTION/UHT/BlueprintInternalUseOnly/BlueprintInternalUseOnly.md)
|
||||
|
||||
UINTERFACE:[Blueprintable](../../Specifier/UINTERFACE/Blueprint/Blueprintable/Blueprintable.md), [NotBlueprintable](../../Specifier/UINTERFACE/Blueprint/NotBlueprintable/NotBlueprintable.md)
|
||||
|
||||
USTRUCT:[BlueprintInternalUseOnly](../../Specifier/USTRUCT/Blueprint/BlueprintInternalUseOnly/BlueprintInternalUseOnly.md), [BlueprintType](../../Specifier/USTRUCT/Blueprint/BlueprintType/BlueprintType.md)
|
||||
|
||||
- **常用程度:** ★★★★★
|
@@ -0,0 +1,43 @@
|
||||
# CPP_Default_XXX
|
||||
|
||||
- **功能描述:** XXX=参数名字
|
||||
- **使用位置:** UPARAM
|
||||
- **引擎模块:** Blueprint
|
||||
- **元数据类型:** string="abc"
|
||||
- **常用程度:** ★★★★★
|
||||
|
||||
在UFUNCTION的meta上保存参数的默认值。
|
||||
|
||||
## 测试代码:
|
||||
|
||||
```cpp
|
||||
//(CPP_Default_intValue = 123, CPP_Default_intValue2 = 456, ModuleRelativePath = Function/Param/MyFunction_TestParam.h)
|
||||
UFUNCTION(BlueprintCallable)
|
||||
FString MyFuncTestParam_DefaultInt2(int intValue=123,int intValue2=456);
|
||||
```
|
||||
|
||||
在Meta里也可以直接写属性的默认值,如Duration。
|
||||
|
||||
```cpp
|
||||
UFUNCTION(BlueprintCallable, Category="Utilities|FlowControl", meta=(Latent, WorldContext="WorldContextObject", LatentInfo="LatentInfo", Duration="0.2", Keywords="sleep"))
|
||||
static ENGINE_API void MyDelay(const UObject* WorldContextObject, float Duration, struct FLatentActionInfo LatentInfo );
|
||||
```
|
||||
|
||||
## 原理代码:
|
||||
|
||||
在UEdGraphSchema_K2::FindFunctionParameterDefaultValue里会尝试找该参数名称对应的Meta。如果找不到则会继续找CPP_Default_ParamName这个名称。然后设置到Pin->AutogeneratedDefaultValue
|
||||
|
||||
```cpp
|
||||
bool UK2Node_CallFunction::CreatePinsForFunctionCall(const UFunction* Function)
|
||||
{
|
||||
FString ParamValue;
|
||||
if (K2Schema->FindFunctionParameterDefaultValue(Function, Param, ParamValue))
|
||||
{
|
||||
K2Schema->SetPinAutogeneratedDefaultValue(Pin, ParamValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
K2Schema->SetPinAutogeneratedDefaultValueBasedOnType(Pin);
|
||||
}
|
||||
}
|
||||
```
|
@@ -0,0 +1,15 @@
|
||||
# CallInEditor
|
||||
|
||||
- **功能描述:** 可以在Actor的细节面板上作为一个按钮来调用该函数。
|
||||
|
||||
- **使用位置:** UFUNCTION
|
||||
|
||||
- **引擎模块:** Blueprint
|
||||
|
||||
- **元数据类型:** bool
|
||||
|
||||
- **关联项:**
|
||||
|
||||
UFUNCTION:[CallInEditor](../../Specifier/UFUNCTION/Blueprint/CallInEditor/CallInEditor.md)
|
||||
|
||||
- **常用程度:** ★★★★★
|
@@ -0,0 +1,39 @@
|
||||
# CallableWithoutWorldContext
|
||||
|
||||
- **功能描述:** 让函数也可以脱离WorldContextObject而使用
|
||||
- **使用位置:** UFUNCTION
|
||||
- **元数据类型:** bool
|
||||
- **关联项:** [WorldContext](../WorldContext/WorldContext.md)
|
||||
|
||||
让函数也可以脱离WorldContextObject而使用。
|
||||
|
||||
CallableWithoutWorldContext 是配合WorldContext或DefaultToSelf来使用,这二者会使得一个函数会要求外部传入一个WorldContext对象才能调用。因此这种函数在没有实现GetWorld的Object子类里就不能调用。但有时某些函数又不一定必须得有WorldContextObject才能工作,比如PrintString或VisualLogger里的函数。
|
||||
|
||||
## 测试代码:
|
||||
|
||||
```cpp
|
||||
UFUNCTION(BlueprintPure, meta = (WorldContext = "WorldContextObject"))
|
||||
static FString MyFunc_HasWorldContextMeta(const UObject* WorldContextObject, FString name, FString value);
|
||||
|
||||
UFUNCTION(BlueprintCallable, meta = (WorldContext = "WorldContextObject",CallableWithoutWorldContext))
|
||||
static FString MyFunc_CallableWithoutWorldContext(const UObject* WorldContextObject, FString name, FString value);
|
||||
|
||||
UCLASS(Blueprintable, BlueprintType)
|
||||
class INSIDER_API UMyObject_NoGetWorld :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
};
|
||||
```
|
||||
|
||||
## 蓝图测试效果:
|
||||
|
||||
在UMyObject_NoGetWorld 的子类内,MyFunc_HasWorldContextMeta不能调用,因为其外部类必须提供WorldContextObject。而MyFunc_CallableWithoutWorldContext可以调用,可以接受不提供WorldContextObject。
|
||||
|
||||

|
||||
|
||||
## 源码里典型的应用是:
|
||||
|
||||
```cpp
|
||||
UFUNCTION(BlueprintCallable, meta=(WorldContext="WorldContextObject", CallableWithoutWorldContext, Keywords = "log print", AdvancedDisplay = "2", DevelopmentOnly), Category="Development")
|
||||
static ENGINE_API void PrintString(const UObject* WorldContextObject, const FString& InString = FString(TEXT("Hello")), bool bPrintToScreen = true, bool bPrintToLog = true, FLinearColor TextColor = FLinearColor(0.0f, 0.66f, 1.0f), float Duration = 2.f, const FName Key = NAME_None);
|
||||
```
|
After Width: | Height: | Size: 254 KiB |
@@ -0,0 +1,15 @@
|
||||
# CannotImplementInterfaceInBlueprint
|
||||
|
||||
- **功能描述:** 指定该接口不能在蓝图中实现
|
||||
|
||||
- **引擎模块:** Blueprint
|
||||
|
||||
- **元数据类型:** bool
|
||||
|
||||
- **关联项:**
|
||||
|
||||
UINTERFACE:[NotBlueprintable](../../Specifier/UINTERFACE/Blueprint/NotBlueprintable/NotBlueprintable.md)
|
||||
|
||||
- **常用程度:** ★★★
|
||||
|
||||
和UINTERFACE(NotBlueprintable)的效果一样,指定不能在蓝图中继承
|
@@ -0,0 +1,99 @@
|
||||
# CommutativeAssociativeBinaryOperator
|
||||
|
||||
- **功能描述:** 标记一个二元运算函数的运算支持交换律和结合律,在蓝图节点上增加一个“+”引脚,允许动态的直接添加多个输入值。
|
||||
- **使用位置:** UFUNCTION
|
||||
- **引擎模块:** Blueprint
|
||||
- **元数据类型:** bool
|
||||
- **常用程度:** ★★★★
|
||||
|
||||
标记一个二元运算函数的运算支持交换律和结合律,在蓝图节点上增加一个“+”引脚,允许动态的直接添加多个输入值。而不需要自己手动创建多个本函数节点来运算,这是蓝图提供的便利功能之一。
|
||||
|
||||
CommutativeAssociativeBinaryOperator的限制是函数必须是BlueprintPure并且有两个参数。否则会产生编译报错或功能失效。
|
||||
|
||||
## 测试代码:
|
||||
|
||||
```cpp
|
||||
UFUNCTION(BlueprintCallable, meta = (CommutativeAssociativeBinaryOperator))
|
||||
static float My_CallableAdd_WithBinaryOperator(float A, float B) { return A + B; }
|
||||
|
||||
UFUNCTION(BlueprintPure, meta = (CommutativeAssociativeBinaryOperator))
|
||||
static float My_PureAdd_WithBinaryOperator(float A, float B) { return A + B; }
|
||||
|
||||
UFUNCTION(BlueprintPure, meta = ())
|
||||
static float My_Add_NoBinaryOperator(float A, float B) { return A + B; }
|
||||
|
||||
// error : Commutative associative binary operators must have exactly 2 parameters of the same type and a return value.
|
||||
//UFUNCTION(BlueprintPure, meta = (CommutativeAssociativeBinaryOperator))
|
||||
// static float My_PureAdd3_WithBinaryOperator(float A, float B,float C) { return A + B+C; }
|
||||
```
|
||||
|
||||
## 蓝图效果:
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
标记CommutativeAssociativeBinaryOperator的函数会采用UK2Node_CommutativeAssociativeBinaryOperator来生成节点。这个二元运算满足交换率和结合律,因此可以通过多次的调用本函数来支持多个输入值的运算。在UK2Node_CommutativeAssociativeBinaryOperator展开的时候,会创建中间的多个UK2Node_CommutativeAssociativeBinaryOperator来形成调用序列。
|
||||
|
||||
在源码中的应用是一些二元运算,在UKismetMathLibrary中有大量的运用,典型的比如FVector的互相运算。
|
||||
|
||||
```cpp
|
||||
void UK2Node_CommutativeAssociativeBinaryOperator::ExpandNode(FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph)
|
||||
{
|
||||
Super::ExpandNode(CompilerContext, SourceGraph);
|
||||
|
||||
if (NumAdditionalInputs > 0)
|
||||
{
|
||||
const UEdGraphSchema_K2* Schema = CompilerContext.GetSchema();
|
||||
|
||||
UEdGraphPin* LastOutPin = NULL;
|
||||
const UFunction* const Function = GetTargetFunction();
|
||||
|
||||
const UEdGraphPin* SrcOutPin = FindOutPin();
|
||||
const UEdGraphPin* SrcSelfPin = FindSelfPin();
|
||||
UEdGraphPin* SrcFirstInput = GetInputPin(0);
|
||||
check(SrcFirstInput);
|
||||
|
||||
for(int32 PinIndex = 0; PinIndex < Pins.Num(); PinIndex++)
|
||||
{
|
||||
UEdGraphPin* CurrentPin = Pins[PinIndex];
|
||||
if( (CurrentPin == SrcFirstInput) || (CurrentPin == SrcOutPin) || (SrcSelfPin == CurrentPin) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
UK2Node_CommutativeAssociativeBinaryOperator* NewOperator = SourceGraph->CreateIntermediateNode<UK2Node_CommutativeAssociativeBinaryOperator>();
|
||||
NewOperator->SetFromFunction(Function);
|
||||
NewOperator->AllocateDefaultPins();
|
||||
CompilerContext.MessageLog.NotifyIntermediateObjectCreation(NewOperator, this);
|
||||
|
||||
UEdGraphPin* NewOperatorInputA = NewOperator->GetInputPin(0);
|
||||
check(NewOperatorInputA);
|
||||
if(LastOutPin)
|
||||
{
|
||||
Schema->TryCreateConnection(LastOutPin, NewOperatorInputA);
|
||||
}
|
||||
else
|
||||
{
|
||||
// handle first created node (SrcFirstInput is skipped, and has no own node).
|
||||
CompilerContext.MovePinLinksToIntermediate(*SrcFirstInput, *NewOperatorInputA);
|
||||
}
|
||||
|
||||
UEdGraphPin* NewOperatorInputB = NewOperator->GetInputPin(1);
|
||||
check(NewOperatorInputB);
|
||||
CompilerContext.MovePinLinksToIntermediate(*CurrentPin, *NewOperatorInputB);
|
||||
|
||||
LastOutPin = NewOperator->FindOutPin();
|
||||
}
|
||||
|
||||
check(LastOutPin);
|
||||
|
||||
UEdGraphPin* TrueOutPin = FindOutPin();
|
||||
check(TrueOutPin);
|
||||
CompilerContext.MovePinLinksToIntermediate(*TrueOutPin, *LastOutPin);
|
||||
|
||||
BreakAllNodeLinks();
|
||||
}
|
||||
}
|
||||
|
||||
```
|
After Width: | Height: | Size: 56 KiB |
@@ -0,0 +1,75 @@
|
||||
# CompactNodeTitle
|
||||
|
||||
- **功能描述:** 使得函数的展示形式变成精简压缩模式,同时指定一个新的精简的名字
|
||||
- **使用位置:** UFUNCTION
|
||||
- **引擎模块:** Blueprint
|
||||
- **元数据类型:** string="abc"
|
||||
- **常用程度:** ★★★
|
||||
|
||||
使得函数的展示形式变成精简压缩模式,同时指定一个新的精简的名字。注意到该模式下就会忽略DisplayName的数据。
|
||||
|
||||
## 测试代码:
|
||||
|
||||
```cpp
|
||||
UFUNCTION(BlueprintCallable, meta = (CompactNodeTitle = "MyCompact",DisplayName="AnotherName"))
|
||||
static int32 MyFunc_HasCompactNodeTitle(FString Name) {return 0;}
|
||||
|
||||
UFUNCTION(BlueprintCallable, meta = ())
|
||||
static int32 MyFunc_NoCompactNodeTitle(FString Name) {return 0;}
|
||||
|
||||
UFUNCTION(BlueprintPure, meta = (CompactNodeTitle = "MyPure",DisplayName="AnotherName"))
|
||||
static int32 MyPure_HasCompactNodeTitle(FString Name) {return 0;}
|
||||
|
||||
UFUNCTION(BlueprintPure, meta = ())
|
||||
static int32 MyPure_NoCompactNodeTitle(FString Name) {return 0;}
|
||||
```
|
||||
|
||||
## 蓝图效果:
|
||||
|
||||
显示效果明显发生了变化。同时我们在蓝图里定义的函数也可以通过这个细节面板上的设置变成压缩模式展示。
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
```cpp
|
||||
bool UK2Node_CallFunction::ShouldDrawCompact(const UFunction* Function)
|
||||
{
|
||||
return (Function != NULL) && Function->HasMetaData(FBlueprintMetadata::MD_CompactNodeTitle);
|
||||
}
|
||||
|
||||
FString UK2Node_CallFunction::GetCompactNodeTitle(const UFunction* Function)
|
||||
{
|
||||
static const FString ProgrammerMultiplicationSymbol = TEXT("*");
|
||||
static const FString CommonMultiplicationSymbol = TEXT("\xD7");
|
||||
|
||||
static const FString ProgrammerDivisionSymbol = TEXT("/");
|
||||
static const FString CommonDivisionSymbol = TEXT("\xF7");
|
||||
|
||||
static const FString ProgrammerConversionSymbol = TEXT("->");
|
||||
static const FString CommonConversionSymbol = TEXT("\x2022");
|
||||
|
||||
const FString& OperatorTitle = Function->GetMetaData(FBlueprintMetadata::MD_CompactNodeTitle);
|
||||
if (!OperatorTitle.IsEmpty())
|
||||
{
|
||||
if (OperatorTitle == ProgrammerMultiplicationSymbol)
|
||||
{
|
||||
return CommonMultiplicationSymbol;
|
||||
}
|
||||
else if (OperatorTitle == ProgrammerDivisionSymbol)
|
||||
{
|
||||
return CommonDivisionSymbol;
|
||||
}
|
||||
else if (OperatorTitle == ProgrammerConversionSymbol)
|
||||
{
|
||||
return CommonConversionSymbol;
|
||||
}
|
||||
else
|
||||
{
|
||||
return OperatorTitle;
|
||||
}
|
||||
}
|
||||
|
||||
return Function->GetName();
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 110 KiB |
@@ -0,0 +1,91 @@
|
||||
# DefaultToSelf
|
||||
|
||||
- **功能描述:** 用在函数上,指定一个参数的默认值为Self值
|
||||
- **使用位置:** UFUNCTION
|
||||
- **引擎模块:** Blueprint
|
||||
- **元数据类型:** bool
|
||||
- **常用程度:** ★★★★★
|
||||
|
||||
使得在蓝图调用的时候更加的便利,当然也要根据这个函数的需要。
|
||||
|
||||
## 测试代码:
|
||||
|
||||
```cpp
|
||||
UCLASS()
|
||||
class INSIDER_API UMyFunctionLibrary_SelfPinTest :public UBlueprintFunctionLibrary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UFUNCTION(BlueprintCallable)
|
||||
static FString PrintProperty_Default(UObject* myOwner,FName propertyName);
|
||||
|
||||
UFUNCTION(BlueprintCallable,meta=(DefaultToSelf="myOwner"))
|
||||
static FString PrintProperty_HasDefaultToSelf(UObject* myOwner,FName propertyName);
|
||||
|
||||
UFUNCTION(BlueprintCallable,meta=(DefaultToSelf="myOwner",hidePin="myOwner"))
|
||||
static FString PrintProperty_HasDefaultToSelf_ButHide(UObject* myOwner,FName propertyName);
|
||||
};
|
||||
```
|
||||
|
||||
蓝图里的节点,可以看出蓝图编译器会自动的把DefaultToSelf指定的函数参数,自动的赋值到Self,当然这个和手动的连到self本质是一样的。额外一点,可以通过HidePin再隐藏掉这个函数参数,这样就默认把该蓝图节点所在的蓝图对象(Self)当作第一个函数参数,显得更加简洁一些。
|
||||
|
||||

|
||||
|
||||
如果是BlueprintPure也是可以的:
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
蓝图中的函数调用在编译的时候,会自动的创建SelfPin(名字为Target)。如果该函数是静态函数或HideSelfPin的标记,则会把SelfPin隐藏起来。其SelfPin的值就是当前蓝图运行时对象的值。因此DefaultToSelf的效果就是蓝图系统会自动的把这个参数的值赋值为被调用处的蓝图运行时对象,相当于C++ this指针的效果
|
||||
|
||||
```cpp
|
||||
bool UK2Node_CallFunction::CreatePinsForFunctionCall(const UFunction* Function)
|
||||
{
|
||||
UEdGraphPin* SelfPin = CreateSelfPin(Function);
|
||||
// Renamed self pin to target
|
||||
SelfPin->PinFriendlyName = LOCTEXT("Target", "Target");
|
||||
}
|
||||
|
||||
UEdGraphPin* FBlueprintNodeStatics::CreateSelfPin(UK2Node* Node, const UFunction* Function)
|
||||
{
|
||||
// Chase up the function's Super chain, the function can be called on any object that is at least that specific
|
||||
const UFunction* FirstDeclaredFunction = Function;
|
||||
while (FirstDeclaredFunction->GetSuperFunction() != nullptr)
|
||||
{
|
||||
FirstDeclaredFunction = FirstDeclaredFunction->GetSuperFunction();
|
||||
}
|
||||
|
||||
// Create the self pin
|
||||
UClass* FunctionClass = CastChecked<UClass>(FirstDeclaredFunction->GetOuter());
|
||||
// we don't want blueprint-function target pins to be formed from the
|
||||
// skeleton class (otherwise, they could be incompatible with other pins
|
||||
// that represent the same type)... this here could lead to a compiler
|
||||
// warning (the GeneratedClass could not have the function yet), but in
|
||||
// that, the user would be reminded to compile the other blueprint
|
||||
if (FunctionClass->ClassGeneratedBy)
|
||||
{
|
||||
FunctionClass = FunctionClass->GetAuthoritativeClass();
|
||||
}
|
||||
|
||||
UEdGraphPin* SelfPin = NULL;
|
||||
if (FunctionClass == Node->GetBlueprint()->GeneratedClass)
|
||||
{
|
||||
// This means the function is defined within the blueprint, so the pin should be a true "self" pin
|
||||
SelfPin = Node->CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Object, UEdGraphSchema_K2::PSC_Self, nullptr, UEdGraphSchema_K2::PN_Self);
|
||||
}
|
||||
else if (FunctionClass->IsChildOf(UInterface::StaticClass()))
|
||||
{
|
||||
SelfPin = Node->CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Interface, FunctionClass, UEdGraphSchema_K2::PN_Self);
|
||||
}
|
||||
else
|
||||
{
|
||||
// This means that the function is declared in an external class, and should reference that class
|
||||
SelfPin = Node->CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Object, FunctionClass, UEdGraphSchema_K2::PN_Self);
|
||||
}
|
||||
check(SelfPin != nullptr);
|
||||
|
||||
return SelfPin;
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 295 KiB |
After Width: | Height: | Size: 266 KiB |
@@ -0,0 +1,15 @@
|
||||
# DisplayName
|
||||
|
||||
- **功能描述:** 此节点在蓝图中的命名将被此处提供的值所取代,而非代码生成的命名。
|
||||
|
||||
- **使用位置:** UCLASS, UENUM::UMETA, UFUNCTION, UPARAM, UPROPERTY
|
||||
|
||||
- **引擎模块:** Blueprint
|
||||
|
||||
- **元数据类型:** string="abc"
|
||||
|
||||
- **关联项:**
|
||||
|
||||
UPARAM:[DisplayName](../../Specifier/UPARAM/Blueprint/DisplayName/DisplayName.md)
|
||||
|
||||
- **常用程度:** ★★★★★
|
@@ -0,0 +1,97 @@
|
||||
# DontUseGenericSpawnObject
|
||||
|
||||
- **功能描述:** 阻止使用蓝图中的Generic Create Object节点来生成本类的对象。
|
||||
- **使用位置:** UCLASS
|
||||
- **引擎模块:** Blueprint
|
||||
- **元数据类型:** bool
|
||||
- **限制类型:** 既非Actor又非ActorComponent的BluprintType类时
|
||||
- **常用程度:** ★★
|
||||
|
||||
用于阻止该类被通用的ConstructObject蓝图节点所构造出来。在源码里典型里使用例子是UDragDropOperation和UUserWidget,前者由UK2Node_CreateDragDropOperation这个专门的节点建出来(内部调用UWidgetBlueprintLibrary::CreateDragDropOperation),后者由CreateWidget创建。因此这种的典型用法是你自己再创建一个New的函数来自己创建该Object。
|
||||
|
||||
## 测试代码:
|
||||
|
||||
```cpp
|
||||
UCLASS(Blueprintable,meta=(DontUseGenericSpawnObject="true"))
|
||||
class INSIDER_API UMyClass_CustomSpawnObject :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(BlueprintReadWrite,EditAnywhere)
|
||||
float MyFloat;
|
||||
|
||||
UFUNCTION(BlueprintCallable)
|
||||
static UMyClass_CustomSpawnObject* CreateMyClassObjectByMyOwnSpawn(float value)
|
||||
{
|
||||
UMyClass_CustomSpawnObject* obj= NewObject<UMyClass_CustomSpawnObject>();
|
||||
obj->MyFloat=value;
|
||||
return obj;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## 测试效果:
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
会提前验证是否包含DontUseGenericSpawnObject元数据,因为是采用GetBoolMetaData,因此必须写上=”true”
|
||||
|
||||
```cpp
|
||||
struct FK2Node_GenericCreateObject_Utils
|
||||
{
|
||||
static bool CanSpawnObjectOfClass(TSubclassOf<UObject> ObjectClass, bool bAllowAbstract)
|
||||
{
|
||||
// Initially include types that meet the basic requirements.
|
||||
// Note: CLASS_Deprecated is an inherited class flag, so any subclass of an explicitly-deprecated class also cannot be spawned.
|
||||
bool bCanSpawnObject = (nullptr != *ObjectClass)
|
||||
&& (bAllowAbstract || !ObjectClass->HasAnyClassFlags(CLASS_Abstract))
|
||||
&& !ObjectClass->HasAnyClassFlags(CLASS_Deprecated | CLASS_NewerVersionExists);
|
||||
|
||||
// UObject is a special case where if we are allowing abstract we are going to allow it through even though it doesn't have BlueprintType on it
|
||||
if (bCanSpawnObject && (!bAllowAbstract || (*ObjectClass != UObject::StaticClass())))
|
||||
{
|
||||
static const FName BlueprintTypeName(TEXT("BlueprintType"));
|
||||
static const FName NotBlueprintTypeName(TEXT("NotBlueprintType"));
|
||||
static const FName DontUseGenericSpawnObjectName(TEXT("DontUseGenericSpawnObject"));
|
||||
|
||||
auto IsClassAllowedLambda = [](const UClass* InClass)
|
||||
{
|
||||
return InClass != AActor::StaticClass()
|
||||
&& InClass != UActorComponent::StaticClass();
|
||||
};
|
||||
|
||||
// Exclude all types in the initial set by default.
|
||||
bCanSpawnObject = false;
|
||||
const UClass* CurrentClass = ObjectClass;
|
||||
|
||||
// Climb up the class hierarchy and look for "BlueprintType." If "NotBlueprintType" is seen first, or if the class is not allowed, then stop searching.
|
||||
while (!bCanSpawnObject && CurrentClass != nullptr && !CurrentClass->GetBoolMetaData(NotBlueprintTypeName) && IsClassAllowedLambda(CurrentClass))
|
||||
{
|
||||
// Include any type that either includes or inherits 'BlueprintType'
|
||||
bCanSpawnObject = CurrentClass->GetBoolMetaData(BlueprintTypeName);
|
||||
|
||||
// Stop searching if we encounter 'BlueprintType' with 'DontUseGenericSpawnObject'
|
||||
if (bCanSpawnObject && CurrentClass->GetBoolMetaData(DontUseGenericSpawnObjectName))
|
||||
{
|
||||
bCanSpawnObject = false;
|
||||
break;
|
||||
}
|
||||
|
||||
CurrentClass = CurrentClass->GetSuperClass();
|
||||
}
|
||||
|
||||
// If we validated the given class, continue walking up the hierarchy to make sure we exclude it if it's an Actor or ActorComponent derivative.
|
||||
while (bCanSpawnObject && CurrentClass != nullptr)
|
||||
{
|
||||
bCanSpawnObject &= IsClassAllowedLambda(CurrentClass);
|
||||
|
||||
CurrentClass = CurrentClass->GetSuperClass();
|
||||
}
|
||||
}
|
||||
|
||||
return bCanSpawnObject;
|
||||
}
|
||||
};
|
||||
```
|
After Width: | Height: | Size: 132 KiB |
@@ -0,0 +1,7 @@
|
||||
# ExpandBoolAsExecs
|
||||
|
||||
- **功能描述:** 是ExpandEnumAsExecs的别名,完全等价其功能。
|
||||
- **使用位置:** UFUNCTION
|
||||
- **元数据类型:** string="abc"
|
||||
- **关联项:** [ExpandEnumAsExecs](ExpandEnumAsExecs/ExpandEnumAsExecs.md)
|
||||
- **常用程度:** ★★★★★
|
@@ -0,0 +1,86 @@
|
||||
# ExpandEnumAsExecs
|
||||
|
||||
- **功能描述:** 指定多个enum或bool类型的函数参数,自动根据条目生成相应的多个输入或输出执行引脚,并根据实参值不同来相应改变控制流。
|
||||
- **使用位置:** UFUNCTION
|
||||
- **引擎模块:** Blueprint
|
||||
- **元数据类型:** strings="a,b,c"
|
||||
- **关联项:** [ExpandBoolAsExecs](../ExpandBoolAsExecs.md)
|
||||
- **常用程度:** ★★★★★
|
||||
|
||||
指定多个enum或bool类型的函数参数,自动根据条目生成相应的多个输入或输出执行引脚,并根据实参值不同来相应改变控制流。
|
||||
|
||||
支持改变输入和输出的Exec,输入Exec只可以一个,但是输出ExecEnum Pin可以多个。但是不能用在BlueprintPure上(都没Exec引脚了)。
|
||||
|
||||
也可以通过‘|’来分隔。
|
||||
|
||||
支持3种参数类型,enum class,TEnumAsByte<EMyExecPins2::Type>和bool,eunum必须用UENUM标记。
|
||||
|
||||
引用类型的参数和返回值用作输出Pin,值类型的参数用作输入Pin。
|
||||
可以用“ReturnValue”这个名字来指定使用返回值参数。
|
||||
|
||||
如果有多个输出Enum参数,会在函数的调用之后排成Sequecene来一一分别根据输出Enum的值来触发输出Exec。
|
||||
|
||||
## 测试代码:
|
||||
|
||||
```cpp
|
||||
UENUM(BlueprintType)
|
||||
enum class EMyExecPins : uint8
|
||||
{
|
||||
First,
|
||||
Second,
|
||||
Third,
|
||||
};
|
||||
|
||||
UENUM(BlueprintType)
|
||||
namespace EMyExecPins2
|
||||
{
|
||||
enum Type : int
|
||||
{
|
||||
Found,
|
||||
NotFound,
|
||||
};
|
||||
}
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EMyExecAnimalPins : uint8
|
||||
{
|
||||
Cat,
|
||||
Dog,
|
||||
};
|
||||
|
||||
public:
|
||||
UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "Pins"))
|
||||
static int32 MyEnumAsExec_Output(FString Name, EMyExecPins& Pins) { return 0; }
|
||||
|
||||
UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "Pins"))
|
||||
static int32 MyEnumAsExec_Input(FString Name, TEnumAsByte<EMyExecPins2::Type> Pins) { return 0; }
|
||||
|
||||
UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "ReturnValue"))
|
||||
static EMyExecPins MyEnumAsExec_Return(FString Name) { return EMyExecPins::First; }
|
||||
public:
|
||||
UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "Pins"))
|
||||
static int32 MyBoolAsExec_Output(FString Name, bool& Pins) { return 0; }
|
||||
|
||||
UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "Pins"))
|
||||
static int32 MyBoolAsExec_Input(FString Name, bool Pins) { return 0; }
|
||||
|
||||
UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "ReturnValue"))
|
||||
static bool MyBoolAsExec_Return(FString Name) { return false; }
|
||||
public:
|
||||
UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "InPins,OutAnimal|OutPins|ReturnValue"))
|
||||
static bool MyEnumAsExec_MultipleOut(FString Name, EMyExecPins InPins, EMyExecAnimalPins& OutAnimal, TEnumAsByte<EMyExecPins2::Type>& OutPins, FString& Result);
|
||||
```
|
||||
|
||||
## 蓝图效果:
|
||||
|
||||
可以对照上述上述的函数原型和蓝图节点,可以发现ExpandEnumAsExecs执行3种参数类型。同时也验证了在同时拥有多个输出Enum参数的时候(代码里是OutAnimal|OutPins|ReturnValue),会按顺序执行3次输出,就像用Sequence节点连接在了一起一样。
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
真正的创建Pin是在void UK2Node_CallFunction::CreateExecPinsForFunctionCall(const UFunction* Function),然后这些新的ExecPin和配套的赋值输入参数值,以及根据输出参数执行不同输出ExecPin的逻辑在UK2Node_CallFunction::ExpandNode中。代码太多就不贴出来了。
|
||||
|
||||
是如何控制Exec流向的?在函数的实现里,只要把相应的引用输出参数赋值,就自然会流向不同的Pin。这部分逻辑是在UK2Node_CallFunction::ExpandNode中实现。大概逻辑是针对Input引脚,会在中间插入UK2Node_AssignmentStatement,执行不同输入Pin,会相应的设置输入enum参数的的值。而针对Output引脚,会在中间插入UK2Node_SwitchEnum,这样当我们在函数中设置引用输出enum参数的值后,就可以根据enum的值,流向相应的不同输出Pin节点。而对bool参数,也会创建相应的中间蓝图节点来获取和设置bool参数。
|
||||
|
||||
函数原始的参数Pin会被隐藏起来,从而只暴露生成后的Exec Pin。
|
After Width: | Height: | Size: 468 KiB |
@@ -0,0 +1,98 @@
|
||||
# ExposeOnSpawn
|
||||
|
||||
- **功能描述:** 使该属性在ContructObject或SpawnActor等创建对象的时候暴露出来。
|
||||
- **使用位置:** UPROPERTY
|
||||
- **引擎模块:** Blueprint
|
||||
- **元数据类型:** bool
|
||||
- **常用程度:** ★★★★★
|
||||
|
||||
使该属性在ContructObject或SpawnActor等创建对象的时候暴露出来。
|
||||
|
||||
- 具体来说,通过在源码搜索,这个标记在UK2Node_AddComponent,UK2Node_ConstructObjectFromClass,UK2Node_SpawnActor,UK2Node_LatentGameplayTaskCall的时候用到。
|
||||
- 在C++里设置的效果等同于在蓝图里勾上ExposeOnSpawn。
|
||||
- 该meta的设置也会同时设置到PropertyFlags里的CPF_ExposeOnSpawn
|
||||
|
||||
## 测试代码:
|
||||
|
||||
```cpp
|
||||
|
||||
UCLASS(BlueprintType)
|
||||
class INSIDER_API UMyProperty_ExposeOnSpawn :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
// (Category = MyProperty_ExposeOnSpawn, ModuleRelativePath = Property/Blueprint/MyProperty_ExposeOnSpawn.h)
|
||||
// PropertyFlags: CPF_Edit | CPF_BlueprintVisible | CPF_ZeroConstructor | CPF_HasGetValueTypeHash | CPF_NativeAccessSpecifierPublic
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
FString MyString = TEXT("First");
|
||||
|
||||
// (Category = MyProperty_ExposeOnSpawn, ExposeOnSpawn = , ModuleRelativePath = Property/Blueprint/MyProperty_ExposeOnSpawn.h)
|
||||
// PropertyFlags: CPF_Edit | CPF_BlueprintVisible | CPF_ZeroConstructor | CPF_ExposeOnSpawn | CPF_HasGetValueTypeHash | CPF_NativeAccessSpecifierPublic
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (ExposeOnSpawn))
|
||||
FString MyString_ExposeOnSpawn = TEXT("Second");
|
||||
};
|
||||
```
|
||||
|
||||
## 测试效果:
|
||||
|
||||
可见MyString_ExposeOnSpawn 暴露了出来,而MyString 没有。
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
在UHT的时候会分析如果包含ExposeOnSpawn就会同步设置CPF_ExposeOnSpawn。
|
||||
|
||||
而在IsPropertyExposedOnSpawn这个函数里具体判断是否要暴露,这个函数被上述的4个函数节点引用。源码里举UK2Node_ConstructObjectFromClass里的CreatePinsForClass作为例子,可见只有bIsExposedToSpawn 的时候才会为蓝图节点开始创建额外的Pin引脚。
|
||||
|
||||
```cpp
|
||||
if (propertySettings.MetaData.ContainsKey(UhtNames.ExposeOnSpawn))
|
||||
{
|
||||
propertySettings.PropertyFlags |= EPropertyFlags.ExposeOnSpawn;
|
||||
}
|
||||
|
||||
bool UEdGraphSchema_K2::IsPropertyExposedOnSpawn(const FProperty* Property)
|
||||
{
|
||||
Property = FBlueprintEditorUtils::GetMostUpToDateProperty(Property);
|
||||
if (Property)
|
||||
{
|
||||
const bool bMeta = Property->HasMetaData(FBlueprintMetadata::MD_ExposeOnSpawn);
|
||||
const bool bFlag = Property->HasAllPropertyFlags(CPF_ExposeOnSpawn);
|
||||
if (bMeta != bFlag)
|
||||
{
|
||||
const FCoreTexts& CoreTexts = FCoreTexts::Get();
|
||||
|
||||
UE_LOG(LogBlueprint, Warning
|
||||
, TEXT("ExposeOnSpawn ambiguity. Property '%s', MetaData '%s', Flag '%s'")
|
||||
, *Property->GetFullName()
|
||||
, bMeta ? *CoreTexts.True.ToString() : *CoreTexts.False.ToString()
|
||||
, bFlag ? *CoreTexts.True.ToString() : *CoreTexts.False.ToString());
|
||||
}
|
||||
return bMeta || bFlag;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void UK2Node_ConstructObjectFromClass::CreatePinsForClass(UClass* InClass, TArray<UEdGraphPin*>* OutClassPins)
|
||||
{
|
||||
for (TFieldIterator<FProperty> PropertyIt(InClass, EFieldIteratorFlags::IncludeSuper); PropertyIt; ++PropertyIt)
|
||||
{
|
||||
FProperty* Property = *PropertyIt;
|
||||
UClass* PropertyClass = CastChecked<UClass>(Property->GetOwner<UObject>());
|
||||
const bool bIsDelegate = Property->IsA(FMulticastDelegateProperty::StaticClass());
|
||||
const bool bIsExposedToSpawn = UEdGraphSchema_K2::IsPropertyExposedOnSpawn(Property);
|
||||
const bool bIsSettableExternally = !Property->HasAnyPropertyFlags(CPF_DisableEditOnInstance);
|
||||
|
||||
if( bIsExposedToSpawn &&
|
||||
!Property->HasAnyPropertyFlags(CPF_Parm) &&
|
||||
bIsSettableExternally &&
|
||||
Property->HasAllPropertyFlags(CPF_BlueprintVisible) &&
|
||||
!bIsDelegate &&
|
||||
(nullptr == FindPin(Property->GetFName()) ) &&
|
||||
FBlueprintEditorUtils::PropertyStillExists(Property))
|
||||
{
|
||||
if (UEdGraphPin* Pin = CreatePin(EGPD_Input, NAME_None, Property->GetFName()))
|
||||
{
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 59 KiB |
@@ -0,0 +1,110 @@
|
||||
# ExposedAsyncProxy
|
||||
|
||||
- **功能描述:** 在 Async Task 节点中公开此类的一个代理对象。
|
||||
- **使用位置:** UCLASS
|
||||
- **引擎模块:** Blueprint
|
||||
- **元数据类型:** string="abc"
|
||||
- **限制类型:** Async Blueprint node
|
||||
- **关联项:** [HideSpawnParms](../Param/HideSpawnParms/HideSpawnParms.md), [HasDedicatedAsyncNode](../HasDedicatedAsyncNode/HasDedicatedAsyncNode.md), [HideThen](../HideThen/HideThen.md)
|
||||
- **常用程度:** ★★★
|
||||
|
||||
在UK2Node_BaseAsyncTask中使用,用来为蓝图异步节点暴露一个异步对象引脚,以支持对这个异步行为的进一步操作。
|
||||
|
||||
在源码里的用处一是UBlueprintAsyncActionBase的子类,二是UGameplayTask子类,皆是分别会有另外的UK2Node_BaseAsyncTask以及UK2Node_LatentGameplayTaskCall来解析类的声明定义并包装生成相应的异步蓝图节点。
|
||||
|
||||
基类都是继承自UBlueprintAsyncActionBase。利用ExposedAsyncProxy 指定异步任务对象的名字。在异步蓝图节点上继续返回异步对象,可以在之后支持取消该异步操作。
|
||||
|
||||
## 测试代码:
|
||||
|
||||
UCancellableAsyncAction是引擎提供的继承自UBlueprintAsyncActionBase的一个便利的子类。UMyFunction_Async 定义了一个蓝图异步节点DelayLoop。
|
||||
|
||||
```cpp
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FDelayOutputPin);
|
||||
UCLASS(Blueprintable, BlueprintType,meta = (ExposedAsyncProxy = MyAsyncObject))
|
||||
class INSIDER_API UMyFunction_Async :public UBlueprintAsyncActionBase
|
||||
{
|
||||
public:
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FDelayOutputPin Loop;
|
||||
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FDelayOutputPin Complete;
|
||||
|
||||
UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject"), Category = "Flow Control")
|
||||
static UMyFunction_Async* DelayLoop(const UObject* WorldContextObject, const float DelayInSeconds, const int Iterations);
|
||||
|
||||
virtual void Activate() override;
|
||||
|
||||
UFUNCTION()
|
||||
static void Test();
|
||||
private:
|
||||
const UObject* WorldContextObject = nullptr;
|
||||
float MyDelay = 0.f;
|
||||
int MyIterations = 0;
|
||||
bool Active = false;
|
||||
|
||||
UFUNCTION()
|
||||
void ExecuteLoop();
|
||||
|
||||
UFUNCTION()
|
||||
void ExecuteComplete();
|
||||
};
|
||||
```
|
||||
|
||||
## 默认的蓝图节点是:
|
||||
|
||||
如果UMyFunction_Async 直接继承自UBlueprintAsyncActionBase,并且没有设置ExposedAsyncProxy,则生成的蓝图异步节点为为下图。
|
||||
|
||||

|
||||
|
||||
而如果继承自UCancellableAsyncAction (提供了Cancel方法),并且设置ExposedAsyncProxy 为自己想要的AsyncObject引脚名称。
|
||||
|
||||
```cpp
|
||||
UCLASS(Abstract, BlueprintType, meta = (ExposedAsyncProxy = AsyncAction), MinimalAPI)
|
||||
class UCancellableAsyncAction : public UBlueprintAsyncActionBase
|
||||
{
|
||||
UFUNCTION(BlueprintCallable, Category = "Async Action")
|
||||
ENGINE_API virtual void Cancel();
|
||||
}
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FDelayOutputPin);
|
||||
UCLASS(Blueprintable, BlueprintType,meta = (ExposedAsyncProxy = MyAsyncObject))
|
||||
class INSIDER_API UMyFunction_Async :public UCancellableAsyncAction
|
||||
{}
|
||||
```
|
||||
|
||||
## 修改后的效果如下图:
|
||||
|
||||

|
||||
|
||||
## 该Meta在源码中发生的位置:
|
||||
|
||||
```cpp
|
||||
void UK2Node_BaseAsyncTask::AllocateDefaultPins()
|
||||
{
|
||||
bool bExposeProxy = false;
|
||||
bool bHideThen = false;
|
||||
FText ExposeProxyDisplayName;
|
||||
for (const UStruct* TestStruct = ProxyClass; TestStruct; TestStruct = TestStruct->GetSuperStruct())
|
||||
{
|
||||
bExposeProxy |= TestStruct->HasMetaData(TEXT("ExposedAsyncProxy"));
|
||||
bHideThen |= TestStruct->HasMetaData(TEXT("HideThen"));
|
||||
if (ExposeProxyDisplayName.IsEmpty())
|
||||
{
|
||||
ExposeProxyDisplayName = TestStruct->GetMetaDataText(TEXT("ExposedAsyncProxy"));
|
||||
}
|
||||
}
|
||||
|
||||
if (bExposeProxy)
|
||||
{
|
||||
UEdGraphPin* ProxyPin = CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Object, ProxyClass, FBaseAsyncTaskHelper::GetAsyncTaskProxyName());
|
||||
if (!ExposeProxyDisplayName.IsEmpty())
|
||||
{
|
||||
ProxyPin->PinFriendlyName = ExposeProxyDisplayName;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 54 KiB |
After Width: | Height: | Size: 23 KiB |
@@ -0,0 +1,103 @@
|
||||
# ForceAsFunction
|
||||
|
||||
- **功能描述:** 把C++里用BlueprintImplementableEvent或NativeEvent定义的事件强制改为函数在子类中覆写。
|
||||
- **使用位置:** UFUNCTION
|
||||
- **引擎模块:** Blueprint
|
||||
- **元数据类型:** bool
|
||||
- **常用程度:** ★★★
|
||||
|
||||
把C++里用BlueprintImplementableEvent或NativeEvent定义的事件强制改为函数在子类中覆写。
|
||||
|
||||
什么情况下需要把Event改成函数?
|
||||
|
||||
- 变成函数后,在实现的时候就可以定义内部的局部变量。当然也就失去了调用Delay等延时函数的能力。
|
||||
- 事件不能有输出的参数,但是如果想要一个有输出的函数在蓝图类里覆写(得BlueprintImplementableEvent或NativeEvent),则默认的以事件方式重载是不行的。因此这个时候把这个事件强迫改为函数的形式,就可以正常的覆写。
|
||||
- 带有输出或返回参数的Event会默认被改为function,即使没有加上ForceAsFunction。
|
||||
|
||||
## 测试代码:
|
||||
|
||||
```cpp
|
||||
|
||||
UCLASS(Blueprintable, BlueprintType)
|
||||
class INSIDER_API AMyFunction_ForceAsFunction :public AActor
|
||||
{
|
||||
public:
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
//FUNC_Native | FUNC_Event | FUNC_Public | FUNC_BlueprintCallable | FUNC_BlueprintEvent
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent)
|
||||
void MyNativeEvent_Default(const FString& name);
|
||||
|
||||
//FUNC_Event | FUNC_Public | FUNC_BlueprintCallable | FUNC_BlueprintEvent
|
||||
UFUNCTION(BlueprintCallable, BlueprintImplementableEvent)
|
||||
void MyImplementableEvent_Default(const FString& name);
|
||||
|
||||
public:
|
||||
//(ForceAsFunction = , ModuleRelativePath = Function/MyFunction_ForceAsFunction.h)
|
||||
//FUNC_Native | FUNC_Event | FUNC_Public | FUNC_BlueprintCallable | FUNC_BlueprintEvent
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, meta = (ForceAsFunction))
|
||||
void MyNativeEvent_ForceAsFunction(const FString& name);
|
||||
|
||||
////(ForceAsFunction = , ModuleRelativePath = Function/MyFunction_ForceAsFunction.h)
|
||||
//FUNC_Event | FUNC_Public | FUNC_BlueprintCallable | FUNC_BlueprintEvent
|
||||
UFUNCTION(BlueprintCallable, BlueprintImplementableEvent, meta = (ForceAsFunction))
|
||||
void MyImplementableEvent_ForceAsFunction(const FString& name);
|
||||
|
||||
public:
|
||||
//FUNC_Native | FUNC_Event | FUNC_Public | FUNC_HasOutParms | FUNC_BlueprintCallable | FUNC_BlueprintEvent
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent)
|
||||
bool MyNativeEvent_Output(const FString& name, int32& OutValue);
|
||||
|
||||
//FUNC_Event | FUNC_Public | FUNC_HasOutParms | FUNC_BlueprintCallable | FUNC_BlueprintEvent
|
||||
UFUNCTION(BlueprintCallable, BlueprintImplementableEvent)
|
||||
bool MyImplementableEvent_Output(const FString& name, int32& OutValue);
|
||||
|
||||
//(ForceAsFunction = , ModuleRelativePath = Function/MyFunction_ForceAsFunction.h)
|
||||
//FUNC_Native | FUNC_Event | FUNC_Public | FUNC_HasOutParms | FUNC_BlueprintCallable | FUNC_BlueprintEvent
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, meta = (ForceAsFunction))
|
||||
bool MyNativeEvent_Output_ForceAsFunction(const FString& name, int32& OutValue);
|
||||
|
||||
//(ForceAsFunction = , ModuleRelativePath = Function/MyFunction_ForceAsFunction.h)
|
||||
//FUNC_Event | FUNC_Public | FUNC_HasOutParms | FUNC_BlueprintCallable | FUNC_BlueprintEvent
|
||||
UFUNCTION(BlueprintCallable, BlueprintImplementableEvent, meta = (ForceAsFunction))
|
||||
bool MyImplementableEvent_Output_ForceAsFunction(const FString& name, int32& OutValue);
|
||||
};
|
||||
```
|
||||
|
||||
## 蓝图中效果:
|
||||
|
||||
在函数上覆写的时候,会发现只有MyNativeEvent_Default和MyImplementableEvent_Default被默认覆写为事件,其他都以函数的方式被覆写。
|
||||
|
||||
图里展示了MyImplementableEvent_ForceAsFunction被改为函数后,可以在内部定义局部变量。
|
||||
|
||||
也展示了MyNativeEvent_Output这种拥有输出参数的事件被覆写成函数后的函数体。
|
||||
|
||||
但无论是覆写为事件还是函数,被调用的时候用法并无区别。
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
判断一个函数是否是事件的逻辑为以下函数:
|
||||
|
||||
主要看第二if和最后判断,BlueprintImplementableEvent或NativeEvent的函数上都会加上FUNC_BlueprintEvent标签,因此如果带有ForceAsFunction元数据或者有输出参数(返回值也算),就只能显示为函数。
|
||||
|
||||
```cpp
|
||||
bool UEdGraphSchema_K2::FunctionCanBePlacedAsEvent(const UFunction* InFunction)
|
||||
{
|
||||
// First check we are override-able, non-static, non-const and not marked thread safe
|
||||
if (!InFunction || !CanKismetOverrideFunction(InFunction) || InFunction->HasAnyFunctionFlags(FUNC_Static|FUNC_Const) || FBlueprintEditorUtils::HasFunctionBlueprintThreadSafeMetaData(InFunction))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if meta data has been set to force this to appear as blueprint function even if it doesn't return a value.
|
||||
if (InFunction->HasAllFunctionFlags(FUNC_BlueprintEvent) && InFunction->HasMetaData(FBlueprintMetadata::MD_ForceAsFunction))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Then look to see if we have any output, return, or reference params
|
||||
return !HasFunctionAnyOutputParameter(InFunction);
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 173 KiB |
@@ -0,0 +1,65 @@
|
||||
# GetByRef
|
||||
|
||||
- **功能描述:** 指定UHT为该属性生成返回引用的C++代码
|
||||
- **使用位置:** UPROPERTY
|
||||
- **引擎模块:** UHT
|
||||
- **元数据类型:** bool
|
||||
- **限制类型:** 只用在SparseClassDataTypes 指定的结构里的属性。
|
||||
- **关联项:** [SparseClassDataTypes](SparseClassDataTypes.md)
|
||||
|
||||
指定UHT为该属性生成返回引用的C++代码。
|
||||
|
||||
只用在SparseClassDataTypes 指定的结构里的属性。
|
||||
|
||||
## 代码例子:
|
||||
|
||||
```cpp
|
||||
USTRUCT(BlueprintType)
|
||||
struct FMySparseClassData
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
|
||||
FString MyString_EditDefault = TEXT("MyName");
|
||||
//FString GetMyString_EditDefault() const { return GetMySparseClassData(EGetSparseClassDataMethod::ArchetypeIfNull)->MyString_EditDefault; } \
|
||||
|
||||
// "GetByRef" means that Blueprint graphs access a const ref instead of a copy.
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, meta = (GetByRef))
|
||||
FString MyString_EditDefault_ReadOnly = TEXT("MyName");
|
||||
//const FString& GetMyString_EditDefault_ReadOnly() const { return GetMySparseClassData(EGetSparseClassDataMethod::ArchetypeIfNull)->MyString_EditDefault_ReadOnly; }
|
||||
};
|
||||
|
||||
UCLASS(Blueprintable, BlueprintType, SparseClassDataTypes = MySparseClassData)
|
||||
class INSIDER_API AMyActor_SparseClassDataTypes :public AActor
|
||||
{
|
||||
GENERATED_BODY()
|
||||
}
|
||||
```
|
||||
|
||||
## 生成的代码:
|
||||
|
||||
可见,后者生成返回值是const FString&而不是FString。
|
||||
|
||||
```cpp
|
||||
#define FID_Hello_Source_Insider_Class_Trait_MyClass_SparseClassDataTypes_h_36_SPARSE_DATA_PROPERTY_ACCESSORS \
|
||||
FString GetMyString_EditDefault() const { return GetMySparseClassData(EGetSparseClassDataMethod::ArchetypeIfNull)->MyString_EditDefault; } \
|
||||
const FString& GetMyString_EditDefault_ReadOnly() const { return GetMySparseClassData(EGetSparseClassDataMethod::ArchetypeIfNull)->MyString_EditDefault_ReadOnly; }
|
||||
```
|
||||
|
||||
## 原理:
|
||||
|
||||
UHT中为SparseDataType生成代码的时候会判断GetByRef来分别生成不同的格式代码。
|
||||
|
||||
```cpp
|
||||
private StringBuilder AppendSparseDeclarations(StringBuilder builder, UhtClass classObj, IEnumerable<UhtScriptStruct> sparseScriptStructs, UhtUsedDefineScopes<UhtProperty> sparseProperties)
|
||||
{
|
||||
if (property.MetaData.ContainsKey(UhtNames.GetByRef))
|
||||
{
|
||||
builder.Append("const ").AppendSparse(property).Append("& Get").Append(cleanPropertyName).Append("() const");
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.AppendSparse(property).Append(" Get").Append(cleanPropertyName).Append("() const");
|
||||
}
|
||||
}
|
||||
```
|
@@ -0,0 +1,172 @@
|
||||
# HasDedicatedAsyncNode
|
||||
|
||||
- **使用位置:** UCLASS
|
||||
- **元数据类型:** bool
|
||||
- **关联项:** [ExposedAsyncProxy](../ExposedAsyncProxy/ExposedAsyncProxy.md)
|
||||
|
||||
隐藏UBlueprintAsyncActionBase子类里工厂方法自动生成的蓝图异步节点,以便自己可以手动自定义创建一个相应的UK2Node_XXX。
|
||||
|
||||
```cpp
|
||||
/**
|
||||
* BlueprintCallable factory functions for classes which inherit from UBlueprintAsyncActionBase will have a special blueprint node created for it: UK2Node_AsyncAction
|
||||
* You can stop this node spawning and create a more specific one by adding the UCLASS metadata "HasDedicatedAsyncNode"
|
||||
*/
|
||||
|
||||
UCLASS(MinimalAPI)
|
||||
class UBlueprintAsyncActionBase : public UObject
|
||||
{}
|
||||
```
|
||||
|
||||
## 测试代码:
|
||||
|
||||
```cpp
|
||||
UCLASS(Blueprintable, BlueprintType,meta = (ExposedAsyncProxy = MyAsyncObject,HasDedicatedAsyncNode))
|
||||
class INSIDER_API UMyFunction_Async :public UCancellableAsyncAction
|
||||
{}
|
||||
|
||||
//可以自定义一个K2Node
|
||||
UCLASS()
|
||||
class INSIDER_API UK2Node_MyFunctionAsyncAction : public UK2Node_AsyncAction
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
// UK2Node interface
|
||||
virtual void GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const override;
|
||||
virtual void AllocateDefaultPins() override;
|
||||
// End of UK2Node interface
|
||||
|
||||
protected:
|
||||
virtual bool HandleDelegates(
|
||||
const TArray<FBaseAsyncTaskHelper::FOutputPinAndLocalVariable>& VariableOutputs, UEdGraphPin* ProxyObjectPin,
|
||||
UEdGraphPin*& InOutLastThenPin, UEdGraph* SourceGraph, FKismetCompilerContext& CompilerContext) override;
|
||||
};
|
||||
|
||||
void UK2Node_MyFunctionAsyncAction::GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const
|
||||
{
|
||||
struct GetMenuActions_Utils
|
||||
{
|
||||
static void SetNodeFunc(UEdGraphNode* NewNode, bool /*bIsTemplateNode*/, TWeakObjectPtr<UFunction> FunctionPtr)
|
||||
{
|
||||
UK2Node_MyFunctionAsyncAction* AsyncTaskNode = CastChecked<UK2Node_MyFunctionAsyncAction>(NewNode);
|
||||
if (FunctionPtr.IsValid())
|
||||
{
|
||||
UFunction* Func = FunctionPtr.Get();
|
||||
FObjectProperty* ReturnProp = CastFieldChecked<FObjectProperty>(Func->GetReturnProperty());
|
||||
|
||||
AsyncTaskNode->ProxyFactoryFunctionName = Func->GetFName();
|
||||
AsyncTaskNode->ProxyFactoryClass = Func->GetOuterUClass();
|
||||
AsyncTaskNode->ProxyClass = ReturnProp->PropertyClass;
|
||||
AsyncTaskNode->NodeComment = TEXT("This is MyCustomK2Node");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
UClass* NodeClass = GetClass();
|
||||
ActionRegistrar.RegisterClassFactoryActions<UMyFunction_Async>(FBlueprintActionDatabaseRegistrar::FMakeFuncSpawnerDelegate::CreateLambda([NodeClass](const UFunction* FactoryFunc)->UBlueprintNodeSpawner*
|
||||
{
|
||||
UBlueprintNodeSpawner* NodeSpawner = UBlueprintFunctionNodeSpawner::Create(FactoryFunc);
|
||||
check(NodeSpawner != nullptr);
|
||||
NodeSpawner->NodeClass = NodeClass;
|
||||
|
||||
TWeakObjectPtr<UFunction> FunctionPtr = MakeWeakObjectPtr(const_cast<UFunction*>(FactoryFunc));
|
||||
NodeSpawner->CustomizeNodeDelegate = UBlueprintNodeSpawner::FCustomizeNodeDelegate::CreateStatic(GetMenuActions_Utils::SetNodeFunc, FunctionPtr);
|
||||
|
||||
return NodeSpawner;
|
||||
}));
|
||||
}
|
||||
|
||||
void UK2Node_MyFunctionAsyncAction::AllocateDefaultPins()
|
||||
{
|
||||
Super::AllocateDefaultPins();
|
||||
}
|
||||
|
||||
bool UK2Node_MyFunctionAsyncAction::HandleDelegates(const TArray<FBaseAsyncTaskHelper::FOutputPinAndLocalVariable>& VariableOutputs, UEdGraphPin* ProxyObjectPin, UEdGraphPin*& InOutLastThenPin, UEdGraph* SourceGraph, FKismetCompilerContext& CompilerContext)
|
||||
{
|
||||
bool bIsErrorFree = true;
|
||||
|
||||
for (TFieldIterator<FMulticastDelegateProperty> PropertyIt(ProxyClass); PropertyIt && bIsErrorFree; ++PropertyIt)
|
||||
{
|
||||
UEdGraphPin* LastActivatedThenPin = nullptr;
|
||||
bIsErrorFree &= FBaseAsyncTaskHelper::HandleDelegateImplementation(*PropertyIt, VariableOutputs, ProxyObjectPin, InOutLastThenPin, LastActivatedThenPin, this, SourceGraph, CompilerContext);
|
||||
}
|
||||
|
||||
return bIsErrorFree;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## 蓝图效果:
|
||||
|
||||
左侧是引擎自带的UK2Node_AsyncAction生成节点,右边是自定义的UK2Node_MyFunctionAsyncAction生成的蓝图节点,虽然功能一致,但是右边额外加了个注释以便区分。有了这个基础,你也可以在其中继续重载方法进一步自定义。
|
||||
|
||||

|
||||
|
||||
## 当前在源码里有两处地方使用:
|
||||
|
||||
```cpp
|
||||
UCLASS(BlueprintType, meta = (ExposedAsyncProxy = "AsyncTask", HasDedicatedAsyncNode))
|
||||
class GAMEPLAYMESSAGES_API UAsyncAction_RegisterGameplayMessageReceiver : public UBlueprintAsyncActionBase
|
||||
{
|
||||
UFUNCTION(BlueprintCallable, Category = Messaging, meta=(WorldContext="WorldContextObject", BlueprintInternalUseOnly="true"))
|
||||
static UAsyncAction_RegisterGameplayMessageReceiver* RegisterGameplayMessageReceiver(UObject* WorldContextObject, FEventMessageTag Channel, UScriptStruct* PayloadType, EGameplayMessageMatchType MatchType = EGameplayMessageMatchType::ExactMatch, AActor* ActorContext = nullptr);
|
||||
|
||||
}
|
||||
//由UK2Node_GameplayMessageAsyncAction来负责创建
|
||||
void UK2Node_GameplayMessageAsyncAction::GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const
|
||||
{
|
||||
//...
|
||||
UClass* NodeClass = GetClass();
|
||||
ActionRegistrar.RegisterClassFactoryActions<UAsyncAction_RegisterGameplayMessageReceiver>(FBlueprintActionDatabaseRegistrar::FMakeFuncSpawnerDelegate::CreateLambda([NodeClass](const UFunction* FactoryFunc)->UBlueprintNodeSpawner*
|
||||
{
|
||||
UBlueprintNodeSpawner* NodeSpawner = UBlueprintFunctionNodeSpawner::Create(FactoryFunc);
|
||||
check(NodeSpawner != nullptr);
|
||||
NodeSpawner->NodeClass = NodeClass;
|
||||
|
||||
TWeakObjectPtr<UFunction> FunctionPtr = MakeWeakObjectPtr(const_cast<UFunction*>(FactoryFunc));
|
||||
NodeSpawner->CustomizeNodeDelegate = UBlueprintNodeSpawner::FCustomizeNodeDelegate::CreateStatic(GetMenuActions_Utils::SetNodeFunc, FunctionPtr);
|
||||
|
||||
return NodeSpawner;
|
||||
}) );
|
||||
}
|
||||
|
||||
UCLASS(BlueprintType, meta=(ExposedAsyncProxy = "AsyncTask", HasDedicatedAsyncNode))
|
||||
class UMovieSceneAsyncAction_SequencePrediction : public UBlueprintAsyncActionBase
|
||||
{
|
||||
UFUNCTION(BlueprintCallable, Category=Cinematics)
|
||||
static UMovieSceneAsyncAction_SequencePrediction* PredictWorldTransformAtTime(UMovieSceneSequencePlayer* Player, USceneComponent* TargetComponent, float TimeInSeconds);
|
||||
}
|
||||
```
|
||||
|
||||
## 生成的蓝图:
|
||||
|
||||
UAsyncAction_RegisterGameplayMessageReceiver由自定义的UK2Node_GameplayMessageAsyncAction来创建蓝图节点,从而提供了一个泛型的Payload输出引脚。而UMovieSceneAsyncAction_SequencePrediction 里的工厂方法PredictWorldTransformAtTime,由于隐藏了自动生成的版本,又没有加上BlueprintInternalUseOnly来抑制UHT生成的版本,因此最终呈现的是普通版本的静态函数蓝图节点。
|
||||
|
||||

|
||||
|
||||
## 源码里的作用机制:
|
||||
|
||||
可以看到,如果在类上有找到HasDedicatedAsyncNode,直接就返回nullptr,不再生成NodeSpawner,因此就阻止了蓝图节点的生成。
|
||||
|
||||
```cpp
|
||||
void UK2Node_AsyncAction::GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const
|
||||
{
|
||||
ActionRegistrar.RegisterClassFactoryActions<UBlueprintAsyncActionBase>(FBlueprintActionDatabaseRegistrar::FMakeFuncSpawnerDelegate::CreateLambda([NodeClass](const UFunction* FactoryFunc)->UBlueprintNodeSpawner*
|
||||
{
|
||||
UClass* FactoryClass = FactoryFunc ? FactoryFunc->GetOwnerClass() : nullptr;
|
||||
if (FactoryClass && FactoryClass->HasMetaData(TEXT("HasDedicatedAsyncNode")))
|
||||
{
|
||||
// Wants to use a more specific blueprint node to handle the async action
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UBlueprintNodeSpawner* NodeSpawner = UBlueprintFunctionNodeSpawner::Create(FactoryFunc);
|
||||
check(NodeSpawner != nullptr);
|
||||
NodeSpawner->NodeClass = NodeClass;
|
||||
|
||||
TWeakObjectPtr<UFunction> FunctionPtr = MakeWeakObjectPtr(const_cast<UFunction*>(FactoryFunc));
|
||||
NodeSpawner->CustomizeNodeDelegate = UBlueprintNodeSpawner::FCustomizeNodeDelegate::CreateStatic(GetMenuActions_Utils::SetNodeFunc, FunctionPtr);
|
||||
|
||||
return NodeSpawner;
|
||||
}) );
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 74 KiB |
After Width: | Height: | Size: 41 KiB |
@@ -0,0 +1,79 @@
|
||||
# HiddenNode
|
||||
|
||||
- **功能描述:** 把指定的UBTNode隐藏不在右键菜单中显示。
|
||||
- **使用位置:** UCLASS
|
||||
- **引擎模块:** Blueprint
|
||||
- **元数据类型:** bool
|
||||
- **限制类型:** UBTNode
|
||||
- **常用程度:** ★
|
||||
|
||||
把指定的UBTNode隐藏不在右键菜单中显示。
|
||||
|
||||
## 测试代码:
|
||||
|
||||
```cpp
|
||||
|
||||
UCLASS(MinimalAPI,meta = ())
|
||||
class UMyBT_NotHiddenNode : public UBTDecorator
|
||||
{
|
||||
GENERATED_UCLASS_BODY()
|
||||
|
||||
UPROPERTY(Category = Node, EditAnywhere)
|
||||
float MyFloat;
|
||||
};
|
||||
|
||||
UCLASS(MinimalAPI,meta = (HiddenNode))
|
||||
class UMyBT_HiddenNode : public UBTDecorator
|
||||
{
|
||||
GENERATED_UCLASS_BODY()
|
||||
|
||||
UPROPERTY(Category = Node, EditAnywhere)
|
||||
float MyFloat;
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## 测试结果:
|
||||
|
||||
可见只有UMyBT_NotHiddenNode 显示了出来,而UMyBT_HiddenNode 被隐藏了。
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
原理比较简单,就是坚持元数据标记,然后设置bIsHidden 。
|
||||
|
||||
```cpp
|
||||
bool FGraphNodeClassHelper::IsHidingClass(UClass* Class)
|
||||
{
|
||||
static FName MetaHideInEditor = TEXT("HiddenNode");
|
||||
|
||||
return
|
||||
Class &&
|
||||
((Class->HasAnyClassFlags(CLASS_Native) && Class->HasMetaData(MetaHideInEditor))
|
||||
|| ForcedHiddenClasses.Contains(Class));
|
||||
}
|
||||
|
||||
//D:\github\UnrealEngine\Engine\Source\Editor\AIGraph\Private\AIGraphTypes.cpp
|
||||
void FGraphNodeClassHelper::BuildClassGraph()
|
||||
{
|
||||
for (TObjectIterator<UClass> It; It; ++It)
|
||||
{
|
||||
UClass* TestClass = *It;
|
||||
if (TestClass->HasAnyClassFlags(CLASS_Native) && TestClass->IsChildOf(RootNodeClass))
|
||||
{
|
||||
|
||||
NewData.bIsHidden = IsHidingClass(TestClass);
|
||||
|
||||
NewNode->Data = NewData;
|
||||
|
||||
if (TestClass == RootNodeClass)
|
||||
{
|
||||
RootNode = NewNode;
|
||||
}
|
||||
|
||||
NodeList.Add(NewNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 130 KiB |
@@ -0,0 +1,15 @@
|
||||
# HideFunctions
|
||||
|
||||
- **功能描述:** 在属性查看器中不显示指定类别中的所有函数。
|
||||
|
||||
- **使用位置:** UCLASS
|
||||
|
||||
- **引擎模块:** Blueprint
|
||||
|
||||
- **元数据类型:** strings="a,b,c"
|
||||
|
||||
- **关联项:**
|
||||
|
||||
UCLASS:[HideFunctions](../../Specifier/UCLASS/Blueprint/HideFunctions/HideFunctions.md), [ShowFunctions](../../Specifier/UCLASS/Blueprint/ShowFunctions.md)
|
||||
|
||||
- **常用程度:** ★★★
|
@@ -0,0 +1,51 @@
|
||||
# HideThen
|
||||
|
||||
- **功能描述:** 隐藏异步蓝图节点的Then引脚
|
||||
- **使用位置:** UCLASS
|
||||
- **元数据类型:** bool
|
||||
- **限制类型:** 蓝图异步节点
|
||||
- **关联项:** [ExposedAsyncProxy](../ExposedAsyncProxy/ExposedAsyncProxy.md)
|
||||
|
||||
在源码中HideThen只在UK2Node_BaseAsyncTask中判断,因此这个标签只作用于蓝图异步节点。
|
||||
|
||||
## 测试代码:
|
||||
|
||||
```cpp
|
||||
UCLASS(Blueprintable, BlueprintType,meta = (ExposedAsyncProxy = MyAsyncObject))
|
||||
class INSIDER_API UMyFunction_Async :public UCancellableAsyncAction
|
||||
{}
|
||||
|
||||
UCLASS(Blueprintable, BlueprintType,meta = (ExposedAsyncProxy = MyAsyncObject,HideThen))
|
||||
class INSIDER_API UMyFunction_Async :public UCancellableAsyncAction
|
||||
{}
|
||||
```
|
||||
|
||||
## 使用HideThen前后对比:
|
||||
|
||||

|
||||
|
||||
## 源码位置:
|
||||
|
||||
```cpp
|
||||
void UK2Node_BaseAsyncTask::AllocateDefaultPins()
|
||||
{
|
||||
bool bExposeProxy = false;
|
||||
bool bHideThen = false;
|
||||
FText ExposeProxyDisplayName;
|
||||
for (const UStruct* TestStruct = ProxyClass; TestStruct; TestStruct = TestStruct->GetSuperStruct())
|
||||
{
|
||||
bExposeProxy |= TestStruct->HasMetaData(TEXT("ExposedAsyncProxy"));
|
||||
bHideThen |= TestStruct->HasMetaData(TEXT("HideThen"));
|
||||
if (ExposeProxyDisplayName.IsEmpty())
|
||||
{
|
||||
ExposeProxyDisplayName = TestStruct->GetMetaDataText(TEXT("ExposedAsyncProxy"));
|
||||
}
|
||||
}
|
||||
|
||||
if (!bHideThen)
|
||||
{
|
||||
CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Then);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 57 KiB |
@@ -0,0 +1,157 @@
|
||||
# IgnoreTypePromotion
|
||||
|
||||
- **功能描述:** 标记该函数不收录进类型提升函数库
|
||||
- **使用位置:** UFUNCTION
|
||||
- **引擎模块:** Blueprint
|
||||
- **元数据类型:** bool
|
||||
- **限制类型:** UBlueprintFunctionLibrary内的BlueprintPure,以OP_XXX形式的函数
|
||||
- **常用程度:** ★★
|
||||
|
||||
标记该函数不收录进类型提升函数库。
|
||||
|
||||
## 这里有三个关键点:
|
||||
|
||||
一是该函数是什么类型?或者说一个类型提升函数是什么类型?根据IsPromotableFunction源码定义,该函数必须定义在UBlueprintFunctionLibrary中,必须是BlueprintPure,且是以操作符”OP_XXX”这种名字格式的函数,其中OP的名字在OperatorNames这个命名空间中可见。示例可见KismetMathLibrary中有大量的这种类型函数。
|
||||
|
||||
二是什么是类型提升函数库?源码中有FTypePromotion的类,里面的OperatorTable记录了从OP名字到函数列表的一个Map映射,比如支持Add(+)的有多个Add_Vector,Add_Float等。当我们在蓝图中右键输入+或Add节点的时候,出现的首先是一个泛型的+节点。然后再连接到具体的变量类型,蓝图系统根据Pin类型会在FTypePromotion::OperatorTable里找到最匹配的Func来最终调用,或者自动的在内部做类型提升。比如下图的+最终调用的就是UKismetMathLibrary::Add_VectorFloat。这种泛型的运算符调用,使得各种基本类型之间的基本运算在蓝图节点创建上更加的便利和统一,也方便直接Add Pin和在Pin上直接Convert到可兼容的其他Pin类型。
|
||||
|
||||

|
||||
|
||||
三是为什么有些函数不想被收录进FTypePromotion里?在源码中搜索,在KismetMathLibrary中发现只有FDateTime加上了IgnoreTypePromotion标记。虽然FDateTime也定义了一系列的各种运算符函数,比如Add,Subtract和其他各种比较运算符,但是FDateTime在意义上和其他的基本类型可互相运算不同,FDateTime+float或FDateTime+vector并无什么意义。FDateTime只允许+FDateTime或+FTimeSpan。因此类似FDateTime这种并不想参与到其他类型的类型提升转换关系中,只想安静的自成一派在自己小范围内运算,就可以加上IgnoreTypePromotion,不参与进FTypePromotion这个体系。
|
||||
|
||||
## 测试代码:
|
||||
|
||||
假设我们有个FGameProp结构,定义了游戏里的战斗属性(HP,Attack,Defense)这些,然后游戏中通常要穿装备和加Buff等等操作会计算个最终的属性。这种FGameProp结构我们就可以为之定义一系列的基本运算函数。并加上IgnoreTypePromotion,因为肯定不想参与进TypePromotion,与别的基本类型直接运算(float,vector等这些)。
|
||||
|
||||
为了对比,代码里也定义一模一样FGameProp2和一样的运算函数,唯一区别是不加IgnoreTypePromotion,然后观察最终的蓝图节点上的差异。
|
||||
|
||||
```cpp
|
||||
USTRUCT(BlueprintType)
|
||||
struct FGameProp
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
double HP;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
double Attack;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
double Defense;
|
||||
};
|
||||
|
||||
UCLASS(Blueprintable, BlueprintType)
|
||||
class INSIDER_API UMyFunction_IgnoreTypePromotion :public UBlueprintFunctionLibrary
|
||||
{
|
||||
public:
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
/** Makes a GameProp struct */
|
||||
UFUNCTION(BlueprintPure, Category = "Math|GameProp", meta = (IgnoreTypePromotion, NativeMakeFunc))
|
||||
static FGameProp MakeGameProp(double HP, double Attack, double Defense) { return FGameProp(); }
|
||||
|
||||
/** Breaks a GameProp into its components */
|
||||
UFUNCTION(BlueprintPure, Category = "Math|GameProp", meta = (IgnoreTypePromotion, NativeBreakFunc))
|
||||
static void BreakGameProp(FGameProp InGameProp, double& HP, double& Attack, double& Defense) {}
|
||||
|
||||
/** Addition (A + B) */
|
||||
UFUNCTION(BlueprintPure, meta = (IgnoreTypePromotion, DisplayName = "GameProp + GameProp", CompactNodeTitle = "+", Keywords = "+ add plus"), Category = "Math|GameProp")
|
||||
static FGameProp Add_GameProp(FGameProp A, FGameProp B);
|
||||
|
||||
/** Subtraction (A - B) */
|
||||
UFUNCTION(BlueprintPure, meta = (IgnoreTypePromotion, DisplayName = "GameProp - GameProp", CompactNodeTitle = "-", Keywords = "- subtract minus"), Category = "Math|GameProp")
|
||||
static FGameProp Subtract_GameProp(FGameProp A, FGameProp B) { return FGameProp(); }
|
||||
|
||||
/** Returns true if the values are equal (A == B) */
|
||||
UFUNCTION(BlueprintPure, meta = (IgnoreTypePromotion, DisplayName = "Equal (GameProp)", CompactNodeTitle = "==", Keywords = "== equal"), Category = "Math|GameProp")
|
||||
static bool EqualEqual_GameProp(FGameProp A, FGameProp B) { return true; }
|
||||
|
||||
/** Returns true if the values are not equal (A != B) */
|
||||
UFUNCTION(BlueprintPure, meta = (IgnoreTypePromotion, DisplayName = "Not Equal (GameProp)", CompactNodeTitle = "!=", Keywords = "!= not equal"), Category = "Math|GameProp")
|
||||
static bool NotEqual_GameProp(FGameProp A, FGameProp B) { return true; }
|
||||
|
||||
/** Returns true if A is greater than B (A > B) */
|
||||
UFUNCTION(BlueprintPure, meta = (IgnoreTypePromotion, DisplayName = "GameProp > GameProp", CompactNodeTitle = ">", Keywords = "> greater"), Category = "Math|GameProp")
|
||||
static bool Greater_GameProp(FGameProp A, FGameProp B) { return true; }
|
||||
|
||||
/** Returns true if A is greater than or equal to B (A >= B) */
|
||||
UFUNCTION(BlueprintPure, meta = (IgnoreTypePromotion, DisplayName = "GameProp >= GameProp", CompactNodeTitle = ">=", Keywords = ">= greater"), Category = "Math|GameProp")
|
||||
static bool GreaterEqual_GameProp(FGameProp A, FGameProp B) { return true; }
|
||||
|
||||
/** Returns true if A is less than B (A < B) */
|
||||
UFUNCTION(BlueprintPure, meta = (IgnoreTypePromotion, DisplayName = "GameProp < GameProp", CompactNodeTitle = "<", Keywords = "< less"), Category = "Math|GameProp")
|
||||
static bool Less_GameProp(FGameProp A, FGameProp B) { return true; }
|
||||
|
||||
/** Returns true if A is less than or equal to B (A <= B) */
|
||||
UFUNCTION(BlueprintPure, meta = (IgnoreTypePromotion, DisplayName = "GameProp <= GameProp", CompactNodeTitle = "<=", Keywords = "<= less"), Category = "Math|GameProp")
|
||||
static bool LessEqual_GameProp(FGameProp A, FGameProp B) { return true; }
|
||||
};
|
||||
|
||||
|
||||
```
|
||||
|
||||
## 蓝图效果:
|
||||
|
||||
加了IgnoreTypePromotion的FGameProp,Add的时候就是直接最原始的Add_GameProp节点。而不加IgnoreTypePromotion的FGameProp2,Add的时候产生的节点是泛型的+,可以继续AddPin,甚至在Pin上右键还会尝试寻找向其他类型的转换(虽然这里结果找不到,是因为我们没有定义FGameProp2和其他类型的运算函数)。
|
||||
|
||||

|
||||
|
||||
另外一点是,如果是在一个空的泛型Add节点上右键,会发现出现转换到FGameProp2的选项(但是FGameProp并没有)。这也是标明FGameProp2存在于TypePromotion这个体系里。但是实际上我们并不希望FGameProp2出现这里,还是那句话,这种玩法的战斗属性,有自己的运算规则,并不想掺和进基本类型的数学运算里。
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
在编辑器设置中,有个选项EnableTypePromotion打开后, 就会使得FTypePromotion开始收集引擎内定义的所有函数,并判断其是否是个类型提升函数。
|
||||
|
||||

|
||||
|
||||
一个函数名如果前面包含运算符前缀(OperatorNames里定义的这些),例如Add_XXX,则会被提取操作符。被注册加入到这个FTypePromotion::OperatorTable映射表里的函数,这样在蓝图里右键一些操作符的时候(比如+),就会在这个映射表里找到最匹配的函数。
|
||||
|
||||
```cpp
|
||||
namespace OperatorNames
|
||||
{
|
||||
static const FName NoOp = TEXT("NO_OP");
|
||||
|
||||
static const FName Add = TEXT("Add");
|
||||
static const FName Multiply = TEXT("Multiply");
|
||||
static const FName Subtract = TEXT("Subtract");
|
||||
static const FName Divide = TEXT("Divide");
|
||||
|
||||
static const FName Greater = TEXT("Greater");
|
||||
static const FName GreaterEq = TEXT("GreaterEqual");
|
||||
static const FName Less = TEXT("Less");
|
||||
static const FName LessEq = TEXT("LessEqual");
|
||||
static const FName NotEq = TEXT("NotEqual");
|
||||
static const FName Equal = TEXT("EqualEqual");
|
||||
}
|
||||
|
||||
bool const bIsPromotableFunction = TypePromoDebug::IsTypePromoEnabled() && FTypePromotion::IsFunctionPromotionReady(Function);
|
||||
if (bIsPromotableFunction)
|
||||
{
|
||||
NodeClass = UK2Node_PromotableOperator::StaticClass();
|
||||
}
|
||||
|
||||
bool FTypePromotion::IsPromotableFunction(const UFunction* Function)
|
||||
{
|
||||
TRACE_CPUPROFILER_EVENT_SCOPE(FTypePromotion::IsPromotableFunction);
|
||||
|
||||
// Ensure that we don't have an invalid OpName as well for extra safety when this function
|
||||
// is called outside of this class, not during the OpTable creation process
|
||||
FName OpName = GetOpNameFromFunction(Function);
|
||||
return Function &&
|
||||
Function->HasAnyFunctionFlags(FUNC_BlueprintPure) &&
|
||||
Function->GetReturnProperty() &&
|
||||
OpName != OperatorNames::NoOp &&
|
||||
!IsPinTypeDeniedForTypePromotion(Function) &&
|
||||
// Users can deny specific functions from being considered for type promotion
|
||||
!Function->HasMetaData(FBlueprintMetadata::MD_IgnoreTypePromotion);
|
||||
}
|
||||
```
|
||||
|
||||
FTypePromotion收集的OperatorTable里面内容:
|
||||
|
||||

|
||||
|
||||
一个函数如果IsPromotableFunction,在调用的时候就会用UK2Node_PromotableOperator来作为蓝图节点(默认是UK2Node_CallFunction),UK2Node_PromotableOperator是典型的用于Wildcard泛型的二元运算符。如下图的Add(+)。在这种Add 的引脚上右键可以弹出Pin的类型转换从Wildcard到特定的类型,因为该结构有定义Add_XXX的函数,并且没有IgnoreTypePromotion,因此就被包含进了TypePromotion的映射表里。
|
||||
|
||||
上面的这个Pin转换菜单就是在UK2Node_PromotableOperator::CreateConversionMenu里收集的。
|
After Width: | Height: | Size: 209 KiB |
After Width: | Height: | Size: 104 KiB |
After Width: | Height: | Size: 42 KiB |
After Width: | Height: | Size: 60 KiB |
After Width: | Height: | Size: 107 KiB |
@@ -0,0 +1,17 @@
|
||||
# IsBlueprintBase
|
||||
|
||||
- **功能描述:** 说明此类是否为创建蓝图的一个可接受基类,与 UCLASS 说明符、Blueprintable 或 'NotBlueprintable` 相似。
|
||||
|
||||
- **使用位置:** UCLASS, UINTERFACE
|
||||
|
||||
- **引擎模块:** Blueprint
|
||||
|
||||
- **元数据类型:** bool
|
||||
|
||||
- **关联项:**
|
||||
|
||||
UCLASS:[Blueprintable](../../Specifier/UCLASS/Blueprint/Blueprintable/Blueprintable.md), [NotBlueprintable](../../Specifier/UCLASS/Blueprint/NotBlueprintable.md)
|
||||
|
||||
UINTERFACE:[Blueprintable](../../Specifier/UINTERFACE/Blueprint/Blueprintable/Blueprintable.md), [NotBlueprintable](../../Specifier/UINTERFACE/Blueprint/NotBlueprintable/NotBlueprintable.md)
|
||||
|
||||
- **常用程度:** ★★★★★
|
@@ -0,0 +1,17 @@
|
||||
# IsConversionRoot
|
||||
|
||||
- **功能描述:** 允许Actor在自身以及子类之间做转换
|
||||
|
||||
- **使用位置:** UCLASS, UINTERFACE
|
||||
|
||||
- **引擎模块:** Blueprint
|
||||
|
||||
- **元数据类型:** bool
|
||||
|
||||
- **关联项:**
|
||||
|
||||
UCLASS:[ConversionRoot](../../Specifier/UCLASS/Scene/ConversionRoot/ConversionRoot.md)
|
||||
|
||||
UINTERFACE:[ConversionRoot](../../Specifier/UINTERFACE/UHT/ConversionRoot.md)
|
||||
|
||||
- **常用程度:** ★★★
|
@@ -0,0 +1,40 @@
|
||||
# Keywords
|
||||
|
||||
- **功能描述:** 指定一系列关键字用于在蓝图内右键找到该函数
|
||||
- **使用位置:** UFUNCTION
|
||||
- **引擎模块:** Blueprint
|
||||
- **元数据类型:** string="abc"
|
||||
- **常用程度:** ★★★★★
|
||||
|
||||
Keywords里的单词可以用空格隔开,也可以逗号隔开。这里面的文本是会被进行字符串匹配搜索。
|
||||
|
||||
## 测试代码:
|
||||
|
||||
```cpp
|
||||
UCLASS(Blueprintable, BlueprintType)
|
||||
class INSIDER_API UMyFunction_Keywords :public UBlueprintFunctionLibrary
|
||||
{
|
||||
public:
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UFUNCTION(BlueprintCallable,meta=(Keywords="This is a SuperFunc,OtherFunc"))
|
||||
static void MyFunc_HasKeyworlds();
|
||||
};
|
||||
```
|
||||
|
||||
## 蓝图效果:
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
该Keywords的内容,最终会被FEdGraphSchemaAction所应用,用于蓝图内右键菜单的文本搜索。
|
||||
|
||||
另外每个K2Node都可以返回一个Keywords。效果应该跟函数上的Keywords一样。
|
||||
|
||||
```cpp
|
||||
FText UEdGraphNode::GetKeywords() const
|
||||
{
|
||||
return GetClass()->GetMetaDataText(TEXT("Keywords"), TEXT("UObjectKeywords"), GetClass()->GetFullGroupName(false));
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 39 KiB |
@@ -0,0 +1,74 @@
|
||||
# KismetHideOverrides
|
||||
|
||||
- **功能描述:** 不允许被覆盖的蓝图事件的列表。
|
||||
- **使用位置:** UCLASS
|
||||
- **引擎模块:** Blueprint
|
||||
- **元数据类型:** strings="a,b,c"
|
||||
|
||||
在源码中发现ALevelScriptActor上面定义了很多,用来阻止被覆盖。
|
||||
|
||||
## 样例:
|
||||
|
||||
```cpp
|
||||
UCLASS(notplaceable, meta=(ChildCanTick, KismetHideOverrides = "ReceiveAnyDamage,ReceivePointDamage,ReceiveRadialDamage,ReceiveActorBeginOverlap,ReceiveActorEndOverlap,ReceiveHit,ReceiveDestroyed,ReceiveActorBeginCursorOver,ReceiveActorEndCursorOver,ReceiveActorOnClicked,ReceiveActorOnReleased,ReceiveActorOnInputTouchBegin,ReceiveActorOnInputTouchEnd,ReceiveActorOnInputTouchEnter,ReceiveActorOnInputTouchLeave"), HideCategories=(Collision,Rendering,Transformation), MinimalAPI)
|
||||
class ALevelScriptActor : public AActor
|
||||
{}
|
||||
```
|
||||
|
||||
但是实际在LevelScriptActor的子类中依然可以覆盖该事件。有一些被隐藏的Event是其实通过HideCategories来做到的。因此该Meta其实并没有实现,如果要达到该效果,还是要通过HideFunctions或HideCategories来达成。
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
可以看到这里面的判断,并没有用到该Meta
|
||||
|
||||
```cpp
|
||||
void SMyBlueprint::CollectAllActions(FGraphActionListBuilderBase& OutAllActions)
|
||||
{
|
||||
// Cache potentially overridable functions
|
||||
UClass* ParentClass = BlueprintObj->SkeletonGeneratedClass ? BlueprintObj->SkeletonGeneratedClass->GetSuperClass() : *BlueprintObj->ParentClass;
|
||||
for ( TFieldIterator<UFunction> FunctionIt(ParentClass, EFieldIteratorFlags::IncludeSuper); FunctionIt; ++FunctionIt )
|
||||
{
|
||||
const UFunction* Function = *FunctionIt;
|
||||
const FName FunctionName = Function->GetFName();
|
||||
|
||||
UClass *OuterClass = CastChecked<UClass>(Function->GetOuter());
|
||||
// ignore skeleton classes and convert them into their "authoritative" types so they
|
||||
// can be found in the graph
|
||||
if(UBlueprintGeneratedClass *GeneratedOuterClass = Cast<UBlueprintGeneratedClass>(OuterClass))
|
||||
{
|
||||
OuterClass = GeneratedOuterClass->GetAuthoritativeClass();
|
||||
}
|
||||
|
||||
if ( UEdGraphSchema_K2::CanKismetOverrideFunction(Function)
|
||||
&& !OverridableFunctionNames.Contains(FunctionName)
|
||||
&& !ImplementedFunctionCache.Contains(FunctionName)
|
||||
&& !FObjectEditorUtils::IsFunctionHiddenFromClass(Function, ParentClass)
|
||||
&& !FBlueprintEditorUtils::FindOverrideForFunction(BlueprintObj, OuterClass, Function->GetFName())
|
||||
&& Blueprint->AllowFunctionOverride(Function)
|
||||
)
|
||||
{
|
||||
FText FunctionTooltip = FText::FromString(UK2Node_CallFunction::GetDefaultTooltipForFunction(Function));
|
||||
FText FunctionDesc = K2Schema->GetFriendlySignatureName(Function);
|
||||
if ( FunctionDesc.IsEmpty() )
|
||||
{
|
||||
FunctionDesc = FText::FromString(Function->GetName());
|
||||
}
|
||||
|
||||
if (Function->HasMetaData(FBlueprintMetadata::MD_DeprecatedFunction))
|
||||
{
|
||||
FunctionDesc = FBlueprintEditorUtils::GetDeprecatedMemberMenuItemName(FunctionDesc);
|
||||
}
|
||||
|
||||
FText FunctionCategory = FObjectEditorUtils::GetCategoryText(Function);
|
||||
|
||||
TSharedPtr<FEdGraphSchemaAction_K2Graph> NewFuncAction = MakeShareable(new FEdGraphSchemaAction_K2Graph(EEdGraphSchemaAction_K2Graph::Function, FunctionCategory, FunctionDesc, FunctionTooltip, 1, NodeSectionID::FUNCTION_OVERRIDABLE));
|
||||
NewFuncAction->FuncName = FunctionName;
|
||||
|
||||
OverridableFunctionActions.Add(NewFuncAction);
|
||||
OverridableFunctionNames.Add(FunctionName);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 21 KiB |
@@ -0,0 +1,83 @@
|
||||
# Latent
|
||||
|
||||
- **功能描述:** 标明一个函数是一个延迟异步操作
|
||||
- **使用位置:** UFUNCTION
|
||||
- **引擎模块:** Blueprint
|
||||
- **元数据类型:** bool
|
||||
- **关联项:** [LatentInfo](LatentInfo.md), [NeedsLatentFixup](NeedsLatentFixup.md), [LatentCallbackTarget](LatentCallbackTarget.md)
|
||||
- **常用程度:** ★★★★★
|
||||
|
||||
标明一个函数是一个延迟异步操作,需要配合LatentInfo来使用。
|
||||
|
||||
会导致在逻辑执行上Then(也叫Complete)引脚需要手动触发(引擎内部触发),且函数右上角增加时钟的图标。
|
||||
|
||||
## 测试代码:
|
||||
|
||||
```cpp
|
||||
class FMySleepAction : public FPendingLatentAction
|
||||
{
|
||||
public:
|
||||
float TimeRemaining;
|
||||
FName ExecutionFunction;
|
||||
int32 OutputLink;
|
||||
FWeakObjectPtr CallbackTarget;
|
||||
|
||||
FMySleepAction(float Duration, const FLatentActionInfo& LatentInfo)
|
||||
: TimeRemaining(Duration)
|
||||
, ExecutionFunction(LatentInfo.ExecutionFunction)
|
||||
, OutputLink(LatentInfo.Linkage)
|
||||
, CallbackTarget(LatentInfo.CallbackTarget)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void UpdateOperation(FLatentResponse& Response) override
|
||||
{
|
||||
TimeRemaining -= Response.ElapsedTime();
|
||||
Response.FinishAndTriggerIf(TimeRemaining <= 0.0f, ExecutionFunction, OutputLink, CallbackTarget);
|
||||
}
|
||||
};
|
||||
|
||||
UCLASS(Blueprintable, BlueprintType)
|
||||
class INSIDER_API UMyFunction_Latent :public UBlueprintFunctionLibrary
|
||||
{
|
||||
public:
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UFUNCTION(BlueprintCallable, meta = (Latent, WorldContext = "WorldContextObject", LatentInfo = "LatentInfo", Duration = "5"))
|
||||
static void MySleep(const UObject* WorldContextObject, float Duration, FLatentActionInfo LatentInfo)
|
||||
{
|
||||
if (UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull))
|
||||
{
|
||||
FLatentActionManager& LatentActionManager = World->GetLatentActionManager();
|
||||
if (LatentActionManager.FindExistingAction<FMySleepAction>(LatentInfo.CallbackTarget, LatentInfo.UUID) == NULL)
|
||||
{
|
||||
LatentActionManager.AddNewAction(LatentInfo.CallbackTarget, LatentInfo.UUID, new FMySleepAction(Duration, LatentInfo));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UFUNCTION(BlueprintCallable, meta = (Latent, WorldContext = "WorldContextObject", Duration = "5"))
|
||||
static void MySleep2(const UObject* WorldContextObject, float Duration, FLatentActionInfo LatentInfo);
|
||||
};
|
||||
```
|
||||
|
||||
## 蓝图效果:
|
||||
|
||||

|
||||
|
||||
MySleep可以像Delay一样正常工作。但是MySleep2因为没有标明LatentInfo,因此LatentInfo函数参数没有被蓝图系统赋值,导致无法工作。
|
||||
|
||||
在源码里Latent使用的非常频繁,最常见的例子:
|
||||
|
||||
```cpp
|
||||
UFUNCTION(BlueprintCallable, meta=(WorldContext="WorldContextObject", Latent = "", LatentInfo = "LatentInfo", DisplayName = "Load Stream Level (by Name)"), Category="Game")
|
||||
static ENGINE_API void LoadStreamLevel(const UObject* WorldContextObject, FName LevelName, bool bMakeVisibleAfterLoad, bool bShouldBlockOnLoad, FLatentActionInfo LatentInfo);
|
||||
|
||||
UFUNCTION(BlueprintCallable, meta = (Latent, LatentInfo = "LatentInfo", WorldContext = "WorldContextObject", BlueprintInternalUseOnly = "true"), Category = "Utilities")
|
||||
static ENGINE_API void LoadAsset(const UObject* WorldContextObject, TSoftObjectPtr<UObject> Asset, FOnAssetLoaded OnLoaded, FLatentActionInfo LatentInfo);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category="Utilities|FlowControl", meta=(Latent, WorldContext="WorldContextObject", LatentInfo="LatentInfo", Duration="0.2", Keywords="sleep"))
|
||||
static ENGINE_API void Delay(const UObject* WorldContextObject, float Duration, struct FLatentActionInfo LatentInfo );
|
||||
```
|
||||
|
||||
关于使用Latent还是继承自UBlueprintAsyncActionBase来创建蓝图异步节点的差异,可以在网上别的文章查看。
|
@@ -0,0 +1,84 @@
|
||||
# LatentCallbackTarget
|
||||
|
||||
- **功能描述:** 用在FLatentActionInfo::CallbackTarget属性上,告诉蓝图VM在哪个对象上调用函数。
|
||||
- **使用位置:** UPROPERTY
|
||||
- **元数据类型:** bool
|
||||
- **关联项:** [Latent](Latent.md)
|
||||
- **常用程度:** ★
|
||||
|
||||
用在FLatentActionInfo::CallbackTarget属性上,告诉蓝图VM在哪个对象上调用函数。
|
||||
|
||||
```cpp
|
||||
USTRUCT(BlueprintInternalUseOnly)
|
||||
struct FLatentActionInfo
|
||||
{
|
||||
GENERATED_USTRUCT_BODY()
|
||||
|
||||
/** Object to execute the function on. */
|
||||
UPROPERTY(meta=(LatentCallbackTarget = true))
|
||||
TObjectPtr<UObject> CallbackTarget;
|
||||
|
||||
//...
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## 源码里作用的地方:
|
||||
|
||||
```cpp
|
||||
void EmitLatentInfoTerm(FBPTerminal* Term, FProperty* LatentInfoProperty, FBlueprintCompiledStatement* TargetLabel)
|
||||
{
|
||||
// Special case of the struct property emitter. Needs to emit a linkage property for fixup
|
||||
FStructProperty* StructProperty = CastFieldChecked<FStructProperty>(LatentInfoProperty);
|
||||
check(StructProperty->Struct == LatentInfoStruct);
|
||||
|
||||
int32 StructSize = LatentInfoStruct->GetStructureSize();
|
||||
uint8* StructData = (uint8*)FMemory_Alloca(StructSize);
|
||||
StructProperty->InitializeValue(StructData);
|
||||
|
||||
// Assume that any errors on the import of the name string have been caught in the function call generation
|
||||
StructProperty->ImportText_Direct(*Term->Name, StructData, NULL, 0, GLog);
|
||||
|
||||
Writer << EX_StructConst;
|
||||
Writer << LatentInfoStruct;
|
||||
Writer << StructSize;
|
||||
|
||||
checkSlow(Schema);
|
||||
for (FProperty* Prop = LatentInfoStruct->PropertyLink; Prop; Prop = Prop->PropertyLinkNext)
|
||||
{
|
||||
if (TargetLabel && Prop->GetBoolMetaData(FBlueprintMetadata::MD_NeedsLatentFixup))
|
||||
{
|
||||
// Emit the literal and queue a fixup to correct it once the address is known
|
||||
Writer << EX_SkipOffsetConst;
|
||||
CodeSkipSizeType PatchUpNeededAtOffset = Writer.EmitPlaceholderSkip();
|
||||
JumpTargetFixupMap.Add(PatchUpNeededAtOffset, FCodeSkipInfo(FCodeSkipInfo::Fixup, TargetLabel));
|
||||
}
|
||||
else if (Prop->GetBoolMetaData(FBlueprintMetadata::MD_LatentCallbackTarget))
|
||||
{
|
||||
FBPTerminal CallbackTargetTerm;
|
||||
CallbackTargetTerm.bIsLiteral = true;
|
||||
CallbackTargetTerm.Type.PinSubCategory = UEdGraphSchema_K2::PN_Self;
|
||||
EmitTermExpr(&CallbackTargetTerm, Prop);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create a new term for each property, and serialize it out
|
||||
FBPTerminal NewTerm;
|
||||
if(Schema->ConvertPropertyToPinType(Prop, NewTerm.Type))
|
||||
{
|
||||
NewTerm.bIsLiteral = true;
|
||||
Prop->ExportText_InContainer(0, NewTerm.Name, StructData, StructData, NULL, PPF_None);
|
||||
|
||||
EmitTermExpr(&NewTerm, Prop);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Do nothing for unsupported/unhandled property types. This will leave the value unchanged from its constructed default.
|
||||
Writer << EX_Nothing;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Writer << EX_EndStructConst;
|
||||
}
|
||||
```
|
@@ -0,0 +1,81 @@
|
||||
# LatentInfo
|
||||
|
||||
- **功能描述:** 和Latent配合,指明哪个函数参数是LatentInfo参数。
|
||||
- **使用位置:** UFUNCTION
|
||||
- **元数据类型:** string="abc"
|
||||
- **关联项:** [Latent](Latent.md)
|
||||
- **常用程度:** ★★★
|
||||
|
||||
Latent的函数需要FLatentActionInfo才能工作。FLatentActionInfo里记录着这个延迟操作的ID以及下一步要执行的函数名称等。在蓝图的虚拟机运行环境下,一个Latent函数执行的时候,蓝图VM会收集当前的函数上下文信息(典型的比如下Latent函数连接的下一个节点),然后继续赋值到Latent函数的FLatentActionInfo参数上,再配合FPendingLatentAction注册到FLatentActionManager里面去。等时间到达或者触发条件达成后,FLatentActionManager会触发CallbackTarget->ProcessEvent(ExecutionFunction, &(LinkInfo.LinkID)),从而继续执行下去。
|
||||
|
||||
如果没有用LatentInfo来指定函数参数,则因为断了LatentInfo的赋值操作,因此就无法正常工作,蓝图效果图见Latent页面。
|
||||
|
||||
LatentInfo值就像WorldContext一样,会被蓝图VM系统自动的填充值。填充值的操作是在EmitLatentInfoTerm里执行的。把LatentInfoStruct的值填充到LatentInfo的函数参数里去。LatentInfo的参数位置并不重要。LatentInfo指定的函数参数Pin会被隐藏。
|
||||
|
||||
```cpp
|
||||
void EmitFunctionCall(FKismetCompilerContext& CompilerContext, FKismetFunctionContext& FunctionContext, FBlueprintCompiledStatement& Statement, UEdGraphNode* SourceNode)
|
||||
{
|
||||
if (bIsUbergraph && FuncParamProperty->GetName() == FunctionToCall->GetMetaData(FBlueprintMetadata::MD_LatentInfo))
|
||||
{
|
||||
EmitLatentInfoTerm(Term, FuncParamProperty, Statement.TargetLabel);
|
||||
}
|
||||
}
|
||||
|
||||
void EmitLatentInfoTerm(FBPTerminal* Term, FProperty* LatentInfoProperty, FBlueprintCompiledStatement* TargetLabel)
|
||||
{
|
||||
// Special case of the struct property emitter. Needs to emit a linkage property for fixup
|
||||
FStructProperty* StructProperty = CastFieldChecked<FStructProperty>(LatentInfoProperty);
|
||||
check(StructProperty->Struct == LatentInfoStruct);
|
||||
|
||||
int32 StructSize = LatentInfoStruct->GetStructureSize();
|
||||
uint8* StructData = (uint8*)FMemory_Alloca(StructSize);
|
||||
StructProperty->InitializeValue(StructData);
|
||||
|
||||
// Assume that any errors on the import of the name string have been caught in the function call generation
|
||||
StructProperty->ImportText_Direct(*Term->Name, StructData, NULL, 0, GLog);
|
||||
|
||||
Writer << EX_StructConst;
|
||||
Writer << LatentInfoStruct;
|
||||
Writer << StructSize;
|
||||
|
||||
checkSlow(Schema);
|
||||
for (FProperty* Prop = LatentInfoStruct->PropertyLink; Prop; Prop = Prop->PropertyLinkNext)
|
||||
{
|
||||
if (TargetLabel && Prop->GetBoolMetaData(FBlueprintMetadata::MD_NeedsLatentFixup))
|
||||
{
|
||||
// Emit the literal and queue a fixup to correct it once the address is known
|
||||
Writer << EX_SkipOffsetConst;
|
||||
CodeSkipSizeType PatchUpNeededAtOffset = Writer.EmitPlaceholderSkip();
|
||||
JumpTargetFixupMap.Add(PatchUpNeededAtOffset, FCodeSkipInfo(FCodeSkipInfo::Fixup, TargetLabel));
|
||||
}
|
||||
else if (Prop->GetBoolMetaData(FBlueprintMetadata::MD_LatentCallbackTarget))
|
||||
{
|
||||
FBPTerminal CallbackTargetTerm;
|
||||
CallbackTargetTerm.bIsLiteral = true;
|
||||
CallbackTargetTerm.Type.PinSubCategory = UEdGraphSchema_K2::PN_Self;
|
||||
EmitTermExpr(&CallbackTargetTerm, Prop);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create a new term for each property, and serialize it out
|
||||
FBPTerminal NewTerm;
|
||||
if(Schema->ConvertPropertyToPinType(Prop, NewTerm.Type))
|
||||
{
|
||||
NewTerm.bIsLiteral = true;
|
||||
Prop->ExportText_InContainer(0, NewTerm.Name, StructData, StructData, NULL, PPF_None);
|
||||
|
||||
EmitTermExpr(&NewTerm, Prop);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Do nothing for unsupported/unhandled property types. This will leave the value unchanged from its constructed default.
|
||||
Writer << EX_Nothing;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Writer << EX_EndStructConst;
|
||||
}
|
||||
```
|
||||
|
||||
LatentInfo信息的收集是在FKCHandler_CallFunction::CreateFunctionCallStatement里
|
@@ -0,0 +1,86 @@
|
||||
# NeedsLatentFixup
|
||||
|
||||
- **功能描述:** 用在FLatentActionInfo::Linkage属性上,告诉蓝图VM生成跳转信息
|
||||
- **使用位置:** UPROPERTY
|
||||
- **元数据类型:** bool
|
||||
- **关联项:** [Latent](Latent.md)
|
||||
- **常用程度:** ★
|
||||
|
||||
## 在源码里找到的用处:
|
||||
|
||||
```cpp
|
||||
USTRUCT(BlueprintInternalUseOnly)
|
||||
struct FLatentActionInfo
|
||||
{
|
||||
GENERATED_USTRUCT_BODY()
|
||||
|
||||
/** The resume point within the function to execute */
|
||||
UPROPERTY(meta=(NeedsLatentFixup = true))
|
||||
int32 Linkage;
|
||||
|
||||
//...
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## 源码里发挥作用的地方:
|
||||
|
||||
看着就是把Linkage这个属性进行单独的处理。用来在JumpTargetFixupMap里进行专门的跳转
|
||||
|
||||
```cpp
|
||||
void EmitLatentInfoTerm(FBPTerminal* Term, FProperty* LatentInfoProperty, FBlueprintCompiledStatement* TargetLabel)
|
||||
{
|
||||
// Special case of the struct property emitter. Needs to emit a linkage property for fixup
|
||||
FStructProperty* StructProperty = CastFieldChecked<FStructProperty>(LatentInfoProperty);
|
||||
check(StructProperty->Struct == LatentInfoStruct);
|
||||
|
||||
int32 StructSize = LatentInfoStruct->GetStructureSize();
|
||||
uint8* StructData = (uint8*)FMemory_Alloca(StructSize);
|
||||
StructProperty->InitializeValue(StructData);
|
||||
|
||||
// Assume that any errors on the import of the name string have been caught in the function call generation
|
||||
StructProperty->ImportText_Direct(*Term->Name, StructData, NULL, 0, GLog);
|
||||
|
||||
Writer << EX_StructConst;
|
||||
Writer << LatentInfoStruct;
|
||||
Writer << StructSize;
|
||||
|
||||
checkSlow(Schema);
|
||||
for (FProperty* Prop = LatentInfoStruct->PropertyLink; Prop; Prop = Prop->PropertyLinkNext)
|
||||
{
|
||||
if (TargetLabel && Prop->GetBoolMetaData(FBlueprintMetadata::MD_NeedsLatentFixup))
|
||||
{
|
||||
// Emit the literal and queue a fixup to correct it once the address is known
|
||||
Writer << EX_SkipOffsetConst;
|
||||
CodeSkipSizeType PatchUpNeededAtOffset = Writer.EmitPlaceholderSkip();
|
||||
JumpTargetFixupMap.Add(PatchUpNeededAtOffset, FCodeSkipInfo(FCodeSkipInfo::Fixup, TargetLabel));
|
||||
}
|
||||
else if (Prop->GetBoolMetaData(FBlueprintMetadata::MD_LatentCallbackTarget))
|
||||
{
|
||||
FBPTerminal CallbackTargetTerm;
|
||||
CallbackTargetTerm.bIsLiteral = true;
|
||||
CallbackTargetTerm.Type.PinSubCategory = UEdGraphSchema_K2::PN_Self;
|
||||
EmitTermExpr(&CallbackTargetTerm, Prop);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create a new term for each property, and serialize it out
|
||||
FBPTerminal NewTerm;
|
||||
if(Schema->ConvertPropertyToPinType(Prop, NewTerm.Type))
|
||||
{
|
||||
NewTerm.bIsLiteral = true;
|
||||
Prop->ExportText_InContainer(0, NewTerm.Name, StructData, StructData, NULL, PPF_None);
|
||||
|
||||
EmitTermExpr(&NewTerm, Prop);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Do nothing for unsupported/unhandled property types. This will leave the value unchanged from its constructed default.
|
||||
Writer << EX_Nothing;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Writer << EX_EndStructConst;
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 72 KiB |
@@ -0,0 +1,9 @@
|
||||
# NativeBreakFunc
|
||||
|
||||
- **功能描述:** 指定一个函数采用BreakStruct的图标。
|
||||
- **使用位置:** UFUNCTION
|
||||
- **元数据类型:** bool
|
||||
- **关联项:** [NativeMakeFunc](NativeMakeFunc/NativeMakeFunc.md)
|
||||
- **常用程度:** ★
|
||||
|
||||
其功能在NativeMakeFunc里已经说明
|
@@ -0,0 +1,15 @@
|
||||
# NativeConst
|
||||
|
||||
- **功能描述:** 指定有C++里的const标志
|
||||
|
||||
- **使用位置:** UPARAM
|
||||
|
||||
- **引擎模块:** Blueprint
|
||||
|
||||
- **元数据类型:** bool
|
||||
|
||||
- **关联项:**
|
||||
|
||||
UPARAM:[Const](../../Specifier/UPARAM/Blueprint/Const/Const.md)
|
||||
|
||||
- **常用程度:** ★
|
@@ -0,0 +1,95 @@
|
||||
# NativeMakeFunc
|
||||
|
||||
- **功能描述:** 指定一个函数采用MakeStruct的图标
|
||||
- **使用位置:** UFUNCTION
|
||||
- **引擎模块:** Blueprint
|
||||
- **元数据类型:** bool
|
||||
- **关联项:** [NativeBreakFunc](../NativeBreakFunc.md)
|
||||
- **常用程度:** ★
|
||||
|
||||
指定一个函数采用MakeStruct的图标。
|
||||
|
||||
这个函数的实际逻辑是否符合MakeStruct的规范并没有做检测,该标记只是造成显示图标的不同。因此虽然正常情况下都是搭配BlueprintPure,但是BlueprintCallable也无所谓。甚至MakeMyStructNative_Wrong函数的版本没有返回值也可以编译通过。
|
||||
|
||||
## 测试代码:
|
||||
|
||||
```cpp
|
||||
USTRUCT(BlueprintType)
|
||||
struct FMyStruct_ForNative
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere)
|
||||
int32 X = 0;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere)
|
||||
int32 Y = 0;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere)
|
||||
int32 Z = 0;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere)
|
||||
FString MyString;
|
||||
};
|
||||
|
||||
UCLASS(Blueprintable, BlueprintType)
|
||||
class INSIDER_API UMyFunction_NativeMakeBreak :public UBlueprintFunctionLibrary
|
||||
{
|
||||
public:
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UFUNCTION(BlueprintPure, meta = (NativeMakeFunc))
|
||||
static FMyStruct_ForNative MakeMyStructNative(FString ValueString);
|
||||
|
||||
UFUNCTION(BlueprintPure)
|
||||
static FMyStruct_ForNative MakeMyStructNative_NoMeta(FString ValueString);
|
||||
|
||||
UFUNCTION(BlueprintPure, meta = (NativeBreakFunc))
|
||||
static void BreakMyStructNative(const FMyStruct_ForNative& InValue, int32& X, int32& Y, int32& Z);
|
||||
|
||||
UFUNCTION(BlueprintCallable, meta = (NativeMakeFunc))
|
||||
static void MakeMyStructNative_Wrong(FString ValueString);
|
||||
};
|
||||
```
|
||||
|
||||
## 蓝图里效果:
|
||||
|
||||
可以看到如果是NoMeta,则函数的图标就是标准是f图标,否则则是另外的图标。同时也注意到Struct可以有多个Make和Break函数,都可以同时正常使用。
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
在引擎源码里唯一找到的地方是如下代码。因此该标记实际上并没有逻辑上的差别,但是在显示上会有差别。
|
||||
|
||||
可以看见针对NativeMakeFunc和NativeBrakeFunc采用了不同的图标。
|
||||
|
||||
```cpp
|
||||
FSlateIcon UK2Node_CallFunction::GetPaletteIconForFunction(UFunction const* Function, FLinearColor& OutColor)
|
||||
{
|
||||
static const FName NativeMakeFunc(TEXT("NativeMakeFunc"));
|
||||
static const FName NativeBrakeFunc(TEXT("NativeBreakFunc"));
|
||||
|
||||
if (Function && Function->HasMetaData(NativeMakeFunc))
|
||||
{
|
||||
static FSlateIcon Icon(FAppStyle::GetAppStyleSetName(), "GraphEditor.MakeStruct_16x");
|
||||
return Icon;
|
||||
}
|
||||
else if (Function && Function->HasMetaData(NativeBrakeFunc))
|
||||
{
|
||||
static FSlateIcon Icon(FAppStyle::GetAppStyleSetName(), "GraphEditor.BreakStruct_16x");
|
||||
return Icon;
|
||||
}
|
||||
// Check to see if the function is calling an function that could be an event, display the event icon instead.
|
||||
else if (Function && UEdGraphSchema_K2::FunctionCanBePlacedAsEvent(Function))
|
||||
{
|
||||
static FSlateIcon Icon(FAppStyle::GetAppStyleSetName(), "GraphEditor.Event_16x");
|
||||
return Icon;
|
||||
}
|
||||
else
|
||||
{
|
||||
OutColor = GetPalletteIconColor(Function);
|
||||
|
||||
static FSlateIcon Icon(FAppStyle::GetAppStyleSetName(), "Kismet.AllClasses.FunctionIcon");
|
||||
return Icon;
|
||||
}
|
||||
}
|
||||
|
||||
```
|
After Width: | Height: | Size: 262 KiB |
@@ -0,0 +1,7 @@
|
||||
# NotBlueprintThreadSafe
|
||||
|
||||
- **功能描述:** 用在函数上,标记这个函数是不线程安全的
|
||||
- **使用位置:** UFUNCTION
|
||||
- **元数据类型:** bool
|
||||
- **关联项:** [BlueprintThreadSafe](BlueprintThreadSafe.md)
|
||||
- **常用程度:** ★
|
@@ -0,0 +1,88 @@
|
||||
# NotInputConfigurable
|
||||
|
||||
- **功能描述:** 让一些UInputModifier和UInputTrigger不能在ProjectSettings里配置。
|
||||
- **使用位置:** UCLASS
|
||||
- **引擎模块:** Blueprint
|
||||
- **元数据类型:** bool
|
||||
- **限制类型:** UInputModifier和UInputTrigger的子类
|
||||
- **常用程度:** ★
|
||||
|
||||
让一些UInputModifier和UInputTrigger不能在ProjectSettings里配置。
|
||||
|
||||
## 源码例子:
|
||||
|
||||
```cpp
|
||||
UCLASS(NotBlueprintable, meta = (DisplayName = "Chorded Action", NotInputConfigurable = "true"))
|
||||
class ENHANCEDINPUT_API UInputTriggerChordAction : public UInputTrigger
|
||||
{}
|
||||
|
||||
UCLASS(NotBlueprintable, meta = (DisplayName = "Combo (Beta)", NotInputConfigurable = "true"))
|
||||
class ENHANCEDINPUT_API UInputTriggerCombo : public UInputTrigger
|
||||
{}
|
||||
```
|
||||
|
||||
## 测试代码:
|
||||
|
||||
```cpp
|
||||
UCLASS( meta = (NotInputConfigurable = "true"))
|
||||
class INSIDER_API UMyInputTrigger_NotInputConfigurable :public UInputTrigger
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(EditAnywhere)
|
||||
float MyFloat = 123;
|
||||
};
|
||||
|
||||
UCLASS( meta = ())
|
||||
class INSIDER_API UMyInputTrigger_Configurable :public UInputTrigger
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(EditAnywhere)
|
||||
float MyFloatConfigurable = 123;
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## 测试效果:
|
||||
|
||||
可见只有UMyInputTrigger_Configurable 可以编辑默认值。
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
UEnhancedInputDeveloperSettings的UI定制化会收集UInputModifier和UInputTrigger的CDO对象,然后根据NotInputConfigurable过滤掉一些不能配置的。
|
||||
|
||||
```cpp
|
||||
|
||||
GatherNativeClassDetailsCDOs(UInputModifier::StaticClass(), ModifierCDOs);
|
||||
GatherNativeClassDetailsCDOs(UInputTrigger::StaticClass(), TriggerCDOs);
|
||||
|
||||
|
||||
void FEnhancedInputDeveloperSettingsCustomization::GatherNativeClassDetailsCDOs(UClass* Class, TArray<UObject*>& CDOs)
|
||||
{
|
||||
// Strip objects with no config stored properties
|
||||
CDOs.RemoveAll([Class](UObject* Object) {
|
||||
UClass* ObjectClass = Object->GetClass();
|
||||
if (ObjectClass->GetMetaData(TEXT("NotInputConfigurable")).ToBool())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
while (ObjectClass)
|
||||
{
|
||||
for (FProperty* Property : TFieldRange<FProperty>(ObjectClass, EFieldIteratorFlags::ExcludeSuper, EFieldIteratorFlags::ExcludeDeprecated))
|
||||
{
|
||||
if (Property->HasAnyPropertyFlags(CPF_Config))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Stop searching at the base type. We don't care about configurable properties lower than that.
|
||||
ObjectClass = ObjectClass != Class ? ObjectClass->GetSuperClass() : nullptr;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 156 KiB |
@@ -0,0 +1,56 @@
|
||||
# ObjectSetType
|
||||
|
||||
- **功能描述:** 指定统计页面的对象集合类型。
|
||||
- **使用位置:** UCLASS
|
||||
- **引擎模块:** Blueprint
|
||||
- **元数据类型:** string="abc"
|
||||
- **常用程度:** ★
|
||||
|
||||
指定统计页面的对象集合类型。
|
||||
|
||||
属于StatViewer模块,只在固定的内部几个类上使用。
|
||||
|
||||
## 源码例子:
|
||||
|
||||
```cpp
|
||||
|
||||
/** Enum defining the object sets for this stats object */
|
||||
UENUM()
|
||||
enum EPrimitiveObjectSets : int
|
||||
{
|
||||
PrimitiveObjectSets_AllObjects UMETA( DisplayName = "All Objects" , ToolTip = "View primitive statistics for all objects in all levels" ),
|
||||
PrimitiveObjectSets_CurrentLevel UMETA( DisplayName = "Current Level" , ToolTip = "View primitive statistics for objects in the current level" ),
|
||||
PrimitiveObjectSets_SelectedObjects UMETA( DisplayName = "Selected Objects" , ToolTip = "View primitive statistics for selected objects" ),
|
||||
};
|
||||
|
||||
/** Statistics page for primitives. */
|
||||
UCLASS(Transient, MinimalAPI, meta=( DisplayName = "Primitive Stats", ObjectSetType = "EPrimitiveObjectSets" ) )
|
||||
class UPrimitiveStats : public UObject
|
||||
{}
|
||||
```
|
||||
|
||||
## 相应效果:
|
||||
|
||||
在统计页面,可见右上角的类型。
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
```cpp
|
||||
template <typename Entry>
|
||||
class FStatsPage : public IStatsPage
|
||||
{
|
||||
public:
|
||||
FStatsPage()
|
||||
{
|
||||
FString EnumName = Entry::StaticClass()->GetName();
|
||||
EnumName += TEXT(".");
|
||||
EnumName += Entry::StaticClass()->GetMetaData( TEXT("ObjectSetType") );
|
||||
ObjectSetEnum = FindObject<UEnum>( nullptr, *EnumName );
|
||||
bRefresh = false;
|
||||
bShow = false;
|
||||
ObjectSetIndex = 0;
|
||||
}
|
||||
};
|
||||
```
|
After Width: | Height: | Size: 246 KiB |
@@ -0,0 +1,46 @@
|
||||
# ArrayParm
|
||||
|
||||
- **功能描述:** 指定一个函数为使用Array<*>的函数,数组元素类型为通配符的泛型。
|
||||
- **使用位置:** UFUNCTION
|
||||
- **引擎模块:** Blueprint
|
||||
- **元数据类型:** strings="a,b,c"
|
||||
- **关联项:** [ArrayTypeDependentParams](../ArrayTypeDependentParams/ArrayTypeDependentParams.md)
|
||||
- **常用程度:** ★★★
|
||||
|
||||
指定一个函数为使用Array<*>的函数,数组元素类型为通配符的泛型。
|
||||
|
||||
在内部逻辑上的处理区别是有ArrayParm的会采用UK2Node_CallArrayFunction来生成节点,而不是UK2Node_CallFunction。
|
||||
|
||||
ArrayParam可以指定多个,用逗号分隔开。
|
||||
|
||||
在源码里只在UKismetArrayLibrary里使用,但如果自己也想顶一个数组的操作,则也可以加上ArrayParam。
|
||||
|
||||
因为数组元素类型为通配符的泛型,因此在C++中实现的时候,要配合CustomThunk来自己写一些蓝图逻辑胶水代码才好正确处理不同的数组类型。这部分可以参照源码里UKismetArrayLibrary的样例模仿。
|
||||
|
||||
## 测试代码:
|
||||
|
||||
```cpp
|
||||
UCLASS(Blueprintable, BlueprintType)
|
||||
class INSIDER_API UMyFunction_Param :public UBlueprintFunctionLibrary
|
||||
{
|
||||
public:
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
//Array
|
||||
UFUNCTION(BlueprintPure, CustomThunk, meta = (ArrayParm = "TargetArray"))
|
||||
static int32 MyArray_Count(const TArray<int32>& TargetArray);
|
||||
static int32 GenericMyArray_Count(const void* TargetArray, const FArrayProperty* ArrayProp);
|
||||
DECLARE_FUNCTION(execMyArray_Count);
|
||||
|
||||
UFUNCTION(BlueprintPure, CustomThunk, meta = (ArrayParm = "ArrayA,ArrayB", ArrayTypeDependentParams = "ArrayB"))
|
||||
static int32 MyArray_CompareSize(const TArray<int32>& ArrayA, const TArray<int32>& ArrayB);
|
||||
static int32 GenericMyArray_CompareSize(void* ArrayA, const FArrayProperty* ArrayAProp, void* ArrayB, const FArrayProperty* ArrayBProp);
|
||||
DECLARE_FUNCTION(execMyArray_CompareSize);
|
||||
};
|
||||
```
|
||||
|
||||
## 蓝图效果:
|
||||
|
||||

|
||||
|
||||
可以看到,在没有连接具体数组类型的时候,Array是灰色的通配符类型。而连接上不同的数组类型,Array参数引脚就会自动变成相应的类型,这些逻辑是在UK2Node_CallArrayFunction中实现的,有兴趣的去自行翻阅。
|
After Width: | Height: | Size: 74 KiB |
@@ -0,0 +1,69 @@
|
||||
# ArrayTypeDependentParams
|
||||
|
||||
- **功能描述:** 当ArryParam指定的函数拥有两个或以上Array参数的时候,指定哪些数组参数的类型也应该相应的被更新改变。
|
||||
- **使用位置:** UFUNCTION
|
||||
- **元数据类型:** string="abc"
|
||||
- **关联项:** [ArrayParm](../ArrayParm/ArrayParm.md)
|
||||
|
||||
当ArryParam指定的函数拥有两个或以上Array参数的时候,指定哪些数组参数的类型也应该相应的被更新改变。
|
||||
|
||||
指明一个参数的类型,用于确定ArrayParam的值类型
|
||||
|
||||
```cpp
|
||||
UCLASS(Blueprintable, BlueprintType)
|
||||
class INSIDER_API UMyFunction_Param :public UBlueprintFunctionLibrary
|
||||
{
|
||||
public:
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
//Array
|
||||
|
||||
UFUNCTION(BlueprintPure, CustomThunk, meta = (ArrayParm = "ArrayA,ArrayB", ArrayTypeDependentParams = "ArrayB"))
|
||||
static int32 MyArray_CompareSize(const TArray<int32>& ArrayA, const TArray<int32>& ArrayB);
|
||||
static int32 GenericMyArray_CompareSize(void* ArrayA, const FArrayProperty* ArrayAProp, void* ArrayB, const FArrayProperty* ArrayBProp);
|
||||
DECLARE_FUNCTION(execMyArray_CompareSize);
|
||||
};
|
||||
```
|
||||
|
||||
如果没有ArrayTypeDependentParams,在连接ArrayA后,ArrayB的类型依然没有确定,即使连接上了也是如此,这应该是引擎的实现所限制。编译会造成编译错误。
|
||||
|
||||

|
||||
|
||||
因此ArrayTypeDependentParams可以指定另外的数组参数,其类型会由别的(第一个)数组实际参数所决定,即typeof(ArrayB)=typeof(ArrayA)。在示例代码里所示加上ArrayB作为ArrayTypeDependentParams 之后,MyArrayB无论是先连接到ArrayA还是ArrayB都可以触发二者改变为一致的数组类型。这是因为ArrayA作为第一个参数,天生在引擎内已经实现了第一个参数的动态类型实时变化。因此我们只要再加上ArrayB就好了。
|
||||
|
||||
## 原理:
|
||||
|
||||
引擎内已经实现了第一个参数的动态类型实时变化:
|
||||
|
||||
```cpp
|
||||
void UK2Node_CallArrayFunction::AllocateDefaultPins()
|
||||
{
|
||||
Super::AllocateDefaultPins();
|
||||
|
||||
UEdGraphPin* TargetArrayPin = GetTargetArrayPin();
|
||||
if (ensureMsgf(TargetArrayPin, TEXT("%s"), *GetFullName()))
|
||||
{
|
||||
TargetArrayPin->PinType.ContainerType = EPinContainerType::Array;
|
||||
TargetArrayPin->PinType.bIsReference = true;
|
||||
TargetArrayPin->PinType.PinCategory = UEdGraphSchema_K2::PC_Wildcard;
|
||||
TargetArrayPin->PinType.PinSubCategory = NAME_None;
|
||||
TargetArrayPin->PinType.PinSubCategoryObject = nullptr;
|
||||
}
|
||||
|
||||
TArray< FArrayPropertyPinCombo > ArrayPins;
|
||||
GetArrayPins(ArrayPins);
|
||||
for(auto Iter = ArrayPins.CreateConstIterator(); Iter; ++Iter)
|
||||
{
|
||||
if(Iter->ArrayPropPin)
|
||||
{
|
||||
Iter->ArrayPropPin->bHidden = true;
|
||||
Iter->ArrayPropPin->bNotConnectable = true;
|
||||
Iter->ArrayPropPin->bDefaultValueIsReadOnly = true;
|
||||
}
|
||||
}
|
||||
|
||||
PropagateArrayTypeInfo(TargetArrayPin);
|
||||
}
|
||||
```
|
||||
|
||||
关于ArrayDependentParam的作用机制,可以参照UK2Node_CallArrayFunction里的NotifyPinConnectionListChanged和PropagateArrayTypeInfo这两个函数的实现,可以看到其他的数组参数Pin类型被动态的修改为SourcePin的类型。
|
After Width: | Height: | Size: 46 KiB |
@@ -0,0 +1,134 @@
|
||||
# AutoCreateRefTerm
|
||||
|
||||
- **功能描述:** 指定函数的多个输入引用参数在没有连接的时候自动为其创建默认值
|
||||
- **使用位置:** UFUNCTION
|
||||
- **引擎模块:** Blueprint
|
||||
- **元数据类型:** strings="a,b,c"
|
||||
- **常用程度:** ★★★★★
|
||||
|
||||
指定函数的多个输入引用参数在没有连接的时候自动为其创建默认值。
|
||||
|
||||
要注意“输入”+“引用”,这两个前提项。
|
||||
|
||||
当有些情况你想把函数的参数采用引用来传递,但是又不想每次都得必须连接一个变量,想在不连接的时候提供一个内联编辑的默认值,因此蓝图系统就提供了这么一个便利功能。
|
||||
|
||||
## 测试代码:
|
||||
|
||||
```cpp
|
||||
UFUNCTION(BlueprintCallable, meta = (AutoCreateRefTerm = "Location,Value"))
|
||||
static bool MyFunc_HasAutoCreateRefTerm(const FVector& Location, const int32& Value) { return false; }
|
||||
|
||||
UFUNCTION(BlueprintCallable)
|
||||
static bool MyFunc_NoAutoCreateRefTerm(const FVector& Location, const int32& Value) { return false; }
|
||||
|
||||
UFUNCTION(BlueprintCallable)
|
||||
static bool MyFunc_NoRef(FVector Location, int32 Value) { return false; }
|
||||
```
|
||||
|
||||
## 蓝图效果:
|
||||
|
||||
可以见到MyFunc_NoAutoCreateRefTerm的函数会产生编译的报错,因为是引用参数但是却没有连接,导致引用缺少实参。
|
||||
|
||||

|
||||
|
||||
## 原理代码:
|
||||
|
||||
```cpp
|
||||
void UEdGraphSchema_K2::GetAutoEmitTermParameters(const UFunction* Function, TArray<FString>& AutoEmitParameterNames)
|
||||
{
|
||||
AutoEmitParameterNames.Reset();
|
||||
|
||||
const FString& MetaData = Function->GetMetaData(FBlueprintMetadata::MD_AutoCreateRefTerm);
|
||||
if (!MetaData.IsEmpty())
|
||||
{
|
||||
MetaData.ParseIntoArray(AutoEmitParameterNames, TEXT(","), true);
|
||||
|
||||
for (int32 NameIndex = 0; NameIndex < AutoEmitParameterNames.Num();)
|
||||
{
|
||||
FString& ParameterName = AutoEmitParameterNames[NameIndex];
|
||||
ParameterName.TrimStartAndEndInline();
|
||||
if (ParameterName.IsEmpty())
|
||||
{
|
||||
AutoEmitParameterNames.RemoveAtSwap(NameIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
++NameIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Allow any params that are blueprint defined to be autocreated:
|
||||
if (!FBlueprintEditorUtils::IsNativeSignature(Function))
|
||||
{
|
||||
for ( TFieldIterator<FProperty> ParamIter(Function, EFieldIterationFlags::Default);
|
||||
ParamIter && (ParamIter->PropertyFlags & CPF_Parm);
|
||||
++ParamIter)
|
||||
{
|
||||
FProperty* Param = *ParamIter;
|
||||
if(Param->HasAnyPropertyFlags(CPF_ReferenceParm))
|
||||
{
|
||||
AutoEmitParameterNames.Add(Param->GetName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
还有在
|
||||
void UK2Node_CallFunction::ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph)
|
||||
{
|
||||
if ( Function )
|
||||
{
|
||||
TArray<FString> AutoCreateRefTermPinNames;
|
||||
CompilerContext.GetSchema()->GetAutoEmitTermParameters(Function, AutoCreateRefTermPinNames);
|
||||
const bool bHasAutoCreateRefTerms = AutoCreateRefTermPinNames.Num() != 0;
|
||||
|
||||
for (UEdGraphPin* Pin : Pins)
|
||||
{
|
||||
const bool bIsRefInputParam = Pin && Pin->PinType.bIsReference && (Pin->Direction == EGPD_Input) && !CompilerContext.GetSchema()->IsMetaPin(*Pin);
|
||||
if (!bIsRefInputParam)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const bool bHasConnections = Pin->LinkedTo.Num() > 0;
|
||||
const bool bCreateDefaultValRefTerm = bHasAutoCreateRefTerms &&
|
||||
!bHasConnections && AutoCreateRefTermPinNames.Contains(Pin->PinName.ToString());
|
||||
|
||||
if (bCreateDefaultValRefTerm)
|
||||
{
|
||||
const bool bHasDefaultValue = !Pin->DefaultValue.IsEmpty() || Pin->DefaultObject || !Pin->DefaultTextValue.IsEmpty();
|
||||
|
||||
// copy defaults as default values can be reset when the pin is connected
|
||||
const FString DefaultValue = Pin->DefaultValue;
|
||||
UObject* DefaultObject = Pin->DefaultObject;
|
||||
const FText DefaultTextValue = Pin->DefaultTextValue;
|
||||
bool bMatchesDefaults = Pin->DoesDefaultValueMatchAutogenerated();
|
||||
|
||||
UEdGraphPin* ValuePin = InnerHandleAutoCreateRef(this, Pin, CompilerContext, SourceGraph, bHasDefaultValue);
|
||||
if ( ValuePin )
|
||||
{
|
||||
if (bMatchesDefaults)
|
||||
{
|
||||
// Use the latest code to set default value
|
||||
Schema->SetPinAutogeneratedDefaultValueBasedOnType(ValuePin);
|
||||
}
|
||||
else
|
||||
{
|
||||
ValuePin->DefaultValue = DefaultValue;
|
||||
ValuePin->DefaultObject = DefaultObject;
|
||||
ValuePin->DefaultTextValue = DefaultTextValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
// since EX_Self does not produce an addressable (referenceable) FProperty, we need to shim
|
||||
// in a "auto-ref" term in its place (this emulates how UHT generates a local value for
|
||||
// native functions; hence the IsNative() check)
|
||||
else if (bHasConnections && Pin->LinkedTo[0]->PinType.PinSubCategory == UEdGraphSchema_K2::PSC_Self && Pin->PinType.bIsConst && !Function->IsNative())
|
||||
{
|
||||
InnerHandleAutoCreateRef(this, Pin, CompilerContext, SourceGraph, /*bForceAssignment =*/true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 128 KiB |
@@ -0,0 +1,122 @@
|
||||
# CustomStructureParam
|
||||
|
||||
- **功能描述:** 被CustomStructureParam标记的函数参数会变成Wildcard的通配符参数,其引脚的类型会等于连接的变量类型。
|
||||
- **使用位置:** UFUNCTION
|
||||
- **引擎模块:** Blueprint
|
||||
- **元数据类型:** strings="a,b,c"
|
||||
- **常用程度:** ★★★★★
|
||||
|
||||
被CustomStructureParam标记的多个函数参数会变成Wildcard的通配符参数,其引脚的类型会等于连接的变量类型。
|
||||
|
||||
CustomStructureParam总是和CustomThunk一起配合使用,这样才能在自己的函数体内来处理泛型的参数类型。
|
||||
|
||||
```cpp
|
||||
UFUNCTION(BlueprintCallable, CustomThunk, meta = (DisplayName = "PrintStructFields", CustomStructureParam = "inputStruct"))
|
||||
static FString PrintStructFields(const int32& inputStruct) { return TEXT(""); }
|
||||
|
||||
DECLARE_FUNCTION(execPrintStructFields);
|
||||
static FString Generic_PrintStructFields(const UScriptStruct* ScriptStruct, const void* StructData);
|
||||
|
||||
DEFINE_FUNCTION(UMyFunction_Custom::execPrintStructFields)
|
||||
{
|
||||
FString result;
|
||||
Stack.MostRecentPropertyAddress = nullptr;
|
||||
Stack.StepCompiledIn<FStructProperty>(nullptr);
|
||||
|
||||
void* StructData = Stack.MostRecentPropertyAddress;
|
||||
FStructProperty* StructProperty = CastField<FStructProperty>(Stack.MostRecentProperty);
|
||||
UScriptStruct* ScriptStruct = StructProperty->Struct;
|
||||
P_FINISH;
|
||||
P_NATIVE_BEGIN;
|
||||
|
||||
result = Generic_PrintStructFields(ScriptStruct, StructData);
|
||||
|
||||
P_NATIVE_END;
|
||||
*(FString*)RESULT_PARAM = result;
|
||||
}
|
||||
|
||||
FString UMyFunction_Custom::Generic_PrintStructFields(const UScriptStruct* ScriptStruct, const void* StructData)
|
||||
{
|
||||
FString str;
|
||||
for (TFieldIterator<FProperty> i(ScriptStruct); i; ++i)
|
||||
{
|
||||
FString propertyValueString;
|
||||
const void* propertyValuePtr = i->ContainerPtrToValuePtr<const void*>(StructData);
|
||||
i->ExportTextItem_Direct(propertyValueString, propertyValuePtr, nullptr, (UObject*)ScriptStruct, PPF_None);
|
||||
|
||||
str += FString::Printf(TEXT("%s:%s\n"), *i->GetFName().ToString(), *propertyValueString);
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## 蓝图中的效果:
|
||||
|
||||

|
||||
|
||||
可以看到定义了一个接受通用结构参数的节点,然后打印出内部所有的属性。其中CustomStructureParam 指定函数的参数是自定义的类型。
|
||||
|
||||
源码中的典型例子是
|
||||
|
||||
```cpp
|
||||
UFUNCTION(BlueprintCallable, CustomThunk, Category = "DataTable", meta=(CustomStructureParam = "OutRow", BlueprintInternalUseOnly="true"))
|
||||
static ENGINE_API bool GetDataTableRowFromName(UDataTable* Table, FName RowName, FTableRowBase& OutRow);
|
||||
```
|
||||
|
||||
## 原理:
|
||||
|
||||
首先拥有CustomStructureParam的参数会被识别为Wildcard属性。然后通过FCustomStructureParamHelper来控制Pin->PinType = LinkedTo->PinType;,从而改变Pin的实际类型。
|
||||
|
||||
```cpp
|
||||
bool UEdGraphSchema_K2::IsWildcardProperty(const FProperty* Property)
|
||||
{
|
||||
UFunction* Function = Property->GetOwner<UFunction>();
|
||||
|
||||
return Function && ( UK2Node_CallArrayFunction::IsWildcardProperty(Function, Property)
|
||||
|| UK2Node_CallFunction::IsStructureWildcardProperty(Function, Property->GetFName())
|
||||
|| UK2Node_CallFunction::IsWildcardProperty(Function, Property)
|
||||
|| FEdGraphUtilities::IsArrayDependentParam(Function, Property->GetFName()) );
|
||||
}
|
||||
|
||||
static void FCustomStructureParamHelper::HandleSinglePin(UEdGraphPin* Pin)
|
||||
{
|
||||
if (Pin)
|
||||
{
|
||||
if (Pin->LinkedTo.Num() > 0)
|
||||
{
|
||||
UEdGraphPin* LinkedTo = Pin->LinkedTo[0];
|
||||
check(LinkedTo);
|
||||
|
||||
if (UK2Node* Node = Cast<UK2Node>(Pin->GetOwningNode()))
|
||||
{
|
||||
ensure(
|
||||
!LinkedTo->PinType.IsContainer() ||
|
||||
Node->DoesWildcardPinAcceptContainer(Pin)
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
ensure( !LinkedTo->PinType.IsContainer() );
|
||||
}
|
||||
|
||||
Pin->PinType = LinkedTo->PinType;
|
||||
}
|
||||
else
|
||||
{
|
||||
// constness and refness are controlled by our declaration
|
||||
// but everything else needs to be reset to default wildcard:
|
||||
const bool bWasRef = Pin->PinType.bIsReference;
|
||||
const bool bWasConst = Pin->PinType.bIsConst;
|
||||
|
||||
Pin->PinType = FEdGraphPinType();
|
||||
Pin->PinType.bIsReference = bWasRef;
|
||||
Pin->PinType.bIsConst = bWasConst;
|
||||
Pin->PinType.PinCategory = UEdGraphSchema_K2::PC_Wildcard;
|
||||
Pin->PinType.PinSubCategory = NAME_None;
|
||||
Pin->PinType.PinSubCategoryObject = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 69 KiB |
@@ -0,0 +1,144 @@
|
||||
# DeterminesOutputType
|
||||
|
||||
- **功能描述:** 指定一个参数的类型作为函数动态调整输出参数类型的参考类型
|
||||
- **使用位置:** UFUNCTION
|
||||
- **引擎模块:** Blueprint
|
||||
- **元数据类型:** string="abc"
|
||||
- **限制类型:** Class或Object指针类型,或容器类型
|
||||
- **关联项:** [DynamicOutputParam](../DynamicOutputParam.md)
|
||||
- **常用程度:** ★★★
|
||||
|
||||
指定一个参数的类型作为函数输出参数的类型。
|
||||
|
||||
假定这么一个函数原型:
|
||||
|
||||
```cpp
|
||||
UFUNCTION(BlueprintCallable, meta = (DeterminesOutputType = "A",DynamicOutputParam="P1,P2"))
|
||||
TypeR MyFunc(TypeA A,Type1 P1,Type2 P2,Type3 P3);
|
||||
```
|
||||
|
||||
DeterminesOutputType的值指定了一个函数参数名称,即A。其TypeA的类型必须是Class或Object,一般是TSubClassOf<XXX> 或者XXX* ,当然也可以是TArray<XXX*>,还可以是指向参数结构里的某个属性。如Args_ActorClassType。TSoftObjectPtr<XXX>也是可以的,指向一个子类Asset对象,然后输出的基类Asset*就可以相应改变。
|
||||
|
||||
所谓输出参数包括返回值和函数的输出参数,因此上述函数原型里的TypeR,P1,P2都是输出参数。为了让输出参数的类型也相应变化,TypeR、Type1和Type2的类型也得是Class或Object类型,且A参数在蓝图节点上实际选择的类型必须是输出参数类型的子类,这样才能自动转换过去。
|
||||
|
||||
如果没有P1和P2,只把返回值当作TypeR,则可以不指定DynamicOutputParam也可以自动默认把返回值当作动态的输出参数。否则则需要手动书写DynamicOutputParam来指定哪些函数参数来支持动态类型。
|
||||
|
||||
## 测试代码:
|
||||
|
||||
```cpp
|
||||
|
||||
UCLASS(Blueprintable, BlueprintType)
|
||||
class INSIDER_API AMyAnimalActor :public AActor
|
||||
{
|
||||
public:
|
||||
GENERATED_BODY()
|
||||
};
|
||||
|
||||
UCLASS(Blueprintable, BlueprintType)
|
||||
class INSIDER_API AMyCatActor :public AMyAnimalActor
|
||||
{
|
||||
public:
|
||||
GENERATED_BODY()
|
||||
};
|
||||
|
||||
UCLASS(Blueprintable, BlueprintType)
|
||||
class INSIDER_API AMyDogActor :public AMyAnimalActor
|
||||
{
|
||||
public:
|
||||
GENERATED_BODY()
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FMyOutputTypeArgs
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere)
|
||||
int32 MyInt = 1;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere)
|
||||
TSubclassOf<AMyAnimalActor> ActorClassType;
|
||||
};
|
||||
|
||||
UCLASS(Blueprintable, BlueprintType)
|
||||
class INSIDER_API UMyFunctionLibrary_OutputTypeTest :public UBlueprintFunctionLibrary
|
||||
{
|
||||
public:
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
//class
|
||||
UFUNCTION(BlueprintCallable, meta = (DeterminesOutputType = "ActorClassType"))
|
||||
static TArray<AActor*> MyGetAnimals(TSubclassOf<AMyAnimalActor> ActorClassType);
|
||||
|
||||
//have to add DynamicOutputParam
|
||||
UFUNCTION(BlueprintCallable, meta = (DeterminesOutputType = "ActorClassType", DynamicOutputParam = "OutActors"))
|
||||
static void MyGetAnimalsOut(TSubclassOf<AMyAnimalActor> ActorClassType, TArray<AActor*>& OutActors);
|
||||
|
||||
//have to add DynamicOutputParam
|
||||
UFUNCTION(BlueprintCallable, meta = (DeterminesOutputType = "ActorClassType", DynamicOutputParam = "FirstOutActor,OutActors"))
|
||||
static void MyGetAnimalsOut2(TSubclassOf<AMyAnimalActor> ActorClassType, AActor*& FirstOutActor, TArray<AActor*>& OutActors);
|
||||
|
||||
//object
|
||||
UFUNCTION(BlueprintCallable, meta = (DeterminesOutputType = "ExampleActor"))
|
||||
static TArray<AActor*> MyGetAnimalsWithActor(AMyAnimalActor* ExampleActor);
|
||||
|
||||
UFUNCTION(BlueprintCallable, meta = (DeterminesOutputType = "ExampleActorArray"))
|
||||
static TArray<AActor*> MyGetAnimalsWithActorArray(TArray<AMyAnimalActor*> ExampleActorArray);
|
||||
|
||||
//struct property
|
||||
UFUNCTION(BlueprintCallable, meta = (DeterminesOutputType = "Args_ActorClassType"))
|
||||
static TArray<AActor*> MyGetAnimalsWithStructProperty(const FMyOutputTypeArgs& Args);
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## 蓝图中效果:
|
||||
|
||||
用返回值当作输出参数的例子,注意到返回值类型实际变成了TArray<AMyCatActor*>。
|
||||
|
||||

|
||||
|
||||
也可以加上DynamicOutputParam来指定输出参数作为动态类型参数:
|
||||
|
||||

|
||||
|
||||
DynamicOutputParam可以指定多个参数
|
||||
|
||||

|
||||
|
||||
DeterminesOutputType 的参数类型也可以是Object或者Object的容器:
|
||||
|
||||

|
||||
|
||||
DeterminesOutputType 的参数甚至可以是结构里的某个属性,但是只有SplitStruct的时候才生效,因为这个时候结构的属性变量才变成函数的Pin,才可以进行DeterminesOutputType的名称比对。这个时候要书写成“A_B”,而不是“A.B”。
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
DeterminesOutputType的作用机制是根据这个名称去函数蓝图节点上查找Pin,这个Pin得是Class或Object类型的(容器也行),因为必须是这二者才支持指针类型的转换。这个Pin在蓝图节点上是会由各种TypePicker来实际指定值,比如ClassPicker或ObjectPicker。之后根据TypePicker选择的值,就可以相应的调整DynamicOutputParam指定的参数的类型(或返回参数),真正发挥类型改变的是
|
||||
|
||||
Pin->PinType.PinSubCategoryObject = PickedClass;这一句。
|
||||
|
||||
```cpp
|
||||
void FDynamicOutputHelper::ConformOutputType() const
|
||||
{
|
||||
if (IsTypePickerPin(MutatingPin))
|
||||
{
|
||||
UClass* PickedClass = GetPinClass(MutatingPin);
|
||||
UK2Node_CallFunction* FuncNode = GetFunctionNode();
|
||||
|
||||
// See if there is any dynamic output pins
|
||||
TArray<UEdGraphPin*> DynamicPins;
|
||||
GetDynamicOutPins(FuncNode, DynamicPins);
|
||||
|
||||
// Set the pins class
|
||||
for (UEdGraphPin* Pin : DynamicPins)
|
||||
{
|
||||
if (ensure(Pin != nullptr))
|
||||
{
|
||||
Pin->PinType.PinSubCategoryObject = PickedClass;//设定每个动态参数的子类型
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 37 KiB |
After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 48 KiB |
After Width: | Height: | Size: 102 KiB |