vault backup: 2024-10-12 17:19:45

This commit is contained in:
2024-10-12 17:19:46 +08:00
parent ff94ddca61
commit 244c0c52f6
960 changed files with 31348 additions and 10 deletions

View File

@@ -0,0 +1,72 @@
# AdvancedDisplay
- **功能描述:** 把函数的一些参数折叠起来不显示,需要手动点开下拉箭头来展开编辑。
- **使用位置:** UFUNCTION
- **引擎模块:** Blueprint
- **元数据类型:** strings="abc"
- **常用程度:** ★★★★★
把函数的一些参数折叠起来不显示,需要手动点开下拉箭头来展开编辑。
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; }
```
## 蓝图效果:
![Untitled](Untitled.png)
源码中典型的例子是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;
}
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

View File

@@ -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。
![Untitled](Untitled.png)
## 原理:
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");
}
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

View File

@@ -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就会产生报错。
![Untitled](Untitled.png)
## 原理代码:
从这可以看出该函数必须是staticC++中的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);
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

View File

@@ -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)
- **常用程度:** ★★★

View File

@@ -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上时此函数是一个内部实现细节用于实现另一个函数或节点。其从未直接在蓝图图表中公开。

View File

@@ -0,0 +1,19 @@
# BlueprintInternalUseOnlyHierarchical
- **功能描述:** 标明该结构及其子类都不暴露给用户定义和使用,均只能在蓝图系统内部使用
- **使用位置:** USTRUCT
- **引擎模块:** Blueprint
- **元数据类型:** bool
- **关联项:**
Meta[BlueprintInternalUseOnly](BlueprintInternalUseOnly.md), [BlueprintType](BlueprintType.md)
USTRUCT[BlueprintInternalUseOnlyHierarchical ](../../Specifier/USTRUCT/Blueprint/BlueprintInternalUseOnlyHierarchical.md)
- **常用程度:** ★
指明一个不向最终用户公开的BlueprintType类型的结构以及其派生的结构。

View File

@@ -0,0 +1,19 @@
# BlueprintPrivate
- **功能描述:** 指定该函数或属性只能在本类中被调用或读写类似C++中的private的作用域限制。不可在别的蓝图类里访问。
- **使用位置:** UFUNCTION, UPROPERTY
- **元数据类型:** bool
- **关联项:** [BlueprintProtected](../BlueprintProtected/BlueprintProtected.md)
- **常用程度:** ★★
在函数细节面板上可以设置函数的访问权限:
![Untitled](Untitled.png)
造成的结果就是在函数上增加BlueprintPrivate=“true”
在细节面板上可以设置属性的
![Untitled](Untitled%201.png)
结果也是在属性上增加BlueprintPrivate=“true”

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -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在更加的蓝图子类无法被调用。
![Untitled](Untitled.png)
蓝图中的子类BPA_Access_Child继承自BPA_Access_Base效果
可见MyNative函数的访问一样。而MyBPPrivate则不能被调用了这和我们预想的规则一样。
![Untitled](Untitled%201.png)
而在外部类中(BPA_Access_Other继承自Actor)通过BPA_Access_Base或BPA_Access_Child对象实例访问函数的时候发现带有BlueprintProtected和BlueprintPrivate都不能被调用。BP的函数也只有AccessSpecifier为默认Public的可以调用。这个规则也很符合预期。
![Untitled](Untitled%202.png)
## 原理:
在蓝图右键上是否可以选择该函数的过滤逻辑:
如果是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但因为是本类里定义的所以在本类里也依然可以读写访问。
![Untitled](Untitled%203.png)
继续在蓝图中的子类BPA_Access_Child继承自BPA_Access_Base效果
Protected的属性依然都可以访问但是MyBPIntPrivate属性因为是Private的因此都不能读写如果强制粘贴节点会在编译的时候报错。Private的含义是只在本类中才可以访问。
![Untitled](Untitled%204.png)
而在外部类中(BPA_Access_Other继承自Actor)通过BPA_Access_Base或BPA_Access_Child对象实例访问属性的时候带有BlueprintProtected和BlueprintPrivate都不能访问。而C++中的protected修饰并无影响。
而MyBPIntPrivate因为是Private所以不能访问。
![Untitled](Untitled%205.png)
## 原理:
在源码里搜索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;
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 445 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

View File

@@ -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)
- **常用程度:** ★★★

View File

@@ -0,0 +1,107 @@
# BlueprintThreadSafe
- **功能描述:** 用在类上或函数上,标记类里的函数都是线程安全的。
这样就可以在动画蓝图等非游戏线程被调用了。
- **使用位置:** UCLASS, UFUNCTION
- **引擎模块:** Blueprint
- **元数据类型:** bool
- **限制类型:** 从实践上类一般是BlueprintFunctionLibrary
- **关联项:** [NotBlueprintThreadSafe](../NotBlueprintThreadSafe.md)
- **常用程度:** ★★★
动画蓝图的AimGraph默认是开启线程安全Update的。设置在ClassSettings里默认是打开的
![Untitled](Untitled.png)
可参考官方文档的**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)
![Untitled](Untitled%201.png)
## 测试蓝图函数库:
同样的函数一个打开ThreadSafe一个没有。没有的那个函数在动画蓝图的AnimGraph里使用的时候在编译的时候就会触发警告。
![Untitled](Untitled%202.png)
测试结果:
![Untitled](Untitled%203.png)
## 在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;}
};
```
## 动画蓝图的测试效果:
![Untitled](Untitled%204.png)
## 解析原理:
```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))这种是没有被识别判断的,因此并没有什么意义。

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@@ -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)
- **常用程度:** ★★★★★

View File

@@ -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);
}
}
```

