vault backup: 2024-10-12 17:19:45
@@ -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并没有关系。
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
而AMyActor_ChildCanTick类里虽然已经手动关闭了PrimaryActorTick.bCanEverTick,但是在子类里依然可以正常的Tick(在编译的时候内部可以正常的再重新开启bCanEverTick)。
|
||||
|
||||

|
||||
|
||||
## 源码里判断的逻辑:
|
||||
|
||||
开启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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 200 KiB |
After Width: | Height: | Size: 438 KiB |
After Width: | Height: | Size: 216 KiB |
@@ -0,0 +1,8 @@
|
||||
# ChildCannotTick
|
||||
|
||||
- **功能描述:** 用于Actor或ActorComponent子类,标记允许其蓝图子类不可以接受响应Tick事件,哪怕父类可以Tick
|
||||
- **使用位置:** UCLASS
|
||||
- **元数据类型:** bool
|
||||
- **限制类型:** Actor类
|
||||
- **关联项:** [ChildCanTick](ChildCanTick/ChildCanTick.md)
|
||||
- **常用程度:** ★★★
|
@@ -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中用到。
|
@@ -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;
|
||||
};
|
||||
```
|
||||
|
||||
## 测试效果:
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
根据源码的里的逻辑可见,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;
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 194 KiB |
@@ -0,0 +1,9 @@
|
||||
# AnimBlueprintFunction
|
||||
|
||||
- **功能描述:** 标明是动画蓝图里的内部纯存根函数,只在动画蓝图编译时设置
|
||||
- **使用位置:** UFUNCTION
|
||||
- **引擎模块:** AnimationGraph
|
||||
- **元数据类型:** bool
|
||||
- **限制类型:** Anim BP
|
||||
|
||||
只是在内部使用,在动画蓝图编译的时候设置。但是没有在代码里显式的编写。
|
@@ -0,0 +1,78 @@
|
||||
# AnimGetter
|
||||
|
||||
- **功能描述:** 指定UAnimInstance及子类的该函数成为一个AnimGetter函数。
|
||||
- **使用位置:** UFUNCTION
|
||||
- **引擎模块:** AnimationGraph
|
||||
- **元数据类型:** bool
|
||||
- **限制类型:** UAnimInstance及子类的函数
|
||||
- **关联项:** [GetterContext](../GetterContext/GetterContext.md)
|
||||
- **常用程度:** ★★★
|
||||
|
||||
指定UAnimInstance及子类的该函数成为一个AnimGetter函数。
|
||||
|
||||
- 在一些情况下会继承UAnimInstance创建自己的动画蓝图子类,然后里面可以自己做一些优化,或者添加一些自己的功能函数。
|
||||
- 所谓的AnimGetter,其实就是会被UK2Node_AnimGetter识别并包装成该蓝图节点的函数。识别的范围是在UAnimInstance及子类(就是动画蓝图)的C++函数。
|
||||
- AnimGetter还有两个额外功能:一是会自动根据当前上下文填充函数里的AssetPlayerIndex,MachineIndex,StateIndex,TransitionIndex和参数。二是会根据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);
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## 测试效果:
|
||||
|
||||
分别定义使用了AssetPlayerIndex,MachineIndex,StateIndex,TransitionIndex的AnimGetter函数以及普通蓝图函数作为对比。分别查看在动画蓝图里几个作用域里的用法。
|
||||
|
||||
- 可见在不管什么作用域,普通蓝图函数都可以调用(毕竟没有做Context的检查)。另外AssetPlayerIndex等参数都没有被自动填充,这几乎是没法用的,因为用户其实并不太懂如何去手填这些Index,最好是交给编译器来填充。
|
||||
- 图里高亮的是可以调用的AnimGetter函数。细看的话,可以分析发现规则是只有能正确填充AssetPlayerIndex等参数的才能调用。因此在Transition里能调用的最多,因为这个时候最叶子节点,有动画,又有状态机和Transition节点。
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
分析函数上的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);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 620 KiB |
@@ -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不一样了。
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
在定制化的时候,根据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;
|
||||
}
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 218 KiB |
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
@@ -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
|
||||
}
|
||||
```
|
@@ -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里用到。
|
@@ -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默认创建流程给跳过了。
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
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());
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 253 KiB |
@@ -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;
|
||||
```
|
||||
|
||||
## 测试结果:
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
编译的时候会把该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);
|
||||
}
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 77 KiB |
@@ -0,0 +1,84 @@
|
||||
# GetterContext
|
||||
|
||||
- **功能描述:** 继续限定AnimGetter函数在哪个地方才可以使用,如果不填,则默认都可以用。
|
||||
- **使用位置:** UFUNCTION
|
||||
- **引擎模块:** AnimationGraph
|
||||
- **元数据类型:** string="abc"
|
||||
- **限制类型:** UAnimInstance及子类的AnimGetter函数
|
||||
- **关联项:** [AnimGetter](../AnimGetter/AnimGetter.md)
|
||||
- **常用程度:** ★★
|
||||
|
||||
继续限定AnimGetter函数在哪个地方才可以使用,如果不填,则默认都可以用。
|
||||
|
||||
选项有:Transition,CustomBlend,AnimGraph。
|
||||
|
||||
## 源码注释:
|
||||
|
||||
```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可以开始调用了。
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
判断能否调用的函数如下。
|
||||
|
||||
```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;
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 412 KiB |
@@ -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只能和右边和默认的属性一样,不能显示为引脚。
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
发现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;
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 194 KiB |
@@ -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);
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 194 KiB |
@@ -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的功能。
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
源码里唯一用的地方就是在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;
|
||||
}
|
||||
```
|
@@ -0,0 +1,9 @@
|
||||
# DisallowedAssetDataTags
|
||||
|
||||
- **功能描述:** 在UObject*属性上指定Tags来进行过滤,必须没有拥有该Tags才可以被选择。
|
||||
- **使用位置:** UPROPERTY
|
||||
- **引擎模块:** Asset Property
|
||||
- **元数据类型:** strings="a=b,c=d,e=f"
|
||||
- **限制类型:** UObject*
|
||||
- **关联项:** [RequiredAssetDataTags](RequiredAssetDataTags/RequiredAssetDataTags.md), [AssetRegistrySearchable](../../Specifier/UPROPERTY/Asset/AssetRegistrySearchable/AssetRegistrySearchable.md)
|
||||
- **常用程度:** ★★
|
@@ -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,可以选择别的插件里的资源。
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
在属性的资源选择器里会尝试寻找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;
|
||||
}
|
||||
```
|
@@ -0,0 +1,8 @@
|
||||
# ForceShowPluginContent
|
||||
|
||||
- **功能描述:** 指定UObject*属性的资源可选列表里强制可选其他插件里的内建资源
|
||||
- **使用位置:** UPROPERTY
|
||||
- **引擎模块:** Asset Property
|
||||
- **元数据类型:** bool
|
||||
- **限制类型:** UObject*
|
||||
- **关联项:** [ForceShowEngineContent](ForceShowEngineContent.md)
|
After Width: | Height: | Size: 353 KiB |
@@ -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类型的资产。
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
在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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 208 KiB |
@@ -0,0 +1,208 @@
|
||||
# RequiredAssetDataTags
|
||||
|
||||
- **功能描述:** 在UObject*属性上指定Tags来进行过滤,必须拥有该Tags才可以被选择。
|
||||
- **使用位置:** UPROPERTY
|
||||
- **引擎模块:** Asset Property
|
||||
- **元数据类型:** strings="a=b,c=d,e=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。同时也有两个DataAsset(AssetRegistrySearchable的例子里定义的结构)都有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个。
|
||||
|
||||

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

|
||||
|
||||
一个函数如果IsPromotableFunction,在调用的时候就会用UK2Node_PromotableOperator来作为蓝图节点(默认是UK2Node_CallFunction),UK2Node_PromotableOperator是典型的用于Wildcard泛型的二元运算符。如下图的Add(+)。在这种Add 的引脚上右键可以弹出Pin的类型转换从Wildcard到特定的类型,因为该结构有定义Add_XXX的函数,并且没有IgnoreTypePromotion,因此就被包含进了TypePromotion的映射表里。
|
||||
|
||||
上面的这个Pin转换菜单就是在UK2Node_PromotableOperator::CreateConversionMenu里收集的。
|
After Width: | Height: | Size: 209 KiB |
After Width: | Height: | Size: 104 KiB |
After Width: | Height: | Size: 42 KiB |