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,139 @@
# ChildCanTick
- **功能描述:** 标记允许其蓝图子类可以接受响应Tick事件
- **使用位置:** UCLASS
- **引擎模块:** Actor
- **元数据类型:** bool
- **限制类型:** Actor或ActorComponent子类
- **关联项:** [ChildCannotTick](../ChildCannotTick.md)
- **常用程度:** ★★★
要在蓝图中重载Tick事件函数并只会在编译的时候触发判断。
```cpp
//(BlueprintType = true, ChildCannotTick = , IncludePath = Class/Blueprint/MyActor_ChildTick.h, IsBlueprintBase = true, ModuleRelativePath = Class/Blueprint/MyActor_ChildTick.h)
UCLASS(Blueprintable,meta=(ChildCanTick))
class INSIDER_API AMyActor_ChildCanTick : public AActor
{
GENERATED_BODY()
public:
AMyActor_ChildCanTick()
{
PrimaryActorTick.bCanEverTick = false;
}
};
//(BlueprintType = true, ChildCanTick = , IncludePath = Class/Blueprint/MyActor_ChildTick.h, IsBlueprintBase = true, ModuleRelativePath = Class/Blueprint/MyActor_ChildTick.h)
UCLASS(Blueprintable,meta=(ChildCanTick))
class INSIDER_API UMyActorComponent_ChildCanTick : public UActorComponent
{
GENERATED_BODY()
public:
};
//(BlueprintType = true, ChildCannotTick = , IncludePath = Class/Blueprint/MyActor_ChildTick.h, IsBlueprintBase = true, ModuleRelativePath = Class/Blueprint/MyActor_ChildTick.h)
UCLASS(Blueprintable,meta=(ChildCannotTick))
class INSIDER_API AMyActor_ChildCannotTick : public AActor
{
GENERATED_BODY()
public:
};
//(BlueprintType = true, ChildCannotTick = , IncludePath = Class/Blueprint/MyActor_ChildTick.h, IsBlueprintBase = true, ModuleRelativePath = Class/Blueprint/MyActor_ChildTick.h)
UCLASS(Blueprintable,meta=(ChildCannotTick))
class INSIDER_API UMyActorComponent_ChildCannotTick : public UActorComponent
{
GENERATED_BODY()
public:
};
```
蓝图Actor或ActorComponent里测试
也注意到这个判断跟蓝图中是否开启Tick并没有关系。
![Untitled](Untitled.png)
![Untitled](Untitled%201.png)
而AMyActor_ChildCanTick类里虽然已经手动关闭了PrimaryActorTick.bCanEverTick但是在子类里依然可以正常的Tick在编译的时候内部可以正常的再重新开启bCanEverTick
![Untitled](Untitled%202.png)
## 源码里判断的逻辑:
开启bCanEverTick=true的条件有3一是EngineSettings->bCanBlueprintsTickByDefault二是父类是AActor或UActorComponent本身三是C++基类上有ChildCanTick的标记。
```cpp
void FKismetCompilerContext::SetCanEverTick() const
{
// RECEIVE TICK
if (!TickFunction->bCanEverTick)
{
// Make sure that both AActor and UActorComponent have the same name for their tick method
static FName ReceiveTickName(GET_FUNCTION_NAME_CHECKED(AActor, ReceiveTick));
static FName ComponentReceiveTickName(GET_FUNCTION_NAME_CHECKED(UActorComponent, ReceiveTick));
if (const UFunction* ReceiveTickEvent = FKismetCompilerUtilities::FindOverriddenImplementableEvent(ReceiveTickName, NewClass))
{
// We have a tick node, but are we allowed to?
const UEngine* EngineSettings = GetDefault<UEngine>();
const bool bAllowTickingByDefault = EngineSettings->bCanBlueprintsTickByDefault;
const UClass* FirstNativeClass = FBlueprintEditorUtils::FindFirstNativeClass(NewClass);
const bool bHasCanTickMetadata = (FirstNativeClass != nullptr) && FirstNativeClass->HasMetaData(FBlueprintMetadata::MD_ChildCanTick);
const bool bHasCannotTickMetadata = (FirstNativeClass != nullptr) && FirstNativeClass->HasMetaData(FBlueprintMetadata::MD_ChildCannotTick);
const bool bHasUniversalParent = (FirstNativeClass != nullptr) && ((AActor::StaticClass() == FirstNativeClass) || (UActorComponent::StaticClass() == FirstNativeClass));
if (bHasCanTickMetadata && bHasCannotTickMetadata)
{
// User error: The C++ class has conflicting metadata
const FString ConlictingMetadataWarning = FText::Format(
LOCTEXT("HasBothCanAndCannotMetadataFmt", "Native class %s has both '{0}' and '{1}' metadata specified, they are mutually exclusive and '{1}' will win."),
FText::FromString(FirstNativeClass->GetPathName()),
FText::FromName(FBlueprintMetadata::MD_ChildCanTick),
FText::FromName(FBlueprintMetadata::MD_ChildCannotTick)
).ToString();
MessageLog.Warning(*ConlictingMetadataWarning);
}
if (bHasCannotTickMetadata)
{
// This could only happen if someone adds bad metadata to AActor or UActorComponent directly
check(!bHasUniversalParent);
// Parent class has forbidden us to tick
const FString NativeClassSaidNo = FText::Format(
LOCTEXT("NativeClassProhibitsTickingFmt", "@@ is not allowed as the C++ parent class {0} has disallowed Blueprint subclasses from ticking. Please consider using a Timer instead of Tick."),
FText::FromString(FirstNativeClass->GetPathName())
).ToString();
MessageLog.Warning(*NativeClassSaidNo, FindLocalEntryPoint(ReceiveTickEvent));
}
else
{
if (bAllowTickingByDefault || bHasUniversalParent || bHasCanTickMetadata)
{
// We're allowed to tick for one reason or another
TickFunction->bCanEverTick = true;
}
else
{
// Nothing allowing us to tick
const FString ReceiveTickEventWarning = FText::Format(
LOCTEXT("ReceiveTick_CanNeverTickFmt", "@@ is not allowed for Blueprints based on the C++ parent class {0}, so it will never Tick!"),
FText::FromString(FirstNativeClass ? *FirstNativeClass->GetPathName() : TEXT("<null>"))
).ToString();
MessageLog.Warning(*ReceiveTickEventWarning, FindLocalEntryPoint(ReceiveTickEvent));
const FString ReceiveTickEventRemedies = FText::Format(
LOCTEXT("ReceiveTick_CanNeverTickRemediesFmt", "You can solve this in several ways:\n 1) Consider using a Timer instead of Tick.\n 2) Add meta=({0}) to the parent C++ class\n 3) Reparent the Blueprint to AActor or UActorComponent, which can always tick."),
FText::FromName(FBlueprintMetadata::MD_ChildCanTick)
).ToString();
MessageLog.Warning(*ReceiveTickEventRemedies);
}
}
}
}
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 438 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

View File

@@ -0,0 +1,8 @@
# ChildCannotTick
- **功能描述:** 用于Actor或ActorComponent子类标记允许其蓝图子类不可以接受响应Tick事件哪怕父类可以Tick
- **使用位置:** UCLASS
- **元数据类型:** bool
- **限制类型:** Actor类
- **关联项:** [ChildCanTick](ChildCanTick/ChildCanTick.md)
- **常用程度:** ★★★

View File

@@ -0,0 +1,17 @@
# AllowedParamType
- **使用位置:** UFUNCTION
- **引擎模块:** AnimationGraph
- **元数据类型:** string="abc"
```cpp
// Sets a parameter's value in the supplied scope.
// @param Scope Scopes corresponding to an existing scope in a schedule, or "None". Passing "None" will apply the parameter to the whole schedule.
// @param Ordering Where to apply the parameter in relation to the supplied scope. Ignored for scope "None".
// @param Name The name of the parameter to apply
// @param Value The value to set the parameter to
UFUNCTION(BlueprintCallable, Category = "AnimNext", CustomThunk, meta = (CustomStructureParam = Value, UnsafeDuringActorConstruction))
void SetParameterInScope(UPARAM(meta = (CustomWidget = "ParamName", AllowedParamType = "FAnimNextScope")) FName Scope, EAnimNextParameterScopeOrdering Ordering, UPARAM(meta = (CustomWidget = "ParamName")) FName Name, int32 Value);
```
查了一下只在AnimNext中用到。

View File

@@ -0,0 +1,65 @@
# AlwaysAsPin
- **功能描述:** 在动画蓝图中使得动画节点的某个属性总是暴露出来成为引脚
- **使用位置:** UPROPERTY
- **引擎模块:** Pin
- **元数据类型:** bool
- **限制类型:** FAnimNode_Base
- **关联项:** [PinShownByDefault](../PinShownByDefault/PinShownByDefault.md)
- **常用程度:** ★★★
和PinShownByDefault的区别是前者会导致只能一直显示为引脚。而PinShownByDefault默认显示为引脚当也之后也可以改变。
## 测试代码:
```cpp
USTRUCT(BlueprintInternalUseOnly)
struct INSIDEREDITOR_API FAnimNode_MyTestPinShown : public FAnimNode_Base
{
GENERATED_USTRUCT_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = PinShownByDefaultTest)
int32 MyInt_NotShown = 123;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = PinShownByDefaultTest, meta = (PinShownByDefault))
int32 MyInt_PinShownByDefault = 123;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = PinShownByDefaultTest, meta = (AlwaysAsPin))
int32 MyInt_AlwaysAsPin = 123;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = PinShownByDefaultTest, meta = (NeverAsPin))
int32 MyInt_NeverAsPin = 123;
};
```
## 测试效果:
![PinShown](PinShown.gif)
## 原理:
根据源码的里的逻辑可见bAlwaysShow 会导致bShowPin和PinShownByDefault的区别是前者会导致只能一直显示为引脚。而PinShownByDefault默认显示为引脚当也之后也可以改变。
```cpp
void FAnimBlueprintNodeOptionalPinManager::GetRecordDefaults(FProperty* TestProperty, FOptionalPinFromProperty& Record) const
{
const UAnimationGraphSchema* Schema = GetDefault<UAnimationGraphSchema>();
// Determine if this is a pose or array of poses
FArrayProperty* ArrayProp = CastField<FArrayProperty>(TestProperty);
FStructProperty* StructProp = CastField<FStructProperty>(ArrayProp ? ArrayProp->Inner : TestProperty);
const bool bIsPoseInput = (StructProp && StructProp->Struct->IsChildOf(FPoseLinkBase::StaticStruct()));
//@TODO: Error if they specified two or more of these flags
const bool bAlwaysShow = TestProperty->HasMetaData(Schema->NAME_AlwaysAsPin) || bIsPoseInput;
const bool bOptional_ShowByDefault = TestProperty->HasMetaData(Schema->NAME_PinShownByDefault);
const bool bOptional_HideByDefault = TestProperty->HasMetaData(Schema->NAME_PinHiddenByDefault);
const bool bNeverShow = TestProperty->HasMetaData(Schema->NAME_NeverAsPin);
const bool bPropertyIsCustomized = TestProperty->HasMetaData(Schema->NAME_CustomizeProperty);
const bool bCanTreatPropertyAsOptional = CanTreatPropertyAsOptional(TestProperty);
Record.bCanToggleVisibility = bCanTreatPropertyAsOptional && (bOptional_ShowByDefault || bOptional_HideByDefault);
Record.bShowPin = bAlwaysShow || bOptional_ShowByDefault;
Record.bPropertyIsCustomized = bPropertyIsCustomized;
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

View File

@@ -0,0 +1,9 @@
# AnimBlueprintFunction
- **功能描述:** 标明是动画蓝图里的内部纯存根函数,只在动画蓝图编译时设置
- **使用位置:** UFUNCTION
- **引擎模块:** AnimationGraph
- **元数据类型:** bool
- **限制类型:** Anim BP
只是在内部使用,在动画蓝图编译的时候设置。但是没有在代码里显式的编写。

View File

@@ -0,0 +1,78 @@
# AnimGetter
- **功能描述:** 指定UAnimInstance及子类的该函数成为一个AnimGetter函数。
- **使用位置:** UFUNCTION
- **引擎模块:** AnimationGraph
- **元数据类型:** bool
- **限制类型:** UAnimInstance及子类的函数
- **关联项:** [GetterContext](../GetterContext/GetterContext.md)
- **常用程度:** ★★★
指定UAnimInstance及子类的该函数成为一个AnimGetter函数。
- 在一些情况下会继承UAnimInstance创建自己的动画蓝图子类然后里面可以自己做一些优化或者添加一些自己的功能函数。
- 所谓的AnimGetter其实就是会被UK2Node_AnimGetter识别并包装成该蓝图节点的函数。识别的范围是在UAnimInstance及子类就是动画蓝图的C++函数。
- AnimGetter还有两个额外功能一是会自动根据当前上下文填充函数里的AssetPlayerIndexMachineIndexStateIndexTransitionIndex和参数。二是会根据GetterContext把该函数限定只能在某些蓝图里调用。普通的蓝图函数不具有这些便利的功能和检查用起来就不够智能。
- 要成为AnimGetter还必须具有
- AnimGetter自然不必说
- BlueprintThreadSafe才能在动画蓝图里调用多线程安全
- BlueprintPure成为一个存获取值的函数
- BlueprintInternalUseOnly = "true”避免再生成一个默认的蓝图节点只用UK2Node_AnimGetter包装而成的那个。
## 测试代码:
```cpp
UCLASS(BlueprintType)
class INSIDER_API UMyAnimInstance :public UAnimInstance
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintPure, Category = "Animation|Insider", meta = (BlueprintInternalUseOnly = "true", AnimGetter, BlueprintThreadSafe))
float MyGetAnimationLength_AnimGetter(int32 AssetPlayerIndex);
UFUNCTION(BlueprintPure, Category = "Animation|Insider", meta = (BlueprintThreadSafe))
float MyGetAnimationLength(int32 AssetPlayerIndex);
public:
UFUNCTION(BlueprintPure, Category = "Animation|Insider", meta = (BlueprintInternalUseOnly = "true", AnimGetter, BlueprintThreadSafe))
float MyGetStateWeight_AnimGetter(int32 MachineIndex, int32 StateIndex);
UFUNCTION(BlueprintPure, Category = "Animation|Insider", meta = (BlueprintThreadSafe))
float MyGetStateWeight(int32 MachineIndex, int32 StateIndex);
public:
UFUNCTION(BlueprintPure, Category = "Animation|Insider", meta = (BlueprintInternalUseOnly = "true", AnimGetter, BlueprintThreadSafe))
float MyGetTransitionTimeElapsed_AnimGetter(int32 MachineIndex, int32 TransitionIndex);
UFUNCTION(BlueprintPure, Category = "Animation|Insider", meta = (BlueprintThreadSafe))
float MyGetTransitionTimeElapsed(int32 MachineIndex, int32 TransitionIndex);
};
```
## 测试效果:
分别定义使用了AssetPlayerIndexMachineIndexStateIndexTransitionIndex的AnimGetter函数以及普通蓝图函数作为对比。分别查看在动画蓝图里几个作用域里的用法。
- 可见在不管什么作用域普通蓝图函数都可以调用毕竟没有做Context的检查。另外AssetPlayerIndex等参数都没有被自动填充这几乎是没法用的因为用户其实并不太懂如何去手填这些Index最好是交给编译器来填充。
- 图里高亮的是可以调用的AnimGetter函数。细看的话可以分析发现规则是只有能正确填充AssetPlayerIndex等参数的才能调用。因此在Transition里能调用的最多因为这个时候最叶子节点有动画又有状态机和Transition节点。
![Untitled](Untitled.png)
## 原理:
分析函数上的AnimGetter标记并且生成蓝图节点的功能基本都在UK2Node_AnimGetter这个类里。大家可自行查看。
```cpp
void UK2Node_AnimGetter::GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const
{
TArray<UFunction*> AnimGetters;
for(TFieldIterator<UFunction> FuncIter(BPClass) ; FuncIter ; ++FuncIter)
{
UFunction* Func = *FuncIter;
if(Func->HasMetaData(TEXT("AnimGetter")) && Func->HasAnyFunctionFlags(FUNC_Native))
{
AnimGetters.Add(Func);
}
}
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 620 KiB

View File

@@ -0,0 +1,89 @@
# AnimNotifyBoneName
- **功能描述:** 使得UAnimNotify或UAnimNotifyState下的FName属性作为BoneName的作用。
- **使用位置:** UPROPERTY
- **引擎模块:** AnimationGraph
- **元数据类型:** bool
- **限制类型:** UAnimNotify或UAnimNotifyState子类下的FName属性
- **常用程度:** ★★
使得UAnimNotify或UAnimNotifyState下的FName属性作为BoneName的作用。
在动画通知的时候也常常需要一个传递骨骼名字参数用普通的字符串参数显然不够定制化。因此给一个FName属性标上AnimNotifyBoneName就可以在配合的细节面板定制化里为它创建专门的更便于填写BoneName的UI。
## 源码中例子:
```cpp
UCLASS(const, hidecategories = Object, collapsecategories, meta = (DisplayName = "Play Niagara Particle Effect"), MinimalAPI)
class UAnimNotify_PlayNiagaraEffect : public UAnimNotify
{
// SocketName to attach to
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AnimNotify", meta = (AnimNotifyBoneName = "true"))
FName SocketName;
}
UCLASS(Blueprintable, meta = (DisplayName = "Timed Niagara Effect"), MinimalAPI)
class UAnimNotifyState_TimedNiagaraEffect : public UAnimNotifyState
{
// The socket within our mesh component to attach to when we spawn the Niagara component
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = NiagaraSystem, meta = (ToolTip = "The socket or bone to attach the system to", AnimNotifyBoneName = "true"))
FName SocketName;
}
```
## 测试代码:
```cpp
UCLASS(BlueprintType)
class INSIDER_API UAnimNotify_MyTest:public UAnimNotify
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FName MyName;
UPROPERTY(EditAnywhere, BlueprintReadWrite,meta=(AnimNotifyBoneName="true"))
FName MyName_Bone;
};
UCLASS(BlueprintType)
class INSIDER_API UAnimNotifyState_MyTest:public UAnimNotifyState
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FName MyName;
UPROPERTY(EditAnywhere, BlueprintReadWrite,meta=(AnimNotifyBoneName="true"))
FName MyName_Bone;
};
```
## 测试效果:
在一个动画序列里加上动画通知可以加两种UAnimNotify或UAnimNotifyState。首先引擎里的自带例子UAnimNotify_PlayNiagaraEffect 和UAnimNotifyState_TimedNiagaraEffect 可以看见在右侧的细节面板上的SocketName的UI不是普通的字符串。
我们自己定义的MyBoneName的动画通知也可以达成同样的效果。MyName_Bone因为加了AnimNotifyBoneName就和普通的MyName不一样了。
![Untitled](Untitled.png)
## 原理:
在定制化的时候根据AnimNotify下的属性是否有这个标记生成专门的的UI。
```cpp
bool FAnimNotifyDetails::CustomizeProperty(IDetailCategoryBuilder& CategoryBuilder, UObject* Notify, TSharedPtr<IPropertyHandle> Property)
{
else if (InPropertyHandle->GetBoolMetaData(TEXT("AnimNotifyBoneName")))
{
// Convert this property to a bone name property
AddBoneNameProperty(CategoryBuilder, Notify, InPropertyHandle);
}
if (bIsBoneName)
{
AddBoneNameProperty(CategoryBuilder, Notify, Property);
return true;
}
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

View File

@@ -0,0 +1,73 @@
# AnimNotifyExpand
- **功能描述:** 使得UAnimNotify或UAnimNotifyState下的属性直接展开到细节面板里。
- **使用位置:** UPROPERTY
- **引擎模块:** AnimationGraph
- **元数据类型:** bool
- **限制类型:** UAnimNotify或UAnimNotifyState子类下的FName属性
使得UAnimNotify或UAnimNotifyState下的属性直接展开到细节面板里。
在源码里也没有找到应用的例子。
## 原理:
看源码,里面写死了只对有限的引擎自带的几个类起效,因此自己的测试代码并不能生效。
这种写法确实不应该这么写死,希望以后改进吧。到时应该就有源码里的例子了。
```cpp
PropertyModule.RegisterCustomClassLayout( "EditorNotifyObject", FOnGetDetailCustomizationInstance::CreateStatic(&FAnimNotifyDetails::MakeInstance));
bool FAnimNotifyDetails::CustomizeProperty(IDetailCategoryBuilder& CategoryBuilder, UObject* Notify, TSharedPtr<IPropertyHandle> Property)
{
if(Notify && Notify->GetClass() && Property->IsValidHandle())
{
FString ClassName = Notify->GetClass()->GetName();
FString PropertyName = Property->GetProperty()->GetName();
bool bIsBoneName = Property->GetBoolMetaData(TEXT("AnimNotifyBoneName"));
if(ClassName.Find(TEXT("AnimNotify_PlayParticleEffect")) != INDEX_NONE && PropertyName == TEXT("SocketName"))
{
AddBoneNameProperty(CategoryBuilder, Notify, Property);
return true;
}
else if(ClassName.Find(TEXT("AnimNotifyState_TimedParticleEffect")) != INDEX_NONE && PropertyName == TEXT("SocketName"))
{
AddBoneNameProperty(CategoryBuilder, Notify, Property);
return true;
}
else if(ClassName.Find(TEXT("AnimNotify_PlaySound")) != INDEX_NONE && PropertyName == TEXT("AttachName"))
{
AddBoneNameProperty(CategoryBuilder, Notify, Property);
return true;
}
else if (ClassName.Find(TEXT("_SoundLibrary")) != INDEX_NONE && PropertyName == TEXT("SoundContext"))
{
CategoryBuilder.AddProperty(Property);
FixBoneNamePropertyRecurse(Property);
return true;
}
else if (ClassName.Find(TEXT("AnimNotifyState_Trail")) != INDEX_NONE)
{
if(PropertyName == TEXT("FirstSocketName") || PropertyName == TEXT("SecondSocketName"))
{
AddBoneNameProperty(CategoryBuilder, Notify, Property);
return true;
}
else if(PropertyName == TEXT("WidthScaleCurve"))
{
AddCurveNameProperty(CategoryBuilder, Notify, Property);
return true;
}
}
else if (bIsBoneName)
{
AddBoneNameProperty(CategoryBuilder, Notify, Property);
return true;
}
}
}
```

View File

@@ -0,0 +1,91 @@
# BlueprintCompilerGeneratedDefaults
- **功能描述:** 指定该属性的值是编译器生成的,因此在编译后无需复制,可以加速一些编译性能。
- **使用位置:** UPROPERTY
- **引擎模块:** AnimationGraph
- **元数据类型:** bool
- **限制类型:** FAnimNode里的属性
指定该属性的值是编译器生成的,因此在编译后无需复制,可以加速一些编译性能。
在源码里寻找例子可以看到基本是在FAnimNode下的属性在使用。在动画蓝图编译后会调用UEngine::CopyPropertiesForUnrelatedObjects来把之前编译的旧对象里的值复制到新对象其中FCopyPropertiesForUnrelatedObjectsParams的bSkipCompilerGeneratedDefaults决定是否要赋值这个属性的值。如果有标上这个值就说明不要复制。这个值会在别的地方由编译器来填充值。
UAnimGraphNode_Base::OnProcessDuringCompilation函数就是编译后回调的函数。
## 测试代码:
```cpp
USTRUCT(BlueprintInternalUseOnly)
struct INSIDER_API FAnimNode_MyCompilerDefaults : public FAnimNode_Base
{
GENERATED_USTRUCT_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Links)
FPoseLink Source;
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = CompilerDefaultsTest)
FString MyString;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = CompilerDefaultsTest, meta = (BlueprintCompilerGeneratedDefaults))
FString MyString_CompilerDefaults;
};
UCLASS()
class INSIDEREDITOR_API UAnimGraphNode_MyCompilerDefaults : public UAnimGraphNode_Base
{
GENERATED_UCLASS_BODY()
public:
~UAnimGraphNode_MyCompilerDefaults();
UPROPERTY(EditAnywhere, Category = Settings)
FAnimNode_MyCompilerDefaults Node;
protected:
virtual void OnProcessDuringCompilation(IAnimBlueprintCompilationContext& InCompilationContext, IAnimBlueprintGeneratedClassCompiledData& OutCompiledData)
{
Node.MyString=TEXT("This is generated by compiler.");
Node.MyString_CompilerDefaults=TEXT("This is generated by compiler.");
}
};
```
## 测试效果:
这个因为是序列化的过程,因此并没有直观的示意图。
可验证的结果是在FCPFUOWriter::ShouldSkipProperty可以见到MyString_CompilerDefaults属性跳过了复制。
## 原理:
蓝图编译的过程核心思想是生成一个新的Graph对象然后把上一次编译的结果对象里的属性和只对象复制到这个新的对象里去。这一步操作是用UEngine::CopyPropertiesForUnrelatedObjects来完成的再内部会继续用FCPFUOWriter::ShouldSkipProperty来判断是否该复制某个属性。而对于一些属性的值只是由编译器生成的临时值反正下一次编译也会再生成因此就不需要复制了标上之后可以略微加速一些性能虽然其实也不多。
```cpp
void UK2Node_PropertyAccess::CreateClassVariablesFromBlueprint(IAnimBlueprintVariableCreationContext& InCreationContext)
{
GeneratedPropertyName = NAME_None;
const bool bRequiresCachedVariable = !bWasResolvedThreadSafe || UAnimBlueprintExtension_PropertyAccess::ContextRequiresCachedVariable(ContextId);
if(ResolvedPinType != FEdGraphPinType() && ResolvedPinType.PinCategory != UEdGraphSchema_K2::PC_Wildcard && bRequiresCachedVariable)
{
// Create internal generated destination property (only if we were not identified as thread safe)
if(FProperty* DestProperty = InCreationContext.CreateUniqueVariable(this, ResolvedPinType))
{
GeneratedPropertyName = DestProperty->GetFName();
DestProperty->SetMetaData(TEXT("BlueprintCompilerGeneratedDefaults"), TEXT("true"));
}
}
}
/* Serializes and stores property data from a specified 'source' object. Only stores data compatible with a target destination object. */
struct FCPFUOWriter : public FObjectWriter, public FCPFUOArchive
{
#if WITH_EDITOR
virtual bool ShouldSkipProperty(const class FProperty* InProperty) const override
{
return (bSkipCompilerGeneratedDefaults && InProperty->HasMetaData(BlueprintCompilerGeneratedDefaultsName));
}
#endif
}
```

View File

@@ -0,0 +1,18 @@
# CustomWidget
- **使用位置:** UFUNCTION, UPROPERTY
- **引擎模块:** AnimationGraph
- **元数据类型:** string="abc"
也可以放在属性上
```cpp
// @param Scope Scopes corresponding to an existing scope in a schedule, or "None". Passing "None" will apply the parameter to the whole schedule.
// @param Ordering Where to apply the parameter in relation to the supplied scope. Ignored for scope "None".
// @param Name The name of the parameter to apply
// @param Value The value to set the parameter to
UFUNCTION(BlueprintCallable, Category = "AnimNext", CustomThunk, meta = (CustomStructureParam = Value, UnsafeDuringActorConstruction))
void SetParameterInScope(UPARAM(meta = (CustomWidget = "ParamName", AllowedParamType = "FAnimNextScope")) FName Scope, EAnimNextParameterScopeOrdering Ordering, UPARAM(meta = (CustomWidget = "ParamName")) FName Name, int32 Value);
```
只在AnimNext和RigVM里用到。

View File

@@ -0,0 +1,144 @@
# CustomizeProperty
- **功能描述:** 使用在FAnimNode的成员属性上告诉编辑器不要为它生成默认Details面板控件后续会在DetailsCustomization里自定义创建相应的编辑控件。
- **使用位置:** UPROPERTY
- **引擎模块:** AnimationGraph
- **元数据类型:** bool
- **限制类型:** FAnimNode里的属性
- **常用程度:** ★
使用在FAnimNode的成员属性上告诉编辑器不要为它生成默认Details面板控件后续会在DetailsCustomization里自定义创建相应的编辑控件。
和AllowEditInlineCustomization的作用有点像都只是做个标记提示编辑器会在别的地方进行自定义不用为它生成默认Details面板控件。
## 源码中例子:
在源码里能见到挺多例子常见的就是在AnimBP中的节点上的属性其在细节面板需要专门的定制化编辑。最常见的例子是Slot这个节点其SlotName只是个FString类型但是在细节面板里显示的却是个ComboString。这是因为它标上了CustomizeProperty告知默认的动画节点细节面板生成器*FAnimGraphNodeDetails先不要为这个属性创建编辑控件之后会在自己的定制化FAnimGraphNodeSlotDetails里为SlotName再创建自定义UI。
```cpp
struct FAnimNode_Slot : public FAnimNode_Base
{
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Settings, meta=(CustomizeProperty))
FName SlotName;
}
void FPersonaModule::CustomizeBlueprintEditorDetails(const TSharedRef<class IDetailsView>& InDetailsView, FOnInvokeTab InOnInvokeTab)
{
InDetailsView->RegisterInstancedCustomPropertyLayout(UAnimGraphNode_Slot::StaticClass(),
FOnGetDetailCustomizationInstance::CreateStatic(&FAnimGraphNodeSlotDetails::MakeInstance, InOnInvokeTab));
InDetailsView->SetExtensionHandler(MakeShared<FAnimGraphNodeBindingExtension>());
}
```
## 测试代码:
```cpp
USTRUCT(BlueprintInternalUseOnly)
struct INSIDER_API FAnimNode_MyCustomProperty : public FAnimNode_Base
{
GENERATED_USTRUCT_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = CustomProperty)
FString MyString_Default;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = CustomProperty, meta = (CustomizeProperty))
FString MyString_CustomizeProperty;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = CustomProperty, meta = (CustomizeProperty))
FString MyString_CustomizeProperty_Other;
};
UCLASS()
class INSIDEREDITOR_API UAnimGraphNode_MyCustomProperty : public UAnimGraphNode_Base
{
GENERATED_UCLASS_BODY()
UPROPERTY(EditAnywhere, Category = Settings)
FAnimNode_MyCustomProperty Node;
};
//再创建一个定制化生成自定义UI
void FMyAnimNode_MyCustomPropertyCustomization::CustomizeDetails(class IDetailLayoutBuilder& DetailBuilder)
{
TSharedPtr<IPropertyHandle> PropertyHandle = DetailBuilder.GetProperty(TEXT("Node.MyString_CustomProperty"));
//Just for test
ComboListItems.Empty();
ComboListItems.Add(MakeShareable(new FString(TEXT("First"))));
ComboListItems.Add(MakeShareable(new FString(TEXT("Second"))));
ComboListItems.Add(MakeShareable(new FString(TEXT("Third"))));
const TSharedPtr<FString> ComboBoxSelectedItem = ComboListItems[0];
IDetailCategoryBuilder& Group = DetailBuilder.EditCategory(TEXT("CustomProperty"));
Group.AddCustomRow(INVTEXT("CustomProperty"))
.NameContent()
[
PropertyHandle->CreatePropertyNameWidget()
]
.ValueContent()
[
SNew(STextComboBox)
.OptionsSource(&ComboListItems)
.InitiallySelectedItem(ComboBoxSelectedItem)
.ContentPadding(2.f)
.ToolTipText(FText::FromString(*ComboBoxSelectedItem))
];
}
//注册定制化
PropertyModule.RegisterCustomClassLayout(TEXT("AnimGraphNode_MyCustomProperty"), FOnGetDetailCustomizationInstance::CreateStatic(&FMyAnimNode_MyCustomPropertyCustomization::MakeInstance));
```
## 测试效果:
SlotName的效果如右侧所示。
我们自己模仿的例子可见MyString_Default依然只是个默认String而MyString_CustomizeProperty则为它创建了自定义UI。
作为对比MyString_CustomizeProperty_Other我们标上了CustomizeProperty但是没有为它创建UI则没有显示出来说明引擎默认的机制因此就把它的UI默认创建流程给跳过了。
![Untitled](Untitled.png)
## 原理:
CustomizeProperty其实会会改变Pin的bPropertyIsCustomized 属性GetRecordDefaults中体现然后在创建流程的过程中不创建默认的widget这个可见CustomizeDetails中的bPropertyIsCustomized判断得知。
```cpp
void FAnimBlueprintNodeOptionalPinManager::GetRecordDefaults(FProperty* TestProperty, FOptionalPinFromProperty& Record) const
{
const UAnimationGraphSchema* Schema = GetDefault<UAnimationGraphSchema>();
// Determine if this is a pose or array of poses
FArrayProperty* ArrayProp = CastField<FArrayProperty>(TestProperty);
FStructProperty* StructProp = CastField<FStructProperty>(ArrayProp ? ArrayProp->Inner : TestProperty);
const bool bIsPoseInput = (StructProp && StructProp->Struct->IsChildOf(FPoseLinkBase::StaticStruct()));
//@TODO: Error if they specified two or more of these flags
const bool bAlwaysShow = TestProperty->HasMetaData(Schema->NAME_AlwaysAsPin) || bIsPoseInput;
const bool bOptional_ShowByDefault = TestProperty->HasMetaData(Schema->NAME_PinShownByDefault);
const bool bOptional_HideByDefault = TestProperty->HasMetaData(Schema->NAME_PinHiddenByDefault);
const bool bNeverShow = TestProperty->HasMetaData(Schema->NAME_NeverAsPin);
const bool bPropertyIsCustomized = TestProperty->HasMetaData(Schema->NAME_CustomizeProperty);
const bool bCanTreatPropertyAsOptional = CanTreatPropertyAsOptional(TestProperty);
Record.bCanToggleVisibility = bCanTreatPropertyAsOptional && (bOptional_ShowByDefault || bOptional_HideByDefault);
Record.bShowPin = bAlwaysShow || bOptional_ShowByDefault;
Record.bPropertyIsCustomized = bPropertyIsCustomized;
}
//这个是在AnimBP中选中一个节点然后在右侧细节面板中的属性
void FAnimGraphNodeDetails::CustomizeDetails(class IDetailLayoutBuilder& DetailBuilder)
{
// sometimes because of order of customization
// this gets called first for the node you'd like to customize
// then the above statement won't work
// so you can mark certain property to have meta data "CustomizeProperty"
// which will trigger below statement
if (OptionalPin.bPropertyIsCustomized)
{
continue;
}
TSharedRef<SWidget> InternalCustomWidget = CreatePropertyWidget(TargetProperty, TargetPropertyHandle.ToSharedRef(), AnimGraphNode->GetClass());
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 KiB

View File

@@ -0,0 +1,70 @@
# FoldProperty
- **功能描述:** 在动画蓝图中使得动画节点的某个属性成为FoldProperty。
- **使用位置:** UPROPERTY
- **引擎模块:** AnimationGraph
- **元数据类型:** bool
- **限制类型:** FAnimNode_Base下的属性
- **常用程度:** ★
在动画蓝图中使得动画节点的某个属性成为FoldProperty。
- 在UI表现上PinHiddenByDefault也有同样的效果但是FoldProperty在行为上有别的不同。
- 所谓FoldProperty指的是这些属性往往使被WITH_EDITORONLY_DATA包起来的。记录编辑器状况下的信息。比如FAnimNode_SequencePlayer下的PlayRate数据其就是在编辑器状态的下数据。又或者只是动画蓝图本身的信息不管动画蓝图的多少个实例这些属性的值其实都是同样的。这些属性就适合成为FoldProperty。
- 这些属性需要在节点上编辑但又不想暴露成引脚因此就在形式上和PinHiddenByDefault一样放到右侧的细节面板里。
在FAnimNodeData* FAnimNode_Base::NodeData里存储着该动画节点的所有实例所用到的“Constant/Fold”属性信息。该动画蓝图在游戏里可能有多个实例在这些实例之间都只存一份动画节点的常量信息也只存一份FoldProperty的信息。因此用FoldProperty标记的属性的真实数据是存在TArray<FAnimNodeData> UAnimBlueprintGeneratedClass::AnimNodeData中的。存在Class中其实就是类似CDO的意思了。这么做的显然好处之一是节省内存。
自然的不同的存储方式自然要采用不同的访问方式。因此这些FoldProperty都是采用GET_ANIM_NODE_DATA来访问该数据。
## 测试代码:
```cpp
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = FoldPropertyTest, meta = (FoldProperty))
int32 MyInt_FoldProperty = 123;
```
## 测试结果:
![Untitled](Untitled.png)
## 原理:
编译的时候会把该FoldProperty添加到FoldRecords里。如果这个属性不是动态也没有暴露成引脚连接则会被当作常量。
```cpp
void FAnimBlueprintCompilerContext::GatherFoldRecordsForAnimationNode(const UScriptStruct* InNodeType, FStructProperty* InNodeProperty, UAnimGraphNode_Base* InVisualAnimNode)
{
if(SubProperty->HasMetaData(NAME_FoldProperty))
{
// Add folding candidate
AddFoldedPropertyRecord(InVisualAnimNode, InNodeProperty, SubProperty, bAllPinsExposed, !bAllPinsDisconnected, bAlwaysDynamic);
}
}
void FAnimBlueprintCompilerContext::AddFoldedPropertyRecord(UAnimGraphNode_Base* InAnimGraphNode, FStructProperty* InAnimNodeProperty, FProperty* InProperty, bool bInExposedOnPin, bool bInPinConnected, bool bInAlwaysDynamic)
{
const bool bConstant = !bInAlwaysDynamic && (!bInExposedOnPin || (bInExposedOnPin && !bInPinConnected));
if(!InProperty->HasAnyPropertyFlags(CPF_EditorOnly))
{
MessageLog.Warning(*FString::Printf(TEXT("Property %s on @@ is foldable, but not editor only"), *InProperty->GetName()), InAnimGraphNode);
}
// Create record and add it our lookup map
TSharedRef<IAnimBlueprintCompilationContext::FFoldedPropertyRecord> Record = MakeShared<IAnimBlueprintCompilationContext::FFoldedPropertyRecord>(InAnimGraphNode, InAnimNodeProperty, InProperty, bConstant);
TArray<TSharedRef<IAnimBlueprintCompilationContext::FFoldedPropertyRecord>>& Array = NodeToFoldedPropertyRecordMap.FindOrAdd(InAnimGraphNode);
Array.Add(Record);
// Record it in the appropriate data area
if(bConstant)
{
ConstantPropertyRecords.Add(Record);
}
else
{
MutablePropertyRecords.Add(Record);
}
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

View File

@@ -0,0 +1,84 @@
# GetterContext
- **功能描述:** 继续限定AnimGetter函数在哪个地方才可以使用如果不填则默认都可以用。
- **使用位置:** UFUNCTION
- **引擎模块:** AnimationGraph
- **元数据类型:** string="abc"
- **限制类型:** UAnimInstance及子类的AnimGetter函数
- **关联项:** [AnimGetter](../AnimGetter/AnimGetter.md)
- **常用程度:** ★★
继续限定AnimGetter函数在哪个地方才可以使用如果不填则默认都可以用。
选项有TransitionCustomBlendAnimGraph。
## 源码注释:
```cpp
* A context string can be provided in the GetterContext metadata and can contain any (or none) of the
* following entries separated by a pipe (|)
* Transition - Only available in a transition rule
* AnimGraph - Only available in an animgraph (also covers state anim graphs)
* CustomBlend - Only available in a custom blend graph
```
## 测试代码:
```cpp
UFUNCTION(BlueprintPure, Category = "Animation|Insider", meta = (BlueprintThreadSafe))
float MyGetStateWeight(int32 MachineIndex, int32 StateIndex);
public:
UFUNCTION(BlueprintPure, Category = "Animation|Insider", meta = (BlueprintInternalUseOnly = "true", AnimGetter, GetterContext = "Transition", BlueprintThreadSafe))
float MyGetStateWeight_AnimGetter_OnlyTransition(int32 MachineIndex, int32 StateIndex);
UFUNCTION(BlueprintPure, Category = "Animation|Insider", meta = (BlueprintInternalUseOnly = "true", AnimGetter, GetterContext = "CustomBlend", BlueprintThreadSafe))
float MyGetTransitionTimeElapsed_AnimGetter_OnlyCustomBlend(int32 MachineIndex, int32 TransitionIndex);
```
## 测试效果:
这个图要对比AnimGetter里的图来查看。
关注点一是在AnimGraph里的MyGetStateWeight_AnimGetter_OnlyTransition如果不标GetterContext 则是可以调用的但标上就只能在Transition里调用。同时也发现该函数不能在CustomBlend里调用。
二是在CustomBlend里。操作步骤是在Rule上右侧细节面板改为Custom然后进入CustomBlend的蓝图。在该蓝图下MyGetStateWeight可以调用因为并没有填写GetterContext。而MyGetTransitionTimeElapsed_AnimGetter_OnlyCustomBlend可以开始调用了。
![Untitled](Untitled.png)
## 原理:
判断能否调用的函数如下。
```cpp
bool UK2Node_AnimGetter::IsContextValidForSchema(const UEdGraphSchema* Schema) const
{
if(Contexts.Num() == 0)
{
// Valid in all graphs
return true;
}
for(const FString& Context : Contexts)
{
UClass* ClassToCheck = nullptr;
if(Context == TEXT("CustomBlend"))
{
ClassToCheck = UAnimationCustomTransitionSchema::StaticClass();
}
if(Context == TEXT("Transition"))
{
ClassToCheck = UAnimationTransitionSchema::StaticClass();
}
if(Context == TEXT("AnimGraph"))
{
ClassToCheck = UAnimationGraphSchema::StaticClass();
}
return Schema->GetClass() == ClassToCheck;
}
return false;
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 412 KiB

View File

@@ -0,0 +1,67 @@
# NeverAsPin
- **功能描述:** 在动画蓝图中使得动画节点的某个属性总是不暴露出来成为引脚
- **使用位置:** UPROPERTY
- **引擎模块:** Pin
- **元数据类型:** bool
- **限制类型:** FAnimNode_Base
- **关联项:** [PinShownByDefault](../PinShownByDefault/PinShownByDefault.md)
- **常用程度:** ★★★
NeverAsPin源码中并没有用到因为默认情况下就是不支持为引脚所以不加也都一样。
## 测试代码:
```cpp
USTRUCT(BlueprintInternalUseOnly)
struct INSIDEREDITOR_API FAnimNode_MyTestPinShown : public FAnimNode_Base
{
GENERATED_USTRUCT_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = PinShownByDefaultTest)
int32 MyInt_NotShown = 123;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = PinShownByDefaultTest, meta = (PinShownByDefault))
int32 MyInt_PinShownByDefault = 123;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = PinShownByDefaultTest, meta = (AlwaysAsPin))
int32 MyInt_AlwaysAsPin = 123;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = PinShownByDefaultTest, meta = (NeverAsPin))
int32 MyInt_NeverAsPin = 123;
};
```
## 测试效果:
MyInt_NeverAsPin只能和右边和默认的属性一样不能显示为引脚。
![PinShown](PinShown.gif)
## 原理:
发现bNeverShow并没有用到因为默认情况下就是不支持为引脚。
```cpp
void FAnimBlueprintNodeOptionalPinManager::GetRecordDefaults(FProperty* TestProperty, FOptionalPinFromProperty& Record) const
{
const UAnimationGraphSchema* Schema = GetDefault<UAnimationGraphSchema>();
// Determine if this is a pose or array of poses
FArrayProperty* ArrayProp = CastField<FArrayProperty>(TestProperty);
FStructProperty* StructProp = CastField<FStructProperty>(ArrayProp ? ArrayProp->Inner : TestProperty);
const bool bIsPoseInput = (StructProp && StructProp->Struct->IsChildOf(FPoseLinkBase::StaticStruct()));
//@TODO: Error if they specified two or more of these flags
const bool bAlwaysShow = TestProperty->HasMetaData(Schema->NAME_AlwaysAsPin) || bIsPoseInput;
const bool bOptional_ShowByDefault = TestProperty->HasMetaData(Schema->NAME_PinShownByDefault);
const bool bOptional_HideByDefault = TestProperty->HasMetaData(Schema->NAME_PinHiddenByDefault);
const bool bNeverShow = TestProperty->HasMetaData(Schema->NAME_NeverAsPin);
const bool bPropertyIsCustomized = TestProperty->HasMetaData(Schema->NAME_CustomizeProperty);
const bool bCanTreatPropertyAsOptional = CanTreatPropertyAsOptional(TestProperty);
Record.bCanToggleVisibility = bCanTreatPropertyAsOptional && (bOptional_ShowByDefault || bOptional_HideByDefault);
Record.bShowPin = bAlwaysShow || bOptional_ShowByDefault;
Record.bPropertyIsCustomized = bPropertyIsCustomized;
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

View File

@@ -0,0 +1,19 @@
# OnEvaluate
- **使用位置:** UPROPERTY
- **引擎模块:** AnimationGraph
## 原理:
在源码中发现说明OnEvaluate已经放弃了。
```cpp
// Dynamic value that needs to be wired up and evaluated each frame
const FString& EvaluationHandlerStr = SourcePinProperty->GetMetaData(AnimGraphDefaultSchema->NAME_OnEvaluate);
FName EvaluationHandlerName(*EvaluationHandlerStr);
if (EvaluationHandlerName != NAME_None)
{
// warn that NAME_OnEvaluate is deprecated:
InCompilationContext.GetMessageLog().Warning(*LOCTEXT("OnEvaluateDeprecated", "OnEvaluate meta data is deprecated, found on @@").ToString(), SourcePinProperty);
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

View File

@@ -0,0 +1,82 @@
# PinShownByDefault
- **功能描述:** 在动画蓝图中使得动画节点的某个属性一开始就暴露出来成为引脚,但也可以改变。
- **使用位置:** UPROPERTY
- **引擎模块:** AnimationGraph
- **元数据类型:** bool
- **限制类型:** FAnimNode_Base
- **关联项:** [AlwaysAsPin](../AlwaysAsPin/AlwaysAsPin.md), [NeverAsPin](../NeverAsPin/NeverAsPin.md)
- **常用程度:** ★★★
在动画蓝图中使得动画节点的某个属性一开始就暴露出来成为引脚。
和常规的蓝图不同FAnimNode_Base里面的属性默认是不在节点上显示出来的。因此才需要这个meta显式的指定哪些需要显式。
PinShownByDefault目前只在动画蓝图节点上应用。
相反的可以用PinHiddenByDefault来隐藏属性成为引脚。
## 测试代码:
```cpp
USTRUCT(BlueprintInternalUseOnly)
struct INSIDEREDITOR_API FAnimNode_MyTestPinShown : public FAnimNode_Base
{
GENERATED_USTRUCT_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = PinShownByDefaultTest)
int32 MyInt_NotShown = 123;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = PinShownByDefaultTest, meta = (PinShownByDefault))
int32 MyInt_PinShownByDefault = 123;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = PinShownByDefaultTest, meta = (AlwaysAsPin))
int32 MyInt_AlwaysAsPin = 123;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = PinShownByDefaultTest, meta = (NeverAsPin))
int32 MyInt_NeverAsPin = 123;
};
UCLASS()
class INSIDEREDITOR_API UAnimGraphNode_MyTestPinShown : public UAnimGraphNode_Base
{
GENERATED_UCLASS_BODY()
UPROPERTY(EditAnywhere, Category = Settings)
FAnimNode_MyTestPinShown Node;
};
```
## 测试效果:
可见同样的两个属性MyInt_NotShown 默认情况不显示成节点只能在细节面板里编辑。而MyInt_PinShownByDefault默认情况下成为引脚。当PinShownByDefault还可以改变去掉Pin的功能。
![PinShown](PinShown.gif)
## 原理:
源码里唯一用的地方就是在FAnimBlueprintNodeOptionalPinManager其实就是处理动画蓝图节点的Pin如何显示。
```cpp
void FAnimBlueprintNodeOptionalPinManager::GetRecordDefaults(FProperty* TestProperty, FOptionalPinFromProperty& Record) const
{
const UAnimationGraphSchema* Schema = GetDefault<UAnimationGraphSchema>();
// Determine if this is a pose or array of poses
FArrayProperty* ArrayProp = CastField<FArrayProperty>(TestProperty);
FStructProperty* StructProp = CastField<FStructProperty>(ArrayProp ? ArrayProp->Inner : TestProperty);
const bool bIsPoseInput = (StructProp && StructProp->Struct->IsChildOf(FPoseLinkBase::StaticStruct()));
//@TODO: Error if they specified two or more of these flags
const bool bAlwaysShow = TestProperty->HasMetaData(Schema->NAME_AlwaysAsPin) || bIsPoseInput;
const bool bOptional_ShowByDefault = TestProperty->HasMetaData(Schema->NAME_PinShownByDefault);
const bool bOptional_HideByDefault = TestProperty->HasMetaData(Schema->NAME_PinHiddenByDefault);
const bool bNeverShow = TestProperty->HasMetaData(Schema->NAME_NeverAsPin);
const bool bPropertyIsCustomized = TestProperty->HasMetaData(Schema->NAME_CustomizeProperty);
const bool bCanTreatPropertyAsOptional = CanTreatPropertyAsOptional(TestProperty);
Record.bCanToggleVisibility = bCanTreatPropertyAsOptional && (bOptional_ShowByDefault || bOptional_HideByDefault);
Record.bShowPin = bAlwaysShow || bOptional_ShowByDefault;
Record.bPropertyIsCustomized = bPropertyIsCustomized;
}
```

View File

@@ -0,0 +1,9 @@
# DisallowedAssetDataTags
- **功能描述:** 在UObject*属性上指定Tags来进行过滤必须没有拥有该Tags才可以被选择。
- **使用位置:** UPROPERTY
- **引擎模块:** Asset Property
- **元数据类型:** strings="a=bc=de=f"
- **限制类型:** UObject*
- **关联项:** [RequiredAssetDataTags](RequiredAssetDataTags/RequiredAssetDataTags.md), [AssetRegistrySearchable](../../Specifier/UPROPERTY/Asset/AssetRegistrySearchable/AssetRegistrySearchable.md)
- **常用程度:** ★★

View File

@@ -0,0 +1,59 @@
# ForceShowEngineContent
- **功能描述:** 指定UObject*属性的资源可选列表里强制可选引擎的内建资源
- **使用位置:** UPROPERTY
- **引擎模块:** Asset Property
- **元数据类型:** bool
- **限制类型:** UObject*
- **关联项:** [ForceShowPluginContent](ForceShowPluginContent.md)
- **常用程度:** ★★
指定UObject*属性的资源可选列表里强制可选引擎的内建资源。
## 测试代码:
```cpp
UCLASS(Blueprintable, BlueprintType)
class INSIDER_API UMyProperty_ShowContent :public UDataAsset
{
public:
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Object)
TObjectPtr<UObject> MyAsset_Default;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Object, meta = (ForceShowEngineContent))
TObjectPtr<UObject> MyAsset_ForceShowEngineContent;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Object, meta = (ForceShowPluginContent))
TObjectPtr<UObject> MyAsset_ForceShowPluginContent;
};
```
## 测试结果:
可见MyAsset_Default默认是只包含本项目的资源。
MyAsset_ForceShowEngineContent的作用其实就是在选项卡里勾选ShowEngineContent因此结果上会发现多了非常多的可选资源。
MyAsset_ForceShowPluginContent的作用同样是在选项卡里勾选ShowPluginContent可以选择别的插件里的资源。
![Untitled](Untitled.png)
## 原理:
在属性的资源选择器里会尝试寻找ForceShowEngineContent和ForceShowPluginContent然后设置到AssetPickerConfig里从而改变资源的可选类型。
```cpp
void SPropertyMenuAssetPicker::Construct( const FArguments& InArgs )
{
const bool bForceShowEngineContent = PropertyHandle ? PropertyHandle->HasMetaData(TEXT("ForceShowEngineContent")) : false;
const bool bForceShowPluginContent = PropertyHandle ? PropertyHandle->HasMetaData(TEXT("ForceShowPluginContent")) : false;
FAssetPickerConfig AssetPickerConfig;
// Force show engine content if meta data says so
AssetPickerConfig.bForceShowEngineContent = bForceShowEngineContent;
// Force show plugin content if meta data says so
AssetPickerConfig.bForceShowPluginContent = bForceShowPluginContent;
}
```

View File

@@ -0,0 +1,8 @@
# ForceShowPluginContent
- **功能描述:** 指定UObject*属性的资源可选列表里强制可选其他插件里的内建资源
- **使用位置:** UPROPERTY
- **引擎模块:** Asset Property
- **元数据类型:** bool
- **限制类型:** UObject*
- **关联项:** [ForceShowEngineContent](ForceShowEngineContent.md)

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 KiB

View File

@@ -0,0 +1,77 @@
# GetAssetFilter
- **功能描述:** 指定一个UFUNCTION来对UObject*属性的可选资源进行排除过滤。
- **使用位置:** UPROPERTY
- **引擎模块:** Asset Property
- **元数据类型:** string="abc"
- **限制类型:** UObject*
- **常用程度:** ★★★
指定一个UFUNCTION来对UObject*属性的可选资源进行排除过滤。
- 指定的函数名字必须是UFUNCTION在本类中定义。
- 函数的原型是bool FuncName(const FAssetData& AssetData) const;返回true来排除掉该资产。
- 这是一种让用户自定义资产过滤的方式。
## 测试代码:
```cpp
UCLASS(Blueprintable, BlueprintType)
class INSIDER_API UMyProperty_GetAssetFilter :public UDataAsset
{
public:
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite)
UObject* MyAsset_Default;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (GetAssetFilter = "IsShouldFilterAsset"))
UObject* MyAsset_GetAssetFilter;
public:
UFUNCTION()
bool IsShouldFilterAsset(const FAssetData& AssetData)
{
return !AssetData.IsInstanceOf<UDataAsset>();
}
};
```
## 测试效果:
可以见到MyAsset_GetAssetFilter进行过滤后只允许DataAsset类型的资产。
![Untitled](Untitled.png)
## 原理:
在SPropertyEditorAsset对应UObject类型属性中有判断GetAssetFilter的meta得到函数并附加到资产排除的回调里去。
```cpp
void SPropertyEditorAsset::Construct(const FArguments& InArgs, const TSharedPtr<FPropertyEditor>& InPropertyEditor)
{
if (Property && Property->GetOwnerProperty()->HasMetaData("GetAssetFilter"))
{
// Add MetaData asset filter
const FString GetAssetFilterFunctionName = Property->GetOwnerProperty()->GetMetaData("GetAssetFilter");
if (!GetAssetFilterFunctionName.IsEmpty())
{
TArray<UObject*> ObjectList;
if (PropertyEditor.IsValid())
{
PropertyEditor->GetPropertyHandle()->GetOuterObjects(ObjectList);
}
else if (PropertyHandle.IsValid())
{
PropertyHandle->GetOuterObjects(ObjectList);
}
for (UObject* Object : ObjectList)
{
const UFunction* GetAssetFilterFunction = Object ? Object->FindFunction(*GetAssetFilterFunctionName) : nullptr;
if (GetAssetFilterFunction)
{
AppendOnShouldFilterAssetCallback(FOnShouldFilterAsset::CreateUFunction(Object, GetAssetFilterFunction->GetFName()));
}
}
}
}
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

View File

@@ -0,0 +1,208 @@
# RequiredAssetDataTags
- **功能描述:** 在UObject*属性上指定Tags来进行过滤必须拥有该Tags才可以被选择。
- **使用位置:** UPROPERTY
- **引擎模块:** Asset Property
- **元数据类型:** strings="a=bc=de=f"
- **限制类型:** UObject*
- **关联项:** [DisallowedAssetDataTags](../DisallowedAssetDataTags.md), [AssetRegistrySearchable](../../../Specifier/UPROPERTY/Asset/AssetRegistrySearchable/AssetRegistrySearchable.md)
- **常用程度:** ★★
在UObject*属性上指定Tags来进行过滤必须拥有该Tags才可以被选择。
相关联的可参考AssetRegistrySearchable标识符和GetAssetRegistryTags 方法。
## 测试代码:
```cpp
USTRUCT(BlueprintType)
struct INSIDER_API FMyTableRow_Required :public FTableRowBase
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadWrite, EditAnywhere)
int32 MyInt = 123;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
FString MyString;
};
USTRUCT(BlueprintType)
struct INSIDER_API FMyTableRow_Disallowed :public FTableRowBase
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadWrite, EditAnywhere)
float MyFloat = 123.f;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
UTexture2D* MyTexture;
};
UCLASS(Blueprintable, BlueprintType)
class INSIDER_API UMyProperty_AssetDataTags :public UDataAsset
{
public:
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Object)
TObjectPtr<UObject> MyAsset_Default;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Object, meta = (RequiredAssetDataTags = "MyIdForSearch=MyId456"))
TObjectPtr<UObject> MyAsset_RequiredAssetDataTags;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Object, meta = (DisallowedAssetDataTags = "MyOtherId=MyOtherId789"))
TObjectPtr<UObject> MyAsset_DisallowedAssetDataTags;
public:
UPROPERTY(EditAnywhere, Category = DataTable)
TObjectPtr<class UDataTable> MyDataTable_Default;
UPROPERTY(EditAnywhere, Category = DataTable, meta = (RequiredAssetDataTags = "RowStructure=/Script/Insider.MyTableRow_Required"))
TObjectPtr<class UDataTable> MyDataTable_RequiredAssetDataTags;
UPROPERTY(EditAnywhere, Category = DataTable, meta = (DisallowedAssetDataTags = "RowStructure=/Script/Insider.MyTableRow_Disallowed"))
TObjectPtr<class UDataTable> MyDataTable_DisallowedAssetDataTags;
};
```
## 测试效果:
如上面代码所见定义了两个不同类型的FTableRowBase并且也创建了两个DataTable。同时也有两个DataAssetAssetRegistrySearchable的例子里定义的结构都有MyIdForSearch和MyOtherId的Tag但是有不同的值以此来进行区分。
- MyAsset_Default可以筛选出所有的对象图中示例有730个。
- MyAsset_RequiredAssetDataTags因为加了RequiredAssetDataTags 只有DA_MyPropertySearch符合因为MyIdForSearch=MyId456。
- MyAsset_DisallowedAssetDataTags把DA_MyPropertySearch_Disallowed过滤掉了因为我配置的MyOtherId=MyOtherId789因此只剩下729个。
- 关于DataTable也是同理。MyDataTable_Default可以获取所有的DataTable有3个而MyDataTable_RequiredAssetDataTags限制了RowStructure只能是FMyTableRow_Required 因此只能筛选出一个。MyDataTable_DisallowedAssetDataTags排除掉一个RowStructure为FMyTableRow_Disallowed 的因此就剩下2个。
![Untitled](Untitled.png)
## 源码中例子:
```cpp
UPROPERTY(Category="StateTree", EditAnywhere, meta=(RequiredAssetDataTags="Schema=/Script/MassAIBehavior.MassStateTreeSchema"))
TObjectPtr<UStateTree> StateTree;
UPROPERTY(EditAnywhere, Category=Appearance, meta = (RequiredAssetDataTags = "RowStructure=/Script/UMG.RichImageRow"))
TObjectPtr<class UDataTable> ImageSet;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Compositing, meta = (AllowPrivateAccess, RequiredAssetDataTags = "IsSourceValid=True"), Setter = SetCompositeTexture, Getter = GetCompositeTexture)
TObjectPtr<class UTexture> CompositeTexture;
```
## 原理:
在UObject*属性上RequiredAssetDataTags和DisallowedAssetDataTags的配置会在这个属性的编辑器SPropertyEditorAsset初始化的时候解析提取到其成员变量RequiredAssetDataTags和DisallowedAssetDataTags里本质就是个键值对。而后续在进行Asset过滤的时候IsAssetFiltered的调用就会开始把FAssetData里的Tags去匹配该属性的Tags需求。Disallowed的出现就排除掉Required的必须拥有才不会被过滤最终实现了过滤效果。
关于FAssetData里的Tags可以参考AssetRegistrySearchable标识符和GetAssetRegistryTags 方法的调用和实现简单来说就是在对象上会有个方式主动提供Tags给AssetRegistry。
关于DataTable为何可以通过RowStructure过滤通过查看DataTable里的GetAssetRegistryTags方法就可以知道它主动提供了RowStructure的Tags注册。
```cpp
FAssetDataTagMapBase=TSortedMap<FName, FString, FDefaultAllocator, FNameFastLess>;
SPropertyEditorAsset::
/** Tags (and eventually values) that can NOT be used with this property */
TSharedPtr<FAssetDataTagMap> DisallowedAssetDataTags;
**/** Tags and values that must be present for an asset to be used with this property */
TSharedPtr<FAssetDataTagMap> RequiredAssetDataTags;
void SPropertyEditorAsset::InitializeAssetDataTags(const FProperty* Property)
{
if (Property == nullptr)
{
return;
}
const FProperty* MetadataProperty = GetActualMetadataProperty(Property);
const FString DisallowedAssetDataTagsFilterString = MetadataProperty->GetMetaData("DisallowedAssetDataTags");
if (!DisallowedAssetDataTagsFilterString.IsEmpty())
{
TArray<FString> DisallowedAssetDataTagsAndValues;
DisallowedAssetDataTagsFilterString.ParseIntoArray(DisallowedAssetDataTagsAndValues, TEXT(","), true);
for (const FString& TagAndOptionalValueString : DisallowedAssetDataTagsAndValues)
{
TArray<FString> TagAndOptionalValue;
TagAndOptionalValueString.ParseIntoArray(TagAndOptionalValue, TEXT("="), true);
size_t NumStrings = TagAndOptionalValue.Num();
check((NumStrings == 1) || (NumStrings == 2)); // there should be a single '=' within a tag/value pair
if (!DisallowedAssetDataTags.IsValid())
{
DisallowedAssetDataTags = MakeShared<FAssetDataTagMap>();
}
DisallowedAssetDataTags->Add(FName(*TagAndOptionalValue[0]), (NumStrings > 1) ? TagAndOptionalValue[1] : FString());
}
}
const FString RequiredAssetDataTagsFilterString = MetadataProperty->GetMetaData("RequiredAssetDataTags");
if (!RequiredAssetDataTagsFilterString.IsEmpty())
{
TArray<FString> RequiredAssetDataTagsAndValues;
RequiredAssetDataTagsFilterString.ParseIntoArray(RequiredAssetDataTagsAndValues, TEXT(","), true);
for (const FString& TagAndOptionalValueString : RequiredAssetDataTagsAndValues)
{
TArray<FString> TagAndOptionalValue;
TagAndOptionalValueString.ParseIntoArray(TagAndOptionalValue, TEXT("="), true);
size_t NumStrings = TagAndOptionalValue.Num();
check((NumStrings == 1) || (NumStrings == 2)); // there should be a single '=' within a tag/value pair
if (!RequiredAssetDataTags.IsValid())
{
RequiredAssetDataTags = MakeShared<FAssetDataTagMap>();
}
RequiredAssetDataTags->Add(FName(*TagAndOptionalValue[0]), (NumStrings > 1) ? TagAndOptionalValue[1] : FString());
}
}
}
bool SPropertyEditorAsset::IsAssetFiltered(const FAssetData& InAssetData)
{
//判断只要包含就不符合
if (DisallowedAssetDataTags.IsValid())
{
for (const auto& DisallowedTagAndValue : *DisallowedAssetDataTags.Get())
{
if (InAssetData.TagsAndValues.ContainsKeyValue(DisallowedTagAndValue.Key, DisallowedTagAndValue.Value))
{
return true;
}
}
}
//判断必须包含才不会被过滤掉
if (RequiredAssetDataTags.IsValid())
{
for (const auto& RequiredTagAndValue : *RequiredAssetDataTags.Get())
{
if (!InAssetData.TagsAndValues.ContainsKeyValue(RequiredTagAndValue.Key, RequiredTagAndValue.Value))
{
// For backwards compatibility compare against short name version of the tag value.
if (!FPackageName::IsShortPackageName(RequiredTagAndValue.Value) &&
InAssetData.TagsAndValues.ContainsKeyValue(RequiredTagAndValue.Key, FPackageName::ObjectPathToObjectName(RequiredTagAndValue.Value)))
{
continue;
}
return true;
}
}
}
return false;
}
void UDataTable::GetAssetRegistryTags(FAssetRegistryTagsContext Context) const
{
if (AssetImportData)
{
Context.AddTag( FAssetRegistryTag(SourceFileTagName(), AssetImportData->GetSourceData().ToJson(), FAssetRegistryTag::TT_Hidden) );
}
// Add the row structure tag
{
static const FName RowStructureTag = "RowStructure";
Context.AddTag( FAssetRegistryTag(RowStructureTag, GetRowStructPathName().ToString(), FAssetRegistryTag::TT_Alphabetical) );
}
Super::GetAssetRegistryTags(Context);
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 325 KiB

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

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