View File

@@ -0,0 +1,15 @@
# CallInEditor
- **功能描述:** 可以在Actor的细节面板上作为一个按钮来调用该函数。
- **使用位置:** UFUNCTION
- **引擎模块:** Blueprint
- **元数据类型:** bool
- **关联项:**
UFUNCTION[CallInEditor](../../Specifier/UFUNCTION/Blueprint/CallInEditor/CallInEditor.md)
- **常用程度:** ★★★★★

View File

@@ -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。
![Untitled](Untitled.png)
## 源码里典型的应用是:
```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);
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 KiB

View File

@@ -0,0 +1,15 @@
# CannotImplementInterfaceInBlueprint
- **功能描述:** 指定该接口不能在蓝图中实现
- **引擎模块:** Blueprint
- **元数据类型:** bool
- **关联项:**
UINTERFACE[NotBlueprintable](../../Specifier/UINTERFACE/Blueprint/NotBlueprintable/NotBlueprintable.md)
- **常用程度:** ★★★
和UINTERFACE(NotBlueprintable)的效果一样,指定不能在蓝图中继承

View File

@@ -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; }
```
## 蓝图效果:
![Untitled](Untitled.png)
## 原理:
标记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();
}
}
```

View File

@@ -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;}
```
## 蓝图效果:
显示效果明显发生了变化。同时我们在蓝图里定义的函数也可以通过这个细节面板上的设置变成压缩模式展示。
![Untitled](Untitled.png)
## 原理:
```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();
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

View File

@@ -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当作第一个函数参数显得更加简洁一些。
![Untitled](Untitled.png)
如果是BlueprintPure也是可以的
![Untitled](Untitled%201.png)
## 原理:
蓝图中的函数调用在编译的时候会自动的创建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;
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

View File

@@ -0,0 +1,15 @@
# DisplayName
- **功能描述:** 此节点在蓝图中的命名将被此处提供的值所取代,而非代码生成的命名。
- **使用位置:** UCLASS, UENUM::UMETA, UFUNCTION, UPARAM, UPROPERTY
- **引擎模块:** Blueprint
- **元数据类型:** string="abc"
- **关联项:**
UPARAM[DisplayName](../../Specifier/UPARAM/Blueprint/DisplayName/DisplayName.md)
- **常用程度:** ★★★★★

View File

@@ -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;
}
};
```
## 测试效果:
![Untitled](Untitled.png)
## 原理:
会提前验证是否包含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;
}
};
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

View File

@@ -0,0 +1,7 @@
# ExpandBoolAsExecs
- **功能描述:** 是ExpandEnumAsExecs的别名完全等价其功能。
- **使用位置:** UFUNCTION
- **元数据类型:** string="abc"
- **关联项:** [ExpandEnumAsExecs](ExpandEnumAsExecs/ExpandEnumAsExecs.md)
- **常用程度:** ★★★★★

View File

@@ -0,0 +1,86 @@
# ExpandEnumAsExecs
- **功能描述:** 指定多个enum或bool类型的函数参数自动根据条目生成相应的多个输入或输出执行引脚并根据实参值不同来相应改变控制流。
- **使用位置:** UFUNCTION
- **引擎模块:** Blueprint
- **元数据类型:** strings="abc"
- **关联项:** [ExpandBoolAsExecs](../ExpandBoolAsExecs.md)
- **常用程度:** ★★★★★
指定多个enum或bool类型的函数参数自动根据条目生成相应的多个输入或输出执行引脚并根据实参值不同来相应改变控制流。
支持改变输入和输出的Exec输入Exec只可以一个但是输出ExecEnum Pin可以多个。但是不能用在BlueprintPure上都没Exec引脚了
也可以通过‘|’来分隔。
支持3种参数类型enum classTEnumAsByte<EMyExecPins2::Type>和booleunum必须用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节点连接在了一起一样。
![Untitled](Untitled.png)
## 原理:
真正的创建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。

Binary file not shown.

After

Width:  |  Height:  |  Size: 468 KiB

View File

@@ -0,0 +1,98 @@
# ExposeOnSpawn
- **功能描述:** 使该属性在ContructObject或SpawnActor等创建对象的时候暴露出来。
- **使用位置:** UPROPERTY
- **引擎模块:** Blueprint
- **元数据类型:** bool
- **常用程度:** ★★★★★
使该属性在ContructObject或SpawnActor等创建对象的时候暴露出来。
- 具体来说通过在源码搜索这个标记在UK2Node_AddComponentUK2Node_ConstructObjectFromClassUK2Node_SpawnActorUK2Node_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 没有。
![Untitled](Untitled.png)
## 原理:
在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()))
{
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

View File

@@ -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则生成的蓝图异步节点为为下图。
![Untitled](Untitled.png)
而如果继承自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
{}
```
## 修改后的效果如下图:
![Untitled](Untitled%201.png)
## 该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;
}
}
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -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这种拥有输出参数的事件被覆写成函数后的函数体。
但无论是覆写为事件还是函数,被调用的时候用法并无区别。
![Untitled](Untitled.png)
## 原理:
判断一个函数是否是事件的逻辑为以下函数:
主要看第二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);
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

View File

@@ -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");
}
}
```

View File

@@ -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生成的蓝图节点虽然功能一致但是右边额外加了个注释以便区分。有了这个基础你也可以在其中继续重载方法进一步自定义。
![Untitled](Untitled.png)
## 当前在源码里有两处地方使用:
```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生成的版本因此最终呈现的是普通版本的静态函数蓝图节点。
![Untitled](Untitled%201.png)
## 源码里的作用机制:
可以看到如果在类上有找到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;
}) );
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

View File

@@ -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 被隐藏了。
![Untitled](Untitled.png)
## 原理:
原理比较简单就是坚持元数据标记然后设置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);
}
}
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

View File

@@ -0,0 +1,15 @@
# HideFunctions
- **功能描述:** 在属性查看器中不显示指定类别中的所有函数。
- **使用位置:** UCLASS
- **引擎模块:** Blueprint
- **元数据类型:** strings="abc"
- **关联项:**
UCLASS[HideFunctions](../../Specifier/UCLASS/Blueprint/HideFunctions/HideFunctions.md), [ShowFunctions](../../Specifier/UCLASS/Blueprint/ShowFunctions.md)
- **常用程度:** ★★★

View File

@@ -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前后对比
![Untitled](Untitled.png)
## 源码位置:
```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);
}
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

View File

@@ -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_VectorAdd_Float等。当我们在蓝图中右键输入+或Add节点的时候出现的首先是一个泛型的+节点。然后再连接到具体的变量类型蓝图系统根据Pin类型会在FTypePromotion::OperatorTable里找到最匹配的Func来最终调用或者自动的在内部做类型提升。比如下图的+最终调用的就是UKismetMathLibrary::Add_VectorFloat。这种泛型的运算符调用使得各种基本类型之间的基本运算在蓝图节点创建上更加的便利和统一也方便直接Add Pin和在Pin上直接Convert到可兼容的其他Pin类型。
![Untitled](Untitled.png)
三是为什么有些函数不想被收录进FTypePromotion里在源码中搜索在KismetMathLibrary中发现只有FDateTime加上了IgnoreTypePromotion标记。虽然FDateTime也定义了一系列的各种运算符函数比如AddSubtract和其他各种比较运算符但是FDateTime在意义上和其他的基本类型可互相运算不同FDateTime+float或FDateTime+vector并无什么意义。FDateTime只允许+FDateTime或+FTimeSpan。因此类似FDateTime这种并不想参与到其他类型的类型提升转换关系中只想安静的自成一派在自己小范围内运算就可以加上IgnoreTypePromotion不参与进FTypePromotion这个体系。
## 测试代码:
假设我们有个FGameProp结构定义了游戏里的战斗属性HPAttackDefense这些然后游戏中通常要穿装备和加Buff等等操作会计算个最终的属性。这种FGameProp结构我们就可以为之定义一系列的基本运算函数。并加上IgnoreTypePromotion因为肯定不想参与进TypePromotion与别的基本类型直接运算floatvector等这些
为了对比代码里也定义一模一样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的FGamePropAdd的时候就是直接最原始的Add_GameProp节点。而不加IgnoreTypePromotion的FGameProp2Add的时候产生的节点是泛型的+可以继续AddPin甚至在Pin上右键还会尝试寻找向其他类型的转换虽然这里结果找不到是因为我们没有定义FGameProp2和其他类型的运算函数
![Untitled](Untitled%201.png)
另外一点是如果是在一个空的泛型Add节点上右键会发现出现转换到FGameProp2的选项但是FGameProp并没有。这也是标明FGameProp2存在于TypePromotion这个体系里。但是实际上我们并不希望FGameProp2出现这里还是那句话这种玩法的战斗属性有自己的运算规则并不想掺和进基本类型的数学运算里。
![Untitled](Untitled%202.png)
## 原理:
在编辑器设置中有个选项EnableTypePromotion打开后 就会使得FTypePromotion开始收集引擎内定义的所有函数并判断其是否是个类型提升函数。
![Untitled](Untitled%203.png)
一个函数名如果前面包含运算符前缀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里面内容
![Untitled](Untitled%204.png)
一个函数如果IsPromotableFunction在调用的时候就会用UK2Node_PromotableOperator来作为蓝图节点默认是UK2Node_CallFunctionUK2Node_PromotableOperator是典型的用于Wildcard泛型的二元运算符。如下图的Add(+)。在这种Add 的引脚上右键可以弹出Pin的类型转换从Wildcard到特定的类型因为该结构有定义Add_XXX的函数并且没有IgnoreTypePromotion因此就被包含进了TypePromotion的映射表里。
上面的这个Pin转换菜单就是在UK2Node_PromotableOperator::CreateConversionMenu里收集的。

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

View File

@@ -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)
- **常用程度:** ★★★★★

View File

@@ -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)
- **常用程度:** ★★★

View File

@@ -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();
};
```
## 蓝图效果:
![Untitled](Untitled.png)
## 原理:
该Keywords的内容最终会被FEdGraphSchemaAction所应用用于蓝图内右键菜单的文本搜索。
另外每个K2Node都可以返回一个Keywords。效果应该跟函数上的Keywords一样。
```cpp
FText UEdGraphNode::GetKeywords() const
{
return GetClass()->GetMetaDataText(TEXT("Keywords"), TEXT("UObjectKeywords"), GetClass()->GetFullGroupName(false));
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View File

@@ -0,0 +1,74 @@
# KismetHideOverrides
- **功能描述:** 不允许被覆盖的蓝图事件的列表。
- **使用位置:** UCLASS
- **引擎模块:** Blueprint
- **元数据类型:** strings="abc"
在源码中发现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来达成。
![Untitled](Untitled.png)
## 原理:
可以看到这里面的判断并没有用到该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);
}
}
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -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);
};
```
## 蓝图效果:
![Untitled](Untitled.png)
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来创建蓝图异步节点的差异可以在网上别的文章查看。

View File

@@ -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;
}
```

View File

@@ -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里

View File

@@ -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;
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

View File

@@ -0,0 +1,9 @@
# NativeBreakFunc
- **功能描述:** 指定一个函数采用BreakStruct的图标。
- **使用位置:** UFUNCTION
- **元数据类型:** bool
- **关联项:** [NativeMakeFunc](NativeMakeFunc/NativeMakeFunc.md)
- **常用程度:** ★
其功能在NativeMakeFunc里已经说明

View File

@@ -0,0 +1,15 @@
# NativeConst
- **功能描述:** 指定有C++里的const标志
- **使用位置:** UPARAM
- **引擎模块:** Blueprint
- **元数据类型:** bool
- **关联项:**
UPARAM[Const](../../Specifier/UPARAM/Blueprint/Const/Const.md)
- **常用程度:** ★

View File

@@ -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函数都可以同时正常使用。
![Untitled](Untitled.png)
## 原理:
在引擎源码里唯一找到的地方是如下代码。因此该标记实际上并没有逻辑上的差别,但是在显示上会有差别。
可以看见针对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;
}
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 KiB

View File

@@ -0,0 +1,7 @@
# NotBlueprintThreadSafe
- **功能描述:** 用在函数上,标记这个函数是不线程安全的
- **使用位置:** UFUNCTION
- **元数据类型:** bool
- **关联项:** [BlueprintThreadSafe](BlueprintThreadSafe.md)
- **常用程度:** ★

View File

@@ -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 可以编辑默认值。
![Untitled](Untitled.png)
## 原理:
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;
});
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

View File

@@ -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
{}
```
## 相应效果:
在统计页面,可见右上角的类型。
![Untitled](Untitled.png)
## 原理:
```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;
}
};
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 KiB

View File

@@ -0,0 +1,46 @@
# ArrayParm
- **功能描述:** 指定一个函数为使用Array<*>的函数,数组元素类型为通配符的泛型。
- **使用位置:** UFUNCTION
- **引擎模块:** Blueprint
- **元数据类型:** strings="abc"
- **关联项:** [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);
};
```
## 蓝图效果:
![Untitled](Untitled.png)
可以看到在没有连接具体数组类型的时候Array是灰色的通配符类型。而连接上不同的数组类型Array参数引脚就会自动变成相应的类型这些逻辑是在UK2Node_CallArrayFunction中实现的有兴趣的去自行翻阅。

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

View File

@@ -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的类型依然没有确定即使连接上了也是如此这应该是引擎的实现所限制。编译会造成编译错误。
![Untitled](Untitled.png)
因此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的类型。

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View File

@@ -0,0 +1,134 @@
# AutoCreateRefTerm
- **功能描述:** 指定函数的多个输入引用参数在没有连接的时候自动为其创建默认值
- **使用位置:** UFUNCTION
- **引擎模块:** Blueprint
- **元数据类型:** strings="abc"
- **常用程度:** ★★★★★
指定函数的多个输入引用参数在没有连接的时候自动为其创建默认值。
要注意“输入”+“引用”,这两个前提项。
当有些情况你想把函数的参数采用引用来传递,但是又不想每次都得必须连接一个变量,想在不连接的时候提供一个内联编辑的默认值,因此蓝图系统就提供了这么一个便利功能。
## 测试代码:
```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的函数会产生编译的报错因为是引用参数但是却没有连接导致引用缺少实参。
![Untitled](Untitled.png)
## 原理代码:
```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);
}
}
}
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

View File

@@ -0,0 +1,122 @@
# CustomStructureParam
- **功能描述:** 被CustomStructureParam标记的函数参数会变成Wildcard的通配符参数其引脚的类型会等于连接的变量类型。
- **使用位置:** UFUNCTION
- **引擎模块:** Blueprint
- **元数据类型:** strings="abc"
- **常用程度:** ★★★★★
被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;
}
```
## 蓝图中的效果:
![Untitled](Untitled.png)
可以看到定义了一个接受通用结构参数的节点然后打印出内部所有的属性。其中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;
}
}
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

View File

@@ -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*>
![pic_a](pic_a.png)
也可以加上DynamicOutputParam来指定输出参数作为动态类型参数
![pic_b](pic_b.png)
DynamicOutputParam可以指定多个参数
![pic_c](pic_c.png)
DeterminesOutputType 的参数类型也可以是Object或者Object的容器
![pic_d](pic_d.png)
DeterminesOutputType 的参数甚至可以是结构里的某个属性但是只有SplitStruct的时候才生效因为这个时候结构的属性变量才变成函数的Pin才可以进行DeterminesOutputType的名称比对。这个时候要书写成“A_B”而不是“A.B”。
![pic_e](pic_e.png)
## 原理:
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;//设定每个动态参数的子类型
}
}
}
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Some files were not shown because too many files have changed in this diff Show More