vault backup: 2024-10-12 17:19:45
@@ -0,0 +1,79 @@
|
||||
# Abstract
|
||||
|
||||
- **功能描述:** 指定此类为抽象基类。可被继承,但不可生成对象。
|
||||
- **引擎模块:** Blueprint
|
||||
- **元数据类型:** bool
|
||||
- **作用机制:** 在ClassFlags中添加[CLASS_Abstract](../../../../Flags/EClassFlags/CLASS_Abstract.md)
|
||||
- **常用程度:** ★★★★★
|
||||
|
||||
指定此类为抽象基类。可被继承,但不可生成对象。
|
||||
|
||||
一般是用在XXXBase基类。
|
||||
|
||||
## 示例代码:
|
||||
|
||||
```cpp
|
||||
/*
|
||||
ClassFlags: CLASS_Abstract | CLASS_MatchedSerializers | CLASS_Native | CLASS_RequiredAPI | CLASS_TokenStreamAssembled | CLASS_Intrinsic | CLASS_Constructed
|
||||
*/
|
||||
UCLASS(Blueprintable, abstract)
|
||||
class INSIDER_API UMyClass_Abstract :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
};
|
||||
|
||||
//测试语句:
|
||||
UMyClass_Abstract* obj=NewObject<UMyClass_Abstract>();
|
||||
```
|
||||
|
||||
## 示例效果:
|
||||
|
||||
在蓝图中的ConstructObject不会出现该类。同时在C++中NewObject也会报错。
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
在NewObject的时候会进行Abstract的判断。
|
||||
|
||||
```cpp
|
||||
bool StaticAllocateObjectErrorTests( const UClass* InClass, UObject* InOuter, FName InName, EObjectFlags InFlags)
|
||||
{
|
||||
// Validation checks.
|
||||
if( !InClass )
|
||||
{
|
||||
UE_LOG(LogUObjectGlobals, Fatal, TEXT("Empty class for object %s"), *InName.ToString() );
|
||||
return true;
|
||||
}
|
||||
|
||||
// for abstract classes that are being loaded NOT in the editor we want to error. If they are in the editor we do not want to have an error
|
||||
if (FScopedAllowAbstractClassAllocation::IsDisallowedAbstractClass(InClass, InFlags))
|
||||
{
|
||||
if ( GIsEditor )
|
||||
{
|
||||
const FString ErrorMsg = FString::Printf(TEXT("Class which was marked abstract was trying to be loaded in Outer %s. It will be nulled out on save. %s %s"), *GetPathNameSafe(InOuter), *InName.ToString(), *InClass->GetName());
|
||||
// if we are trying instantiate an abstract class in the editor we'll warn the user that it will be nulled out on save
|
||||
UE_LOG(LogUObjectGlobals, Warning, TEXT("%s"), *ErrorMsg);
|
||||
ensureMsgf(false, TEXT("%s"), *ErrorMsg);
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogUObjectGlobals, Fatal, TEXT("%s"), *FString::Printf( TEXT("Can't create object %s in Outer %s: class %s is abstract"), *InName.ToString(), *GetPathNameSafe(InOuter), *InClass->GetName()));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool FScopedAllowAbstractClassAllocation::IsDisallowedAbstractClass(const UClass* InClass, EObjectFlags InFlags)
|
||||
{
|
||||
if (((InFlags& RF_ClassDefaultObject) == 0) && InClass->HasAnyClassFlags(CLASS_Abstract))
|
||||
{
|
||||
if (AllowAbstractCount == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 82 KiB |
@@ -0,0 +1,93 @@
|
||||
# BlueprintType
|
||||
|
||||
- **功能描述:** 可当做变量类型
|
||||
- **引擎模块:** Blueprint
|
||||
- **元数据类型:** bool
|
||||
- **作用机制:** Meta增加[BlueprintType](../../../../Meta/Blueprint/BlueprintType.md)
|
||||
- **关联项:** [NotBlueprintType ](../NotBlueprintType.md)
|
||||
- **常用程度:** ★★★★★
|
||||
|
||||
可当做变量类型。
|
||||
|
||||
关键是设置BlueprintType和NotBlueprintType这两个metadata.
|
||||
|
||||
## 示例代码:
|
||||
|
||||
```cpp
|
||||
|
||||
/*
|
||||
(BlueprintType = true, IncludePath = Class/MyClass_BlueprintType.h, ModuleRelativePath = Class/MyClass_BlueprintType.h)
|
||||
*/
|
||||
UCLASS(BlueprintType)
|
||||
class INSIDER_API UMyClass_BlueprintType :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
};
|
||||
|
||||
/*
|
||||
(IncludePath = Class/MyClass_BlueprintType.h, ModuleRelativePath = Class/MyClass_BlueprintType.h)
|
||||
*/
|
||||
UCLASS()
|
||||
class INSIDER_API UMyClass_BlueprintType_Child :public UMyClass_BlueprintType
|
||||
{
|
||||
GENERATED_BODY()
|
||||
};
|
||||
|
||||
/*
|
||||
(IncludePath = Class/MyClass_BlueprintType.h, ModuleRelativePath = Class/MyClass_BlueprintType.h, NotBlueprintType = true)
|
||||
*/
|
||||
UCLASS(NotBlueprintType)
|
||||
class INSIDER_API UMyClass_NotBlueprintType :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
};
|
||||
|
||||
/*
|
||||
(BlueprintType = true, IncludePath = Class/MyClass_BlueprintType.h, ModuleRelativePath = Class/MyClass_BlueprintType.h)
|
||||
*/
|
||||
UCLASS(BlueprintType)
|
||||
class INSIDER_API UMyClass_NotBlueprintType_To_BlueprintType:public UMyClass_NotBlueprintType
|
||||
{
|
||||
GENERATED_BODY()
|
||||
};
|
||||
|
||||
/*
|
||||
(IncludePath = Class/MyClass_BlueprintType.h, ModuleRelativePath = Class/MyClass_BlueprintType.h, NotBlueprintType = true)
|
||||
*/
|
||||
UCLASS(NotBlueprintType)
|
||||
class INSIDER_API UMyClass_BlueprintType_To_NotBlueprintType:public UMyClass_BlueprintType
|
||||
{
|
||||
GENERATED_BODY()
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## 示例结果:
|
||||
|
||||
带有BlueprintType =true的才可以当作变量
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
在UEdGraphSchema_K2::IsAllowableBlueprintVariableType的3个重载函数分别判断UEnum,UClass,UScriptStruct能否当作变量。
|
||||
|
||||
```cpp
|
||||
用UEdGraphSchema_K2::IsAllowableBlueprintVariableType来判断
|
||||
|
||||
const UClass* ParentClass = InClass;
|
||||
while(ParentClass)
|
||||
{
|
||||
// Climb up the class hierarchy and look for "BlueprintType" and "NotBlueprintType" to see if this class is allowed.
|
||||
if(ParentClass->GetBoolMetaData(FBlueprintMetadata::MD_AllowableBlueprintVariableType)
|
||||
|| ParentClass->HasMetaData(FBlueprintMetadata::MD_BlueprintSpawnableComponent))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if(ParentClass->GetBoolMetaData(FBlueprintMetadata::MD_NotAllowableBlueprintVariableType))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
ParentClass = ParentClass->GetSuperClass();
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 14 KiB |
@@ -0,0 +1,96 @@
|
||||
# Blueprintable
|
||||
|
||||
- **功能描述:** 可以在蓝图里被继承,隐含的作用也可当变量类型
|
||||
- **引擎模块:** Blueprint
|
||||
- **元数据类型:** bool
|
||||
- **作用机制:** 在Meta添加[IsBlueprintBase](../../../../Meta/Blueprint/IsBlueprintBase.md)和[BlueprintType](../../../../Meta/Blueprint/BlueprintType.md)
|
||||
- **关联项:** [NotBlueprintable](../NotBlueprintable.md)
|
||||
- **常用程度:** ★★★★★
|
||||
|
||||
可以在蓝图里被继承,隐含的作用也可当变量类型。
|
||||
|
||||
当设置Blueprintable标记的时候,会隐含的设置上BlueprintType = true的metadata。去除的时候,也会相应的去除掉BlueprintType = true。
|
||||
|
||||
## 示例代码:
|
||||
|
||||
```cpp
|
||||
/*
|
||||
(BlueprintType = true, IncludePath = Class/MyClass_Blueprintable.h, IsBlueprintBase = true, ModuleRelativePath = Class/MyClass_Blueprintable.h)
|
||||
*/
|
||||
UCLASS(Blueprintable)
|
||||
class INSIDER_API UMyClass_Blueprintable :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
};
|
||||
|
||||
/*
|
||||
(IncludePath = Class/MyClass_Blueprintable.h, IsBlueprintBase = false, ModuleRelativePath = Class/MyClass_Blueprintable.h)
|
||||
*/
|
||||
UCLASS(NotBlueprintable)
|
||||
class INSIDER_API UMyClass_NotBlueprintable :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
};
|
||||
|
||||
/*
|
||||
(BlueprintType = true, IncludePath = Class/MyClass_Blueprintable.h, IsBlueprintBase = true, ModuleRelativePath = Class/MyClass_Blueprintable.h)
|
||||
*/
|
||||
UCLASS(Blueprintable)
|
||||
class INSIDER_API UMyClass_NotBlueprintable_To_Blueprintable :public UMyClass_NotBlueprintable
|
||||
{
|
||||
GENERATED_BODY()
|
||||
};
|
||||
|
||||
/*
|
||||
(IncludePath = Class/MyClass_Blueprintable.h, IsBlueprintBase = false, ModuleRelativePath = Class/MyClass_Blueprintable.h)
|
||||
*/
|
||||
UCLASS(NotBlueprintable)
|
||||
class INSIDER_API UMyClass_Blueprintable_To_NotBlueprintable :public UMyClass_Blueprintable
|
||||
{
|
||||
GENERATED_BODY()
|
||||
};
|
||||
```
|
||||
|
||||
## 示例效果:
|
||||
|
||||
只有带有Blueprintable才可以被选做基类。
|
||||
|
||||

|
||||
|
||||
不过是否能够当做变量的规则,还是会依赖父类的Blueprint标记。因此以下这3个都是可以当做变量的。
|
||||
|
||||
其中UMyClass_Blueprintable_To_NotBlueprintable可以当做变量是因为父类UMyClass_Blueprintable可以当做变量,因此就继承了下来。
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
可见MD_IsBlueprintBase的判断用来决定是否能创建子类
|
||||
|
||||
```cpp
|
||||
bool FKismetEditorUtilities::CanCreateBlueprintOfClass(const UClass* Class)
|
||||
{
|
||||
bool bCanCreateBlueprint = false;
|
||||
|
||||
if (Class)
|
||||
{
|
||||
bool bAllowDerivedBlueprints = false;
|
||||
GConfig->GetBool(TEXT("Kismet"), TEXT("AllowDerivedBlueprints"), /*out*/ bAllowDerivedBlueprints, GEngineIni);
|
||||
|
||||
bCanCreateBlueprint = !Class->HasAnyClassFlags(CLASS_Deprecated)
|
||||
&& !Class->HasAnyClassFlags(CLASS_NewerVersionExists)
|
||||
&& (!Class->ClassGeneratedBy || (bAllowDerivedBlueprints && !IsClassABlueprintSkeleton(Class)));
|
||||
|
||||
const bool bIsBPGC = (Cast<UBlueprintGeneratedClass>(Class) != nullptr);
|
||||
|
||||
const bool bIsValidClass = Class->GetBoolMetaDataHierarchical(FBlueprintMetadata::MD_IsBlueprintBase)
|
||||
|| (Class == UObject::StaticClass())
|
||||
|| (Class == USceneComponent::StaticClass() || Class == UActorComponent::StaticClass())
|
||||
|| bIsBPGC; // BPs are always considered inheritable
|
||||
|
||||
bCanCreateBlueprint &= bIsValidClass;
|
||||
}
|
||||
|
||||
return bCanCreateBlueprint;
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,60 @@
|
||||
# Const
|
||||
|
||||
- **功能描述:** 表示本类的内部属性不可在蓝图中被修改,只读不可写。
|
||||
- **引擎模块:** Blueprint
|
||||
- **元数据类型:** bool
|
||||
- **作用机制:** 在ClassFlags中添加[CLASS_Abstract](../../../../Flags/EClassFlags/CLASS_Const.md)
|
||||
- **常用程度:** ★★★
|
||||
|
||||
表示本类的内部属性不可在蓝图中被修改,只读不可写。
|
||||
|
||||
继承的蓝图类也是如此。其实就是自动的给本类和子类上添加const的标志。注意只是在蓝图里检查,C++依然可以随意改变,遵循C++的规则。所以这个const是只给蓝图用的,在蓝图里检查。函数依然可以随便调用,只是没有属性的Set方法了,也不能改变了。
|
||||
|
||||
## 示例代码:
|
||||
|
||||
```cpp
|
||||
/*
|
||||
ClassFlags: CLASS_MatchedSerializers | CLASS_Native | CLASS_Const | CLASS_RequiredAPI | CLASS_TokenStreamAssembled | CLASS_Intrinsic | CLASS_Constructed
|
||||
*/
|
||||
UCLASS(Blueprintable, Const)
|
||||
class INSIDER_API UMyClass_Const :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
int32 MyProperty = 123;
|
||||
UFUNCTION(BlueprintCallable)
|
||||
void MyFunc() { ++MyProperty; }
|
||||
};
|
||||
```
|
||||
|
||||
## 示例效果:
|
||||
|
||||
在蓝图子类中尝试修改属性会报错。
|
||||
|
||||

|
||||
|
||||
跟蓝图Class Settings里打开这个开关设定的一样
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
Const类生成的实例属性对带有const的标记,从而阻止修改自身的属性。
|
||||
|
||||
```cpp
|
||||
void FKCHandler_VariableSet::InnerAssignment(FKismetFunctionContext& Context, UEdGraphNode* Node, UEdGraphPin* VariablePin, UEdGraphPin* ValuePin)
|
||||
{
|
||||
if (!(*VariableTerm)->IsTermWritable())
|
||||
{
|
||||
CompilerContext.MessageLog.Error(*LOCTEXT("WriteConst_Error", "Cannot write to const @@").ToString(), VariablePin);
|
||||
}
|
||||
}
|
||||
|
||||
bool FBPTerminal::IsTermWritable() const
|
||||
{
|
||||
return !bIsLiteral && !bIsConst;
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 48 KiB |
After Width: | Height: | Size: 6.8 KiB |
After Width: | Height: | Size: 28 KiB |
@@ -0,0 +1,108 @@
|
||||
# HideFunctions
|
||||
|
||||
- **功能描述:** 在子类的函数覆盖列表里隐藏掉某些函数。
|
||||
- **引擎模块:** Blueprint
|
||||
- **元数据类型:** strings=(abc,"d|e","x|y|z")
|
||||
- **作用机制:** 在Meta中增加[HideFunctions](../../../../Meta/Blueprint/HideFunctions.md)
|
||||
- **关联项:** [ShowFunctions](../ShowFunctions.md)
|
||||
- **常用程度:** ★★
|
||||
|
||||
在子类的函数覆盖列表里隐藏掉某些函数。
|
||||
|
||||
- 在蓝图中鼠标右键依然可以查看到该类下BlueprintCallable的函数,依然可以调用,本标记只是用在类的函数覆盖列表上。
|
||||
- HideFunctions其实只能填函数名字,想要隐藏一个目录下的函数,是需要HideCategories再额外定义的。
|
||||
|
||||
源码中只有一个地方用到,一个很好的示例是UCameraComponent中定义的SetFieldOfView和SetAspectRatio,对UCineCameraComponent 来说是无意义的,因此隐藏掉会更好。
|
||||
|
||||
```cpp
|
||||
class ENGINE_API UCameraComponent : public USceneComponent
|
||||
{
|
||||
UFUNCTION(BlueprintCallable, Category = Camera)
|
||||
virtual void SetFieldOfView(float InFieldOfView) { FieldOfView = InFieldOfView; }
|
||||
UFUNCTION(BlueprintCallable, Category = Camera)
|
||||
void SetAspectRatio(float InAspectRatio) { AspectRatio = InAspectRatio; }
|
||||
}
|
||||
|
||||
UCLASS(HideCategories = (CameraSettings), HideFunctions = (SetFieldOfView, SetAspectRatio), Blueprintable, ClassGroup = Camera, meta = (BlueprintSpawnableComponent), Config = Engine)
|
||||
class CINEMATICCAMERA_API UCineCameraComponent : public UCameraComponent
|
||||
```
|
||||
|
||||
## 示例代码:
|
||||
|
||||
```cpp
|
||||
UCLASS(Blueprintable, HideFunctions = (MyFunc1, MyEvent2),hideCategories= EventCategory2)
|
||||
class INSIDER_API AMyClass_HideFunctions :public AActor
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UFUNCTION(BlueprintCallable)
|
||||
void MyFunc1() {}
|
||||
|
||||
UFUNCTION(BlueprintCallable)
|
||||
void MyFunc2() {}
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "FuncCategory1")
|
||||
void MyFuncInCategory1() {}
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "FuncCategory2")
|
||||
void MyFuncInCategory2() {}
|
||||
|
||||
public:
|
||||
UFUNCTION(BlueprintImplementableEvent)
|
||||
void MyEvent1();
|
||||
|
||||
UFUNCTION(BlueprintImplementableEvent)
|
||||
void MyEvent2();
|
||||
|
||||
UFUNCTION(BlueprintImplementableEvent, Category = "EventCategory1")
|
||||
void MyEventInCategory1();
|
||||
|
||||
UFUNCTION(BlueprintImplementableEvent, Category = "EventCategory2")
|
||||
void MyEventInCategory2();
|
||||
};
|
||||
|
||||
UCLASS(Blueprintable, ShowFunctions = (MyEvent2),showCategories= EventCategory2)
|
||||
class INSIDER_API AMyClass_ShowFunctions :public AMyClass_HideFunctions
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
|
||||
};
|
||||
```
|
||||
|
||||
## 示例效果:
|
||||
|
||||
发现Callable的函数是依然可以调用的。
|
||||
|
||||

|
||||
|
||||
在HideFunction子类里,函数重载会发现少两个
|
||||
|
||||

|
||||
|
||||
在ShowFunction的子类里可以重新打开Event2和EventCategory2
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
原理显示,HideFunctions其实只能填函数名字,想要隐藏一个目录下的函数,是需要HideCategories再额外定义的。
|
||||
|
||||
```cpp
|
||||
bool IsFunctionHiddenFromClass( const UFunction* InFunction,const UClass* Class )
|
||||
{
|
||||
bool bResult = false;
|
||||
if( InFunction )
|
||||
{
|
||||
bResult = Class->IsFunctionHidden( *InFunction->GetName() );
|
||||
|
||||
static const FName FunctionCategory(TEXT("Category")); // FBlueprintMetadata::MD_FunctionCategory
|
||||
if( !bResult && InFunction->HasMetaData( FunctionCategory ) )
|
||||
{
|
||||
FString const& FuncCategory = InFunction->GetMetaData(FunctionCategory);
|
||||
bResult = FEditorCategoryUtils::IsCategoryHiddenFromClass(Class, FuncCategory);
|
||||
}
|
||||
}
|
||||
return bResult;
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 104 KiB |
After Width: | Height: | Size: 116 KiB |
After Width: | Height: | Size: 59 KiB |
@@ -0,0 +1,24 @@
|
||||
# NeedsDeferredDependencyLoading
|
||||
|
||||
- **引擎模块:** Blueprint
|
||||
- **元数据类型:** bool
|
||||
- **作用机制:** 在ClassFlags增加[CLASS_NeedsDeferredDependencyLoading](../../../Flags/EClassFlags/CLASS_NeedsDeferredDependencyLoading.md)
|
||||
|
||||
## 源码例子:
|
||||
|
||||
```cpp
|
||||
UCLASS(NeedsDeferredDependencyLoading, MinimalAPI)
|
||||
class UBlueprintGeneratedClass : public UClass, public IBlueprintPropertyGuidProvider
|
||||
{
|
||||
}
|
||||
```
|
||||
|
||||
## 原理:
|
||||
|
||||
```cpp
|
||||
if (ClassFlags.HasAnyFlags(EClassFlags.NeedsDeferredDependencyLoading) && !IsChildOf(Session.UClass))
|
||||
{
|
||||
// CLASS_NeedsDeferredDependencyLoading can only be set on classes derived from UClass
|
||||
this.LogError($"'NeedsDeferredDependencyLoading' is set on '{SourceName}' but the flag can only be used with classes derived from UClass.");
|
||||
}
|
||||
```
|
@@ -0,0 +1,8 @@
|
||||
# NotBlueprintType
|
||||
|
||||
- **功能描述:** 不可当做变量类型
|
||||
- **引擎模块:** Blueprint
|
||||
- **元数据类型:** bool
|
||||
- **作用机制:** Meta移除[BlueprintType](../../../Meta/Blueprint/BlueprintType.md)
|
||||
- **关联项:** [BlueprintType](BlueprintType/BlueprintType.md)
|
||||
- **常用程度:★★★★**
|
@@ -0,0 +1,9 @@
|
||||
# NotBlueprintable
|
||||
|
||||
- **功能描述:** 不可在蓝图里继承,隐含作用也不可当作变量
|
||||
- **引擎模块:** Blueprint
|
||||
- **元数据类型:** bool
|
||||
- **作用机制:** 在Meta去除[IsBlueprintBase](../../../Meta/Blueprint/IsBlueprintBase.md)和[BlueprintType](../../../Meta/Blueprint/BlueprintType.md)
|
||||
- **关联项:** [Blueprintable](Blueprintable/Blueprintable.md)
|
||||
- **常用程度:★★★★**
|
||||
|
@@ -0,0 +1,31 @@
|
||||
# ShowFunctions
|
||||
|
||||
- **功能描述:** 在子类的函数覆盖列表里重新打开某些函数。
|
||||
- **引擎模块:** Blueprint
|
||||
- **元数据类型:** strings=(abc,"d|e","x|y|z")
|
||||
- **作用机制:** 在Meta中去除[HideFunctions](../../../Meta/Blueprint/HideFunctions.md)
|
||||
- **关联项:** [HideFunctions](HideFunctions/HideFunctions.md)
|
||||
- **常用程度:★★**
|
||||
|
||||
在子类的函数覆盖列表里重新打开某些函数。
|
||||
|
||||
测试代码和效果图见HideFunctions。
|
||||
|
||||
## 原理:
|
||||
|
||||
UHT中的代码,可见ShowFunctions的作用就是去除掉之前设置的HideFunctions。
|
||||
|
||||
```cpp
|
||||
private void MergeCategories()
|
||||
{
|
||||
MergeShowCategories();
|
||||
|
||||
// Merge ShowFunctions and HideFunctions
|
||||
AppendStringListMetaData(SuperClass, UhtNames.HideFunctions, HideFunctions);
|
||||
foreach (string value in ShowFunctions)
|
||||
{
|
||||
HideFunctions.RemoveSwap(value);
|
||||
}
|
||||
ShowFunctions.Clear();
|
||||
}
|
||||
```
|
@@ -0,0 +1,263 @@
|
||||
# SparseClassDataType
|
||||
|
||||
- **功能描述:** 让Actor的一些重复不变的数据存放在一个共同的结构里,以达到减少内容使用量的目的
|
||||
- **引擎模块:** Blueprint
|
||||
- **元数据类型:** string="abc"
|
||||
- **作用机制:** 在Meta中增加[SparseClassDataTypes](../../../../Meta/Blueprint/SparseClassDataTypes.md)
|
||||
- **关联项:** [NoGetter](../../../../Meta/SparseDataType/NoGetter/NoGetter.md)
|
||||
- **常用程度:** ★★★
|
||||
|
||||
这是个重构和性能优化的点。在使用SparseClassDataType的时候,分为两种情况,一是以前的Actor想利用这个特性来优化,二是新创建的Actor一开始就想使用这个特性。
|
||||
|
||||
## 示例用法:
|
||||
|
||||
分为两部分:
|
||||
|
||||
一,旧的Actor存在冗余属性
|
||||
|
||||
简而言之是那些不会在BP改变的属性。C++方面,如果有修改这些属性,也要修改为使用Get函数来获得,从而转到SparseDataStruct里去。
|
||||
|
||||
```cpp
|
||||
UCLASS(Blueprintable, BlueprintType)
|
||||
class INSIDER_API AMyActor_SparseClassDataTypes :public AActor
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(EditDefaultsOnly)
|
||||
int32 MyInt_EditDefaultOnly = 123;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly)
|
||||
int32 MyInt_BlueprintReadOnly = 1024;
|
||||
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
|
||||
FString MyString_EditDefault_ReadOnly = TEXT("MyName");
|
||||
|
||||
UPROPERTY(EditAnywhere)
|
||||
float MyFloat_EditAnywhere = 555.f;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite)
|
||||
float MyFloat_BlueprintReadWrite = 666.f;
|
||||
};
|
||||
```
|
||||
|
||||
改为以下的代码。把属性用WITH_EDITORONLY_DATA包起来,以示意只在editor下做操作,在runtime是已经消除的。加上_DEPRECATED后缀标记也是为了进一步提醒原先BP里的访问要去除。重载MoveDataToSparseClassDataStruct以便把现在BP Class Defaults里配置的值拷贝给新的FMySparseClassData结构数值。
|
||||
|
||||
```cpp
|
||||
USTRUCT(BlueprintType)
|
||||
struct FMySparseClassData
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditDefaultsOnly)
|
||||
int32 MyInt_EditDefaultOnly = 123;
|
||||
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
|
||||
int32 MyInt_BlueprintReadOnly = 1024;
|
||||
|
||||
// "GetByRef" means that Blueprint graphs access a const ref instead of a copy.
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, meta=(GetByRef))
|
||||
FString MyString_EditDefault_ReadOnly = TEXT("MyName");
|
||||
};
|
||||
|
||||
UCLASS(Blueprintable, BlueprintType,SparseClassDataTypes= MySparseClassData)
|
||||
class INSIDER_API AMyActor_SparseClassDataTypes :public AActor
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
#if WITH_EDITOR
|
||||
// ~ This function transfers existing data into FMySparseClassData.
|
||||
virtual void MoveDataToSparseClassDataStruct() const override;
|
||||
#endif // WITH_EDITOR
|
||||
public:
|
||||
#if WITH_EDITORONLY_DATA
|
||||
UPROPERTY()
|
||||
int32 MyInt_EditDefaultOnly_DEPRECATED = 123;
|
||||
|
||||
UPROPERTY()
|
||||
int32 MyInt_BlueprintReadOnly_DEPRECATED = 1024;
|
||||
|
||||
UPROPERTY()
|
||||
FString MyString_EditDefault_ReadOnly_DEPRECATED = TEXT("MyName");
|
||||
#endif // WITH_EDITORONLY_DATA
|
||||
public:
|
||||
UPROPERTY(EditAnywhere)
|
||||
float MyFloat_EditAnywhere = 555.f;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite)
|
||||
float MyFloat_BlueprintReadWrite = 666.f;
|
||||
};
|
||||
|
||||
//cpp
|
||||
#if WITH_EDITOR
|
||||
void AMyActor_SparseClassDataTypes::MoveDataToSparseClassDataStruct() const
|
||||
{
|
||||
// make sure we don't overwrite the sparse data if it has been saved already
|
||||
UBlueprintGeneratedClass* BPClass = Cast<UBlueprintGeneratedClass>(GetClass());
|
||||
if (BPClass == nullptr || BPClass->bIsSparseClassDataSerializable == true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Super::MoveDataToSparseClassDataStruct();
|
||||
|
||||
#if WITH_EDITORONLY_DATA
|
||||
// Unreal Header Tool (UHT) will create GetMySparseClassData automatically.
|
||||
FMySparseClassData* SparseClassData = GetMySparseClassData();
|
||||
|
||||
// Modify these lines to include all Sparse Class Data properties.
|
||||
SparseClassData->MyInt_EditDefaultOnly = MyInt_EditDefaultOnly_DEPRECATED;
|
||||
SparseClassData->MyInt_BlueprintReadOnly = MyInt_BlueprintReadOnly_DEPRECATED;
|
||||
SparseClassData->MyString_EditDefault_ReadOnly = MyString_EditDefault_ReadOnly_DEPRECATED;
|
||||
#endif // WITH_EDITORONLY_DATA
|
||||
|
||||
}
|
||||
#endif // WITH_EDITOR
|
||||
```
|
||||
|
||||
在BP的PostLoad加载之后,会自动的调用MoveDataToSparseClassDataStruct,所以要在内部检测bIsSparseClassDataSerializable.
|
||||
|
||||
```cpp
|
||||
void UBlueprintGeneratedClass::PostLoadDefaultObject(UObject* Object)
|
||||
{
|
||||
FScopeLock SerializeAndPostLoadLock(&SerializeAndPostLoadCritical);
|
||||
|
||||
Super::PostLoadDefaultObject(Object);
|
||||
|
||||
if (Object == ClassDefaultObject)
|
||||
{
|
||||
// Rebuild the custom property list used in post-construct initialization logic. Note that PostLoad() may have altered some serialized properties.
|
||||
UpdateCustomPropertyListForPostConstruction();
|
||||
|
||||
// Restore any property values from config file
|
||||
if (HasAnyClassFlags(CLASS_Config))
|
||||
{
|
||||
ClassDefaultObject->LoadConfig();
|
||||
}
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
Object->MoveDataToSparseClassDataStruct();
|
||||
|
||||
if (Object->GetSparseClassDataStruct())
|
||||
{
|
||||
// now that any data has been moved into the sparse data structure we can safely serialize it
|
||||
bIsSparseClassDataSerializable = true;
|
||||
}
|
||||
|
||||
ConformSparseClassData(Object);
|
||||
#endif
|
||||
}
|
||||
```
|
||||
|
||||
在UClass下
|
||||
|
||||
```cpp
|
||||
protected:
|
||||
/** This is where we store the data that is only changed per class instead of per instance */
|
||||
void* SparseClassData;
|
||||
|
||||
/** The struct used to store sparse class data. */
|
||||
UScriptStruct* SparseClassDataStruct;
|
||||
|
||||
在构造UClass的时候,会SetSparseClassDataStruct来把结构传进去,因此就把结构关联起来。
|
||||
UClass* Z_Construct_UClass_AMyActor_SparseClassDataTypes()
|
||||
{
|
||||
if (!Z_Registration_Info_UClass_AMyActor_SparseClassDataTypes.OuterSingleton)
|
||||
{
|
||||
UECodeGen_Private::ConstructUClass(Z_Registration_Info_UClass_AMyActor_SparseClassDataTypes.OuterSingleton, Z_Construct_UClass_AMyActor_SparseClassDataTypes_Statics::ClassParams);
|
||||
Z_Registration_Info_UClass_AMyActor_SparseClassDataTypes.OuterSingleton->SetSparseClassDataStruct(AMyActor_SparseClassDataTypes::StaticGetMySparseClassDataScriptStruct());
|
||||
}
|
||||
return Z_Registration_Info_UClass_AMyActor_SparseClassDataTypes.OuterSingleton;
|
||||
}
|
||||
```
|
||||
|
||||
注意此时BP里没法blueprint get 那些ReadOnly的变量的,因为有_DEPRECATED在占用着。一种方法是自己再额外定义Gettter方法:
|
||||
|
||||
```cpp
|
||||
UFUNCTION(BlueprintPure)
|
||||
int32 GetMyMyInt_BlueprintReadOnly()const
|
||||
{
|
||||
return GetMySparseClassData()->MyInt_BlueprintReadOnly;
|
||||
}
|
||||
```
|
||||
|
||||
二,另一种方法是在MoveDataToSparseClassDataStruct之后(记得要打开编辑器,并且打开子类BP蓝图后保存)就干脆删除掉AMyActor_SparseClassDataTypes里的冗余属性,全部使用FMySparseClassData中的值。从而变成:
|
||||
|
||||
```cpp
|
||||
USTRUCT(BlueprintType)
|
||||
struct FMySparseClassData
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditDefaultsOnly)
|
||||
int32 MyInt_EditDefaultOnly = 123;
|
||||
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
|
||||
int32 MyInt_BlueprintReadOnly = 1024;
|
||||
|
||||
// "GetByRef" means that Blueprint graphs access a const ref instead of a copy.
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, meta=(GetByRef))
|
||||
FString MyString_EditDefault_ReadOnly = TEXT("MyName");
|
||||
};
|
||||
|
||||
UCLASS(Blueprintable, BlueprintType,SparseClassDataTypes= MySparseClassData)
|
||||
class INSIDER_API AMyActor_SparseClassDataTypes :public AActor
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(EditAnywhere)
|
||||
float MyFloat_EditAnywhere = 555.f;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite)
|
||||
float MyFloat_BlueprintReadWrite = 666.f;
|
||||
};
|
||||
```
|
||||
|
||||
这样就达到了最终的效果,这个效果也对新的Actor要采用冗余属性的结果也是一样的。注意此时,在BP里是依然可以访问BlueprintReadOnly属性的,因为UHT和BP系统已经帮我们加了一层访问方便的控制。
|
||||
|
||||
## 示例效果:
|
||||
|
||||
UHT会帮我们生成C++访问函数:
|
||||
|
||||
```cpp
|
||||
#define FID_Hello_Source_Insider_Class_Trait_MyClass_SparseClassDataTypes_h_30_SPARSE_DATA \
|
||||
FMySparseClassData* GetMySparseClassData(); \
|
||||
FMySparseClassData* GetMySparseClassData() const; \
|
||||
const FMySparseClassData* GetMySparseClassData(EGetSparseClassDataMethod GetMethod) const; \
|
||||
static UScriptStruct* StaticGetMySparseClassDataScriptStruct(); \
|
||||
int32 GetMyInt_EditDefaultOnly() \
|
||||
{ \
|
||||
return GetMySparseClassData()->MyInt_EditDefaultOnly; \
|
||||
} \
|
||||
int32 GetMyInt_EditDefaultOnly() const \
|
||||
{ \
|
||||
return GetMySparseClassData()->MyInt_EditDefaultOnly; \
|
||||
} \
|
||||
int32 GetMyInt_BlueprintReadOnly() \
|
||||
{ \
|
||||
return GetMySparseClassData()->MyInt_BlueprintReadOnly; \
|
||||
} \
|
||||
int32 GetMyInt_BlueprintReadOnly() const \
|
||||
{ \
|
||||
return GetMySparseClassData()->MyInt_BlueprintReadOnly; \
|
||||
} \
|
||||
const FString& GetMyString_EditDefault_ReadOnly() \
|
||||
{ \
|
||||
return GetMySparseClassData()->MyString_EditDefault_ReadOnly; \
|
||||
} \
|
||||
const FString& GetMyString_EditDefault_ReadOnly() const \
|
||||
{ \
|
||||
return GetMySparseClassData()->MyString_EditDefault_ReadOnly; \
|
||||
}
|
||||
```
|
||||
|
||||
在BP中依然可以访问:
|
||||
|
||||

|
||||
|
||||
在Class Defaults里也可以改变值:
|
||||
|
||||

|
After Width: | Height: | Size: 8.8 KiB |
After Width: | Height: | Size: 18 KiB |
@@ -0,0 +1,51 @@
|
||||
# AdvancedClassDisplay
|
||||
|
||||
- **功能描述:** 把该类下的所有属性都默认显示在高级目录下
|
||||
- **引擎模块:** Category
|
||||
- **元数据类型:** bool
|
||||
- **作用机制:** 在Meta增加[AdvancedClassDisplay](../../../../Meta/DetailsPanel/AdvancedClassDisplay.md)
|
||||
- **常用程度:★★★★**
|
||||
|
||||
让这个类的所有属性显示在本身类的Detail面板的“高级”栏目下显示。
|
||||
|
||||
但是可以通过在单个属性上使用SimpleDisplay来重载掉。在搜索了一番源码后,发现使用AdvancedClassDisplay的只有3个Actor,且这3个Actor里都没有再定义属性。
|
||||
|
||||
## 示例代码:
|
||||
|
||||
```cpp
|
||||
UCLASS(Blueprintable,AdvancedClassDisplay)
|
||||
class INSIDER_API UMyClass_AdvancedClassDisplay :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
int32 MyProperty;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite,SimpleDisplay)
|
||||
int32 MyProperty_Simple;
|
||||
};
|
||||
|
||||
UCLASS(Blueprintable)
|
||||
class INSIDER_API UMyClass_NoAdvancedClassDisplay :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
int32 MyProperty;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, SimpleDisplay)
|
||||
int32 MyProperty_Simple;
|
||||
};
|
||||
```
|
||||
|
||||
## 示例效果:
|
||||
|
||||
MyProperty_Simple即使在AdvancedClassDisplay的类中也依然是简单的显示。
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
```cpp
|
||||
// Property is advanced if it is marked advanced or the entire class is advanced and the property not marked as simple
|
||||
static const FName Name_AdvancedClassDisplay("AdvancedClassDisplay");
|
||||
bool bAdvanced = Property.IsValid() ? ( Property->HasAnyPropertyFlags(CPF_AdvancedDisplay) || ( !Property->HasAnyPropertyFlags( CPF_SimpleDisplay ) && Property->GetOwnerClass() && Property->GetOwnerClass()->GetBoolMetaData(Name_AdvancedClassDisplay) ) ) : false;
|
||||
```
|
After Width: | Height: | Size: 35 KiB |
@@ -0,0 +1,25 @@
|
||||
# AutoCollapseCategories
|
||||
|
||||
- **功能描述:** AutoCollapseCategories说明符使父类上的 AutoExpandCategories 说明符的列出类别的效果无效。
|
||||
- **引擎模块:** Category
|
||||
- **元数据类型:** strings=(abc,"d|e","x|y|z")
|
||||
- **作用机制:** 在Meta中增加[AutoCollapseCategories](../../../../Meta/DetailsPanel/AutoCollapseCategories.md),去除[AutoExpandCategories](../../../../Meta/DetailsPanel/AutoExpandCategories.md)
|
||||
- **关联项:** [DontAutoCollapseCategories](../DontAutoCollapseCategories.md)、[AutoExpandCategories](../AutoExpandCategories/AutoExpandCategories.md)
|
||||
- **常用程度:★**
|
||||
|
||||
## 示例代码:
|
||||
|
||||
```cpp
|
||||
UCLASS(Blueprintable, AutoCollapseCategories = ("MyGroup2|MyGroup22"))
|
||||
class INSIDER_API UMyClass_AutoCollapseCategories :public UMyClass_AutoExpandCategories
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
};
|
||||
```
|
||||
|
||||
## 示例结果:
|
||||
|
||||
关闭了Group22的展开,但是444的展开依然继承了
|
||||
|
||||

|
After Width: | Height: | Size: 47 KiB |
@@ -0,0 +1,111 @@
|
||||
# AutoExpandCategories
|
||||
|
||||
- **功能描述:** 指定此类的对象在细节面板中应该自动展开的Category。
|
||||
- **引擎模块:** Category
|
||||
- **元数据类型:** strings=(abc,"d|e","x|y|z")
|
||||
- **作用机制:** 在Meta中去除[AutoCollapseCategories](../../../../Meta/DetailsPanel/AutoCollapseCategories.md),增加[AutoExpandCategories](../../../../Meta/DetailsPanel/AutoExpandCategories.md)
|
||||
- **关联项:** [AutoCollapseCategories](../AutoCollapseCategories/AutoCollapseCategories.md)
|
||||
- **常用程度:★**
|
||||
|
||||
指定此类的对象在细节面板中应该自动展开的Category。
|
||||
|
||||
- 这里面的Category可以填多个,对应本类中属性身上定义的Category。
|
||||
- 值得注意的是,编辑器会自动的保存属性目录的展开关闭状态。影响属性是否展开,还会受到DetailPropertyExpansion的配置的影响,在打开窗口后,SDetailsViewBase::UpdateFilteredDetails()会保存当前展开的属性项目,应该是为了下次打开的时候自动展开。保存的代码为GConfig->SetSingleLineArray(TEXT("DetailPropertyExpansion"), *Struct->GetName(), ExpandedPropertyItems, GEditorPerProjectIni); 从而在\Hello\Saved\Config\WindowsEditor\EditorPerProjectUserSettings.ini下保存。因此为了更好的测试该元数据的作用状态。应该手动先清除一下ini中的保存值后再测试。
|
||||
|
||||
```cpp
|
||||
[DetailCategories]
|
||||
MyClass_AutoExpandCategories.MyClass_AutoExpandCategories=False
|
||||
MyClass_AutoExpandCategories.MyGroup1=False
|
||||
MyClass_AutoExpandCategories.MyGroup2=False
|
||||
MyClass_AutoExpandCategories.MyGroup3=True
|
||||
MyClass_AutoExpandCategories.MyGroup4=True
|
||||
|
||||
[DetailPropertyExpansion]
|
||||
GeometryCache="\"Object.GeometryCache.Materials\" \"Object.GeometryCache.Tracks\" "
|
||||
Object="\"Object.MyGroup2.MyGroup2|MyGroup22\" \"Object.MyGroup4.MyGroup4|MyGroup44\" \"Object.MyGroup4.MyGroup4|MyGroup44.MyGroup4|MyGroup44|MyGroup444\" "
|
||||
GeometryCacheCodecV1="\"Object.GeometryCache.TopologyRanges\" "
|
||||
GeometryCacheCodecBase="\"Object.GeometryCache.TopologyRanges\" "
|
||||
MassSettings="\"Object.Mass\" "
|
||||
DeveloperSettings=
|
||||
SmartObjectSettings="\"Object.SmartObject\" "
|
||||
MyClass_ShowCategories=
|
||||
MyClass_ShowCategoriesChild=
|
||||
MyClass_DontCollapseCategories="\"Object.MyGroup2.MyGroup2|MyGroup22\" \"Object.MyGroup3.MyGroup3|MyGroup33\" \"Object.MyGroup3.MyGroup3|MyGroup33.MyGroup3|MyGroup33|MyGroup333\" "
|
||||
MyClass_CollapseCategories="\"Object.MyGroup2.MyGroup2|MyGroup22\" \"Object.MyGroup3.MyGroup3|MyGroup33\" \"Object.MyGroup3.MyGroup3|MyGroup33.MyGroup3|MyGroup33|MyGroup333\" "
|
||||
MyClass_AutoExpandCategories="\"Object.MyGroup2.MyGroup2|MyGroup22\" \"Object.MyGroup4.MyGroup4|MyGroup44\" \"Object.MyGroup4.MyGroup4|MyGroup44.MyGroup4|MyGroup44|MyGroup444\" "
|
||||
MyClass_AutoExpandCategoriesCompare=
|
||||
MyClass_AutoCollapseCategories="\"Object.MyGroup2.MyGroup2|MyGroup22\" \"Object.MyGroup4.MyGroup4|MyGroup44\" \"Object.MyGroup4.MyGroup4|MyGroup44.MyGroup4|MyGroup44|MyGroup444\" "
|
||||
```
|
||||
|
||||
根据代码搜索规则,AutoExpandCategories 和AutoCollapseCategories的值要用空格隔开。顶层目录一开始默认就是打开的,所以AutoExpandCategories 一般用在子层目录。而且还有个限制是必须一级一级都打开。直接打开最子目录还不行。因此在示例代码里必须要把中间的二级目录"MyGroup4|MyGroup44"也都得写上。
|
||||
|
||||
## 示例代码:
|
||||
|
||||
```cpp
|
||||
UCLASS(Blueprintable, AutoExpandCategories = ("MyGroup2|MyGroup22", "MyGroup4|MyGroup44","MyGroup4|MyGroup44|MyGroup444"))
|
||||
class INSIDER_API UMyClass_AutoExpandCategories :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
int Property_NotInGroup;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MyGroup1")
|
||||
int Property_Group1;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MyGroup2")
|
||||
int Property_Group2;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MyGroup2|MyGroup22")
|
||||
int Property_Group22;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MyGroup3|MyGroup33")
|
||||
int Property_Group33;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MyGroup4|MyGroup44|MyGroup444")
|
||||
int Property_Group444;
|
||||
};
|
||||
|
||||
源码里最复杂的样例:
|
||||
UCLASS(Config = Engine, PerObjectConfig, BlueprintType, AutoCollapseCategories = ("Data Layer|Advanced"), AutoExpandCategories = ("Data Layer|Editor", "Data Layer|Advanced|Runtime"))
|
||||
class ENGINE_API UDataLayerInstance : public UObject
|
||||
|
||||
可以打开子目录:UCLASS(Blueprintable, AutoExpandCategories = ("MyGroup2|MyGroup22", "MyGroup4|MyGroup44","MyGroup4|MyGroup44|MyGroup444"))
|
||||
不可以打开子目录:UCLASS(Blueprintable, AutoExpandCategories = ("MyGroup2|MyGroup22", "MyGroup4|MyGroup44|MyGroup444"))
|
||||
```
|
||||
|
||||
## 示例效果:
|
||||
|
||||
在Saved\EditorPerProjectUserSettings中删除掉DetailCategories以及DetailPropertyExpansion下的MyClass_AutoCollapseCategories值之后再用testprops class=MyClass_AutoExpandCategories来打开该窗口:
|
||||
|
||||
通过对比可以看出Expand确实可以自动展开子目录方便立马编辑。要求是AutoExpandCategories 里填的目录要和属性上的Category匹配
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
UClass里提取AutoExpandCategories和AutoCollapseCategories的元数据来判断Category是否应该显示。
|
||||
|
||||
```cpp
|
||||
if (BaseClass->IsAutoExpandCategory(*CategoryName.ToString()) && !BaseClass->IsAutoCollapseCategory(*CategoryName.ToString()))
|
||||
{
|
||||
NewCategoryNode->SetNodeFlags(EPropertyNodeFlags::Expanded, true);
|
||||
}
|
||||
|
||||
bool UClass::IsAutoExpandCategory(const TCHAR* InCategory) const
|
||||
{
|
||||
static const FName NAME_AutoExpandCategories(TEXT("AutoExpandCategories"));
|
||||
if (const FString* AutoExpandCategories = FindMetaData(NAME_AutoExpandCategories))
|
||||
{
|
||||
return !!FCString::StrfindDelim(**AutoExpandCategories, InCategory, TEXT(" "));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UClass::IsAutoCollapseCategory(const TCHAR* InCategory) const
|
||||
{
|
||||
static const FName NAME_AutoCollapseCategories(TEXT("AutoCollapseCategories"));
|
||||
if (const FString* AutoCollapseCategories = FindMetaData(NAME_AutoCollapseCategories))
|
||||
{
|
||||
return !!FCString::StrfindDelim(**AutoCollapseCategories, InCategory, TEXT(" "));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
```
|
After Width: | Height: | Size: 51 KiB |
@@ -0,0 +1,51 @@
|
||||
# ClassGroup
|
||||
|
||||
- **功能描述:** 指定组件在Actor的AddComponent面板里的分组,以及在蓝图右键菜单中的分组。
|
||||
- **引擎模块:** Category, Editor
|
||||
- **元数据类型:** string="a|b|c"
|
||||
- **作用机制:** 在Meta中增加[ClassGroupNames](../../../../Meta/DetailsPanel/ClassGroupNames.md)
|
||||
- **常用程度:★★★**
|
||||
|
||||
指定组件在Actor的AddComponent面板里的分组,以及在蓝图右键菜单中的分组。
|
||||
|
||||
## 示例代码:
|
||||
|
||||
```cpp
|
||||
|
||||
//ClassGroup 必须是BlueprintSpawnableComponent才有效
|
||||
/*
|
||||
(BlueprintSpawnableComponent = , BlueprintType = true, ClassGroupNames = MyGroup|MySubGroup, IncludePath = Class/MyComponent_ClassGroup.h, IsBlueprintBase = true, ModuleRelativePath = Class/MyComponent_ClassGroup.h)
|
||||
*/
|
||||
UCLASS(Blueprintable,ClassGroup="MyGroup|MySubGroup", meta = (BlueprintSpawnableComponent))
|
||||
class INSIDER_API UMyComponent_ClassGroup:public UActorComponent
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
};
|
||||
```
|
||||
|
||||
## 示例效果:
|
||||
|
||||
在添加组件的时候:
|
||||
|
||||

|
||||
|
||||
在蓝图中右键AddComponent,该测试只对带有BlueprintSpawnableComponent的UActorComponent起作用,因为只有BlueprintSpawnableComponent才可在蓝图中动态添加组件。
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
Metadata中的ClassGroupNames,被使用方法是UClass::GetClassGroupNames,这个又是在BlueprintComponentNodeSpawner中被使用。还有一个使用地方是ComponentTypeRegistry.cpp中,也是在判断Component。因此这个ClassGroup确实是只被Component使用的。
|
||||
|
||||
```cpp
|
||||
static FText GetDefaultMenuCategory(const TSubclassOf<UActorComponent> ComponentClass)
|
||||
{
|
||||
TArray<FString> ClassGroupNames;
|
||||
ComponentClass->GetClassGroupNames(ClassGroupNames);
|
||||
|
||||
if (FKismetEditorUtilities::IsClassABlueprintSpawnableComponent(Class))
|
||||
{
|
||||
TArray<FString> ClassGroupNames;
|
||||
Class->GetClassGroupNames(ClassGroupNames);
|
||||
```
|
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 11 KiB |
@@ -0,0 +1,64 @@
|
||||
# CollapseCategories
|
||||
|
||||
- **功能描述:** 在类的属性面板里隐藏所有带Category的属性,但是只对带有多个嵌套Category的属性才起作用。
|
||||
- **引擎模块:** Category
|
||||
- **元数据类型:** bool
|
||||
- **作用机制:** 在ClassFlags中添加[CLASS_CollapseCategories](../../../../Flags/EClassFlags/CLASS_CollapseCategories.md)
|
||||
- **关联项:** [DontCollapseCategories](../DontCollapseCategories.md)
|
||||
- **常用程度:★★**
|
||||
|
||||
在类的属性面板里隐藏所有带Category的属性,但是只对带有多个嵌套Category的属性才起作用。
|
||||
|
||||
## 示例代码:
|
||||
|
||||
```cpp
|
||||
/*
|
||||
ClassFlags: CLASS_MatchedSerializers | CLASS_Native | CLASS_CollapseCategories | CLASS_RequiredAPI | CLASS_TokenStreamAssembled | CLASS_Intrinsic | CLASS_Constructed
|
||||
*/
|
||||
UCLASS(Blueprintable, CollapseCategories)
|
||||
class INSIDER_API UMyClass_CollapseCategories :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
int Property_NotInGroup;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MyGroup1")
|
||||
int Property_Group1;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MyGroup2|MyGroup22")
|
||||
int Property_Group22;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MyGroup3|MyGroup33|MyGroup333")
|
||||
int Property_Group333;
|
||||
};
|
||||
|
||||
/*
|
||||
ClassFlags: CLASS_MatchedSerializers | CLASS_Native | CLASS_RequiredAPI | CLASS_TokenStreamAssembled | CLASS_Intrinsic | CLASS_Constructed
|
||||
*/
|
||||
UCLASS(Blueprintable, dontCollapseCategories)
|
||||
class INSIDER_API UMyClass_DontCollapseCategories :public UMyClass_CollapseCategories
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
};
|
||||
```
|
||||
|
||||
## 示例效果:
|
||||
|
||||
第一个是UMyClass_CollapseCategories 的效果,第二个是UMyClass_DontCollapseCategories 的效果,可见一些属性被隐藏了起来。
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
```cpp
|
||||
if (Specifier == TEXT("collapseCategories"))
|
||||
{
|
||||
// Class' properties should not be shown categorized in the editor.
|
||||
ClassFlags |= CLASS_CollapseCategories;
|
||||
}
|
||||
else if (Specifier == TEXT("dontCollapseCategories"))
|
||||
{
|
||||
// Class' properties should be shown categorized in the editor.
|
||||
ClassFlags &= ~CLASS_CollapseCategories;
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 70 KiB |
@@ -0,0 +1,90 @@
|
||||
# ComponentWrapperClass
|
||||
|
||||
- **功能描述:** 指定该类为一个简单的封装类,忽略掉子类的Category相关设置。
|
||||
- **引擎模块:** Category
|
||||
- **元数据类型:** bool
|
||||
- **作用机制:** 在Meta中增加[IgnoreCategoryKeywordsInSubclasses](../../../../Meta/DetailsPanel/IgnoreCategoryKeywordsInSubclasses.md)
|
||||
- **常用程度:★★**
|
||||
|
||||
指定该类为一个简单的封装类,忽略掉子类的Category相关设置。
|
||||
|
||||
如名字所说,为一个组件的包装类,其实就是一个Actor简单的只包含一个Component。这种简单的包装关系,典型的例子是ALight包装ULightComponent,ASkeletalMeshActor包装USkeletalMeshComponent。
|
||||
|
||||
控制子类上面定义的hideCategories和showCategories都被忽略,而直接采用基类上的目录定义,也就是本组件包装类上的目录设置。当前源码里只有BlueprintEditorUtils.cpp在用,而这是蓝图打开的过程,因此这个只有在双击打开一个蓝图的时候才起作用。普通的UObject类,直接用testprops创建的窗口,因为不是双击打开蓝图,因此是无法生效的。
|
||||
|
||||
ComponentWrapperClass在源码里搜了一下,只有一些Actor在用。
|
||||
|
||||
## 示例代码:
|
||||
|
||||
```cpp
|
||||
UCLASS(Blueprintable, BlueprintType, ComponentWrapperClass, hideCategories = MyGroup3) //依然会显示出Property_Group3
|
||||
class AMyActor_ComponentWrapperClass : public AActor
|
||||
{
|
||||
GENERATED_UCLASS_BODY()
|
||||
public:
|
||||
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
|
||||
class UPointLightComponent* PointLightComponent;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = MyGroup3)
|
||||
int Property_Group3;
|
||||
};
|
||||
|
||||
UCLASS(Blueprintable, BlueprintType, hideCategories = MyGroup3)
|
||||
class AMyActor_NoComponentWrapperClass : public AActor //Property_Group3会被隐藏
|
||||
{
|
||||
GENERATED_UCLASS_BODY()
|
||||
public:
|
||||
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
|
||||
class UPointLightComponent* PointLightComponent;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = MyGroup3)
|
||||
int Property_Group3;
|
||||
};
|
||||
```
|
||||
|
||||
## 子类的实际作用效果:
|
||||
|
||||
子类里的MyGroup3即使被隐藏了起来,也还是显示了出来。
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
ComponentWrapperClass实际会造成IgnoreCategoryKeywordsInSubclasses=true的元数据添加。因此在有了IgnoreCategoryKeywordsInSubclasses元数据之后,就不会判断之后的ShowCategories等设置了。
|
||||
|
||||
当前源码里只有BlueprintEditorUtils.cpp在用,而这是蓝图打开的过程,因此这个只有在双击打开一个蓝图的时候才起作用。普通的UObject类,直接用testprops创建的窗口,因为不是双击打开蓝图,因此是无法生效的。
|
||||
|
||||
```cpp
|
||||
case EClassMetadataSpecifier::ComponentWrapperClass:
|
||||
MetaData.Add(NAME_IgnoreCategoryKeywordsInSubclasses, TEXT("true")); //"IgnoreCategoryKeywordsInSubclasses"
|
||||
break;
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
E:\P4V\Engine\Source\Editor\UnrealEd\Private\Kismet2\BlueprintEditorUtils.cpp
|
||||
void FBlueprintEditorUtils::RecreateClassMetaData(UBlueprint* Blueprint, UClass* Class, bool bRemoveExistingMetaData)
|
||||
|
||||
if (!ParentClass->HasMetaData(FBlueprintMetadata::MD_IgnoreCategoryKeywordsInSubclasses)) //如果没有这个设置
|
||||
{
|
||||
// we want the categories just as they appear in the parent class
|
||||
// (set bHomogenize to false) - especially since homogenization
|
||||
// could inject spaces
|
||||
|
||||
//以下这些操作是当没有这个设置的时候,子类会继承父类的目录设置。
|
||||
FEditorCategoryUtils::GetClassHideCategories(ParentClass, AllHideCategories, /*bHomogenize =*/false);
|
||||
if (ParentClass->HasMetaData(TEXT("ShowCategories")))
|
||||
{
|
||||
Class->SetMetaData(TEXT("ShowCategories"), *ParentClass->GetMetaData("ShowCategories"));
|
||||
}
|
||||
if (ParentClass->HasMetaData(TEXT("AutoExpandCategories")))
|
||||
{
|
||||
Class->SetMetaData(TEXT("AutoExpandCategories"), *ParentClass->GetMetaData("AutoExpandCategories"));
|
||||
}
|
||||
if (ParentClass->HasMetaData(TEXT("AutoCollapseCategories")))
|
||||
{
|
||||
Class->SetMetaData(TEXT("AutoCollapseCategories"), *ParentClass->GetMetaData("AutoCollapseCategories"));
|
||||
}
|
||||
if (ParentClass->HasMetaData(TEXT("PrioritizeCategories")))
|
||||
{
|
||||
Class->SetMetaData(TEXT("PrioritizeCategories"), *ParentClass->GetMetaData("PrioritizeCategories"));
|
||||
}
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 92 KiB |
@@ -0,0 +1,64 @@
|
||||
# DontAutoCollapseCategories
|
||||
|
||||
- **功能描述:** 使列出的类别的继承自父类的AutoCollapseCategories说明符无效。
|
||||
- **引擎模块:** Category
|
||||
- **元数据类型:** strings="a,b,c"
|
||||
- **作用机制:** 在Meta中去除[AutoCollapseCategories](../../../Meta/DetailsPanel/AutoCollapseCategories.md)
|
||||
- **关联项:** [AutoCollapseCategories](AutoCollapseCategories/AutoCollapseCategories.md)
|
||||
- **常用程度:★**
|
||||
|
||||
根据代码,只是简单的移除AutoCollapseCategories,和AutoExpandCategories的区别就是不会自动加一个展开。在源码里搜了一下,并没有使用到。而且当前的源码实现有bug,做不到去除。
|
||||
|
||||
```cpp
|
||||
case EClassMetadataSpecifier::AutoExpandCategories:
|
||||
|
||||
FHeaderParser::RequireSpecifierValue(*this, PropSpecifier);
|
||||
|
||||
for (FString& Value : PropSpecifier.Values)
|
||||
{
|
||||
AutoCollapseCategories.RemoveSwap(Value);
|
||||
AutoExpandCategories.AddUnique(MoveTemp(Value));
|
||||
}
|
||||
break;
|
||||
|
||||
case EClassMetadataSpecifier::AutoCollapseCategories:
|
||||
|
||||
FHeaderParser::RequireSpecifierValue(*this, PropSpecifier);
|
||||
|
||||
for (FString& Value : PropSpecifier.Values)
|
||||
{
|
||||
AutoExpandCategories.RemoveSwap(Value);
|
||||
AutoCollapseCategories.AddUnique(MoveTemp(Value));
|
||||
}
|
||||
break;
|
||||
case EClassMetadataSpecifier::DontAutoCollapseCategories:
|
||||
|
||||
FHeaderParser::RequireSpecifierValue(*this, PropSpecifier);
|
||||
|
||||
for (const FString& Value : PropSpecifier.Values)
|
||||
{
|
||||
AutoCollapseCategories.RemoveSwap(Value);//当前AutoCollapseCategories的值还是空的。去除是没有用的
|
||||
}
|
||||
break;
|
||||
|
||||
改动:
|
||||
FUnrealClassDefinitionInfo::MergeClassCategories()放最后:
|
||||
// Merge DontAutoCollapseCategories and AutoCollapseCategories
|
||||
for (const FString& Value : DontAutoCollapseCategories)
|
||||
{
|
||||
AutoCollapseCategories.RemoveSwap(Value);
|
||||
}
|
||||
DontAutoCollapseCategories.Empty();
|
||||
|
||||
改为:
|
||||
case EClassMetadataSpecifier::DontAutoCollapseCategories:
|
||||
|
||||
FHeaderParser::RequireSpecifierValue(*this, PropSpecifier);
|
||||
|
||||
for (FString& Value : PropSpecifier.Values)
|
||||
{
|
||||
DontAutoCollapseCategories.AddUnique(MoveTemp(Value));
|
||||
//AutoCollapseCategories.RemoveSwap(Value);
|
||||
}
|
||||
break;
|
||||
```
|
@@ -0,0 +1,10 @@
|
||||
# DontCollapseCategories
|
||||
|
||||
- **功能描述:** 使继承自基类的CollapseCatogories说明符无效。
|
||||
- **引擎模块:** Category
|
||||
- **元数据类型:** bool
|
||||
- **作用机制:** 在ClassFlags中去除[CLASS_CollapseCategories](../../../Flags/EClassFlags/CLASS_CollapseCategories.md)
|
||||
- **关联项:** [CollapseCategories](CollapseCategories/CollapseCategories.md)
|
||||
- **常用程度:★★**
|
||||
|
||||
理论上是去除类标志上的CLASS_CollapseCategories标志。可以重新打开所有的属性显示。
|
@@ -0,0 +1,100 @@
|
||||
# HideCategories
|
||||
|
||||
- **功能描述:** 在类的ClassDefaults属性面板里隐藏某些Category的属性。
|
||||
- **引擎模块:** Category
|
||||
- **元数据类型:** strings=(abc,"d|e","x|y|z")
|
||||
- **关联项:** [ShowCategories](../ShowCategories/ShowCategories.md)
|
||||
- **常用程度:★★★★**
|
||||
|
||||
在类的ClassDefaults属性面板里隐藏某些Category的属性。
|
||||
|
||||
注意,要先在类里定义属性然后设置它的Category。HideCategories的信息会被UHT分析,并保存到UClass的元数据里去。HideCategories的信息可以被子类继承下来。
|
||||
|
||||
## 示例代码:
|
||||
|
||||
```cpp
|
||||
UCLASS(Blueprintable, hideCategories = MyGroup1)
|
||||
class INSIDER_API UMyClass_HideCategories :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = MyGroup1)
|
||||
int Property_Group1;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MyGroup2 | MyGroup3")
|
||||
int Property_Group23;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
int Property_NotInGroup;
|
||||
};
|
||||
|
||||
/*
|
||||
(BlueprintType = true, HideCategories = MyGroup2 | MyGroup3, IncludePath = Class/Display/MyClass_ShowCategories.h, IsBlueprintBase = true, ModuleRelativePath = Class/Display/MyClass_ShowCategories.h)
|
||||
*/
|
||||
|
||||
UCLASS(Blueprintable, showCategories = MyGroup1, hideCategories = "MyGroup2 | MyGroup3")
|
||||
class INSIDER_API UMyClass_HideCategoriesChild :public UMyClass_ShowCategories
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MyGroup2")
|
||||
int Property_Group2;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MyGroup3")
|
||||
int Property_Group3;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = MyGroup4)
|
||||
int Property_Group4;
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## 示例效果:
|
||||
|
||||
注意这里,单独的MyGroup2和MyGroup3也都没有显示。所以判断的标准只要目录符合某个目录名字匹配就行。
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
在GetClassHideCategories中检查ClassHideCategoriesMetaKey元数据。
|
||||
|
||||
```cpp
|
||||
void FEditorCategoryUtils::GetClassShowCategories(const UStruct* Class, TArray<FString>& CategoriesOut)
|
||||
{
|
||||
CategoriesOut.Empty();
|
||||
|
||||
using namespace FEditorCategoryUtilsImpl;
|
||||
if (Class->HasMetaData(ClassShowCategoriesMetaKey))
|
||||
{
|
||||
const FString& ShowCategories = Class->GetMetaData(ClassShowCategoriesMetaKey);
|
||||
ShowCategories.ParseIntoArray(CategoriesOut, TEXT(" "), /*InCullEmpty =*/true);
|
||||
|
||||
for (FString& Category : CategoriesOut)
|
||||
{
|
||||
Category = GetCategoryDisplayString(FText::FromString(Category)).ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FEditorCategoryUtils::GetClassHideCategories(const UStruct* Class, TArray<FString>& CategoriesOut, bool bHomogenize)
|
||||
{
|
||||
CategoriesOut.Empty();
|
||||
|
||||
using namespace FEditorCategoryUtilsImpl;
|
||||
if (Class->HasMetaData(ClassHideCategoriesMetaKey))
|
||||
{
|
||||
const FString& HideCategories = Class->GetMetaData(ClassHideCategoriesMetaKey);
|
||||
|
||||
HideCategories.ParseIntoArray(CategoriesOut, TEXT(" "), /*InCullEmpty =*/true);
|
||||
|
||||
if (bHomogenize)
|
||||
{
|
||||
for (FString& Category : CategoriesOut)
|
||||
{
|
||||
Category = GetCategoryDisplayString(Category);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 60 KiB |
@@ -0,0 +1,51 @@
|
||||
# PrioritizeCategories
|
||||
|
||||
- **功能描述:** 把指定的属性目录优先显示在细节面板的前面。
|
||||
- **引擎模块:** Category
|
||||
- **元数据类型:** strings=(abc,"d|e","x|y|z")
|
||||
- **作用机制:** 在Meta中增加[PrioritizeCategories](../../../../Meta/DetailsPanel/PrioritizeCategories.md)
|
||||
- **常用程度:★★★**
|
||||
|
||||
把指定的属性目录优先显示在细节面板的前面。
|
||||
|
||||
## 示例代码:
|
||||
|
||||
```cpp
|
||||
UCLASS(Blueprintable, PrioritizeCategories= ("MyGroup3|MyGroup33|MyGroup333","MyGroup1"))
|
||||
class INSIDER_API UMyClass_PrioritizeCategories :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
int Property_NotInGroup;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MyGroup1")
|
||||
int Property_Group1;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MyGroup2|MyGroup22")
|
||||
int Property_Group22;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MyGroup3|MyGroup33|MyGroup333")
|
||||
int Property_Group333;
|
||||
};
|
||||
```
|
||||
|
||||
## 示例结果:
|
||||
|
||||
可见Property_Group333排到了最前面。
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
在UClass::GetPrioritizeCategories(TArray<FString>& OutPrioritizedCategories)中获取优先级目录。原理是按照指定的顺序放到SortedCategories里,所以就会被首先创建出属性目录来。
|
||||
|
||||
```cpp
|
||||
TArray<FString> ClassPrioritizeCategories;
|
||||
Class->GetPrioritizeCategories(ClassPrioritizeCategories);
|
||||
for (const FString& ClassPrioritizeCategory : ClassPrioritizeCategories)
|
||||
{
|
||||
FName PrioritizeCategoryName = FName(ClassPrioritizeCategory);
|
||||
SortedCategories.AddUnique(PrioritizeCategoryName);
|
||||
PrioritizeCategories.AddUnique(PrioritizeCategoryName);
|
||||
}
|
||||
|
||||
```
|
After Width: | Height: | Size: 43 KiB |
After Width: | Height: | Size: 62 KiB |
@@ -0,0 +1,102 @@
|
||||
# ShowCategories
|
||||
|
||||
- **功能描述:** 在类的ClassDefaults属性面板里显示某些Category的属性。
|
||||
- **引擎模块:** Category
|
||||
- **元数据类型:** strings=(abc,"d|e","x|y|z")
|
||||
- **作用机制:** 在Meta中增加[HideCategories](../../../../Meta/DetailsPanel/HideCategories.md)
|
||||
- **关联项:** [HideCategories](../HideCategories/HideCategories.md)
|
||||
- **常用程度:★★★**
|
||||
|
||||
在类的ClassDefaults属性面板里显示某些Category的属性。使列出的类别的继承自基类的HideCategories说明符无效。
|
||||
|
||||
ShowCategories会被UHT分析,但不会被保存到UClass的元数据里去。它作用的方式是可以抹去之前基类设置的HideCategories的属性。ShowCategories可以被子类继承下来。
|
||||
|
||||
## 示例代码:
|
||||
|
||||
```cpp
|
||||
/*
|
||||
(BlueprintType = true, HideCategories = MyGroup1, IncludePath = Class/Display/MyClass_ShowCategories.h, IsBlueprintBase = true, ModuleRelativePath = Class/Display/MyClass_ShowCategories.h)
|
||||
*/
|
||||
UCLASS(Blueprintable, hideCategories = MyGroup1)
|
||||
class INSIDER_API UMyClass_ShowCategories :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = MyGroup1)
|
||||
int Property_Group1;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MyGroup2 | MyGroup3")
|
||||
int Property_Group23;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
int Property_NotInGroup;
|
||||
};
|
||||
|
||||
/*
|
||||
(BlueprintType = true, HideCategories = MyGroup2 | MyGroup3, IncludePath = Class/Display/MyClass_ShowCategories.h, IsBlueprintBase = true, ModuleRelativePath = Class/Display/MyClass_ShowCategories.h)
|
||||
*/
|
||||
|
||||
UCLASS(Blueprintable, showCategories = MyGroup1, hideCategories = "MyGroup2 | MyGroup3")
|
||||
class INSIDER_API UMyClass_ShowCategoriesChild :public UMyClass_ShowCategories
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MyGroup2")
|
||||
int Property_Group2;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MyGroup3")
|
||||
int Property_Group3;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = MyGroup4)
|
||||
int Property_Group4;
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## 示例效果:
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
其实实际上UHT保存的只在HideCategories里,这点通过对类的元数据查看就可知。
|
||||
|
||||
```cpp
|
||||
void FEditorCategoryUtils::GetClassShowCategories(const UStruct* Class, TArray<FString>& CategoriesOut)
|
||||
{
|
||||
CategoriesOut.Empty();
|
||||
|
||||
using namespace FEditorCategoryUtilsImpl;
|
||||
if (Class->HasMetaData(ClassShowCategoriesMetaKey))
|
||||
{
|
||||
const FString& ShowCategories = Class->GetMetaData(ClassShowCategoriesMetaKey);
|
||||
ShowCategories.ParseIntoArray(CategoriesOut, TEXT(" "), /*InCullEmpty =*/true);
|
||||
|
||||
for (FString& Category : CategoriesOut)
|
||||
{
|
||||
Category = GetCategoryDisplayString(FText::FromString(Category)).ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FEditorCategoryUtils::GetClassHideCategories(const UStruct* Class, TArray<FString>& CategoriesOut, bool bHomogenize)
|
||||
{
|
||||
CategoriesOut.Empty();
|
||||
|
||||
using namespace FEditorCategoryUtilsImpl;
|
||||
if (Class->HasMetaData(ClassHideCategoriesMetaKey))
|
||||
{
|
||||
const FString& HideCategories = Class->GetMetaData(ClassHideCategoriesMetaKey);
|
||||
|
||||
HideCategories.ParseIntoArray(CategoriesOut, TEXT(" "), /*InCullEmpty =*/true);
|
||||
|
||||
if (bHomogenize)
|
||||
{
|
||||
for (FString& Category : CategoriesOut)
|
||||
{
|
||||
Category = GetCategoryDisplayString(Category);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 62 KiB |
@@ -0,0 +1,58 @@
|
||||
# Config
|
||||
|
||||
- **功能描述:** 指定配置文件的名字,把该对象的值保存到ini配置文件中。
|
||||
- **引擎模块:** Config
|
||||
- **元数据类型:** string="abc"
|
||||
- **作用机制:** Config文件名存在FName UClass::ClassConfigName这个参数里
|
||||
- **关联项:** [PerObjectConfig](PerObjectConfig.md)、[ConfigDoNotCheckDefaults](ConfigDoNotCheckDefaults.md)、[DefaultConfig](DefaultConfig/DefaultConfig.md)、[GlobalUserConfig](GlobalUserConfig/GlobalUserConfig.md)、[ProjectUserConfig](ProjectUserConfig/ProjectUserConfig.md)
|
||||
- **常用程度:★★★★★**
|
||||
|
||||
指定配置文件的名字,把该对象的值保存到ini配置文件中。
|
||||
|
||||
- 一整个类在ini中只有一个节的值,因此一般是保存的CDO对象,但也可以用普通对象。
|
||||
- Config文件名称的元数据值保存在FName UClass::ClassConfigName。
|
||||
- 默认是保存在Saved/XXX.ini的Local文件中。
|
||||
- 此说明符会传播到所有子类并且无法使此说明符无效,但是子类可通过重新声明config说明符并提供不同的ConfigName来更改配置文件。
|
||||
- 常见的ConfigName值是“Engine”、“Editor”、“Input”和“Game”。
|
||||
- 可以自己手动调用SaveConfig和LoadConfig来读写配置值。CDO的值会被引擎自己的从配置中读取而更新。
|
||||
- 想保存到配置文件里的属性要相应的用UPROPERTY(config)修饰。
|
||||
|
||||
## 示例代码:
|
||||
|
||||
```cpp
|
||||
UCLASS(Config = Game)
|
||||
class INSIDER_API UMyClass_Config :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
int32 MyProperty = 123;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Config)
|
||||
int32 MyPropertyWithConfig = 123;
|
||||
};
|
||||
|
||||
//测试代码
|
||||
UMyClass_Config* testObject = NewObject<UMyClass_Config>(GetTransientPackage(),TEXT("testObject"));
|
||||
testObject->SaveConfig();
|
||||
|
||||
//生成
|
||||
\Hello\Saved\Config\WindowsEditor\Game.ini
|
||||
[/Script/Insider.MyClass_Config]
|
||||
MyPropertyWithConfig=123
|
||||
```
|
||||
|
||||
## 原理:
|
||||
|
||||
在引擎启动的时候UObjectLoadAllCompiledInDefaultProperties会加载所有Class的CDO,在多个调用链条之后会自动的调用CDO的LoadConfig来初始化CDO的值。
|
||||
|
||||
```cpp
|
||||
static void UObjectLoadAllCompiledInDefaultProperties(TArray<UClass*>& OutAllNewClasses)
|
||||
{
|
||||
for (UClass* Class : NewClasses)
|
||||
{
|
||||
UE_LOG(LogUObjectBootstrap, Verbose, TEXT("GetDefaultObject Begin %s %s"), *Class->GetOutermost()->GetName(), *Class->GetName());
|
||||
Class->GetDefaultObject();
|
||||
UE_LOG(LogUObjectBootstrap, Verbose, TEXT("GetDefaultObject End %s %s"), *Class->GetOutermost()->GetName(), *Class->GetName());
|
||||
}
|
||||
}
|
||||
```
|
@@ -0,0 +1,93 @@
|
||||
# ConfigDoNotCheckDefaults
|
||||
|
||||
- **功能描述:** 指定在保存配置值的时候忽略上一级的配置值的一致性检查。
|
||||
- **引擎模块:** Config
|
||||
- **元数据类型:** bool
|
||||
- **作用机制:** 在ClassFlags中增加[CLASS_ConfigDoNotCheckDefaults](../../../Flags/EClassFlags/CLASS_ConfigDoNotCheckDefaults.md)
|
||||
- **关联项:** [Config](Config.md)
|
||||
- **常用程度:★**
|
||||
|
||||
指定在保存配置值的时候忽略上一级的配置值的一致性检查。
|
||||
|
||||
- 在保存配置的时候,决定是否要先根据Base或Default的配置来检查属性是否一致,如果一致就不用序列化写入下来。但加上这个标志后,即使同上一个层级的配置值相同也无论如何都要保存下来。
|
||||
|
||||
UCLASS(config=XXX,configdonotcheckdefaults):表示这个类对应的配置文件不会检查XXX层级上层的DefaultXXX配置文件是否有该信息(后面会解释层级),就直接存储到Saved目录下。
|
||||
|
||||
## 示例代码:
|
||||
|
||||
```cpp
|
||||
UCLASS(Config = Game)
|
||||
class INSIDER_API UMyClass_Config :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
int32 MyProperty = 123;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Config)
|
||||
int32 MyPropertyWithConfig = 123;
|
||||
};
|
||||
|
||||
UCLASS(Config = Game,configdonotcheckdefaults)
|
||||
class INSIDER_API UMyClass_ConfigDoNotCheckDefaults :public UMyClass_Config
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Config)
|
||||
int32 MyPropertyWithConfigSub = 123;
|
||||
};
|
||||
|
||||
UCLASS(Config = Game)
|
||||
class INSIDER_API UMyClass_ConfigDefaultChild :public UMyClass_Config
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Config)
|
||||
int32 MyPropertyWithConfigSub = 123;
|
||||
};
|
||||
```
|
||||
|
||||
## 示例效果:
|
||||
|
||||
```cpp
|
||||
void UMyClass_Config_Test::TestConfigCheckDefaultSave()
|
||||
{
|
||||
auto* testObject = NewObject<UMyClass_ConfigDoNotCheckDefaults>(GetTransientPackage(), TEXT("testObjectCheckDefault"));
|
||||
auto* testObject2 = NewObject<UMyClass_ConfigDefaultChild>(GetTransientPackage(), TEXT("testObjectDefaultChild"));
|
||||
|
||||
testObject->SaveConfig();
|
||||
testObject2->SaveConfig();
|
||||
}
|
||||
|
||||
生成:
|
||||
[/Script/Insider.MyClass_Config]
|
||||
MyPropertyWithConfig=777
|
||||
|
||||
[/Script/Insider.MyClass_ConfigDoNotCheckDefaults]
|
||||
MyPropertyWithConfigSub=123
|
||||
MyPropertyWithConfig=777
|
||||
|
||||
[/Script/Insider.MyClass_ConfigDefaultChild]
|
||||
MyPropertyWithConfigSub=123
|
||||
```
|
||||
|
||||
由此可见,MyClass_ConfigDoNotCheckDefaults中的MyPropertyWithConfig的值默认跟UMyClass_Config中的777值一致,但是依然会写入进来。在MyClass_ConfigDefaultChild类中,MyPropertyWithConfig的值因为没有改变,就会被略过。
|
||||
|
||||
在源码里搜configdonotcheckdefaults的时候发现常常和defaultconfig配合使用。什么时候应该使用configdonotcheckdefaults?感觉是为了保持自己的完整性,无论如何都要全部写入进去。在defaultConfig的时候,就可以不管Base里的值,都写入一份到Default配置里,这样在编辑上更加的完整。
|
||||
|
||||
## 原理:
|
||||
|
||||
```cpp
|
||||
const bool bShouldCheckIfIdenticalBeforeAdding = !GetClass()->HasAnyClassFlags(CLASS_ConfigDoNotCheckDefaults) && !bPerObject && bIsPropertyInherited;
|
||||
//简单的示例判断
|
||||
if (!bPropDeprecated && (!bShouldCheckIfIdenticalBeforeAdding || !Property->Identical_InContainer(this, SuperClassDefaultObject, Index)))
|
||||
{
|
||||
FString Value;
|
||||
Property->ExportText_InContainer( Index, Value, this, this, this, PortFlags );
|
||||
Config->SetString( *Section, *Key, *Value, PropFileName );
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we are not writing it to config above, we should make sure that this property isn't stagnant in the cache.
|
||||
Config->RemoveKey( *Section, *Key, PropFileName );
|
||||
}
|
||||
```
|
@@ -0,0 +1,89 @@
|
||||
# DefaultConfig
|
||||
|
||||
- **功能描述:** 指定保存到的配置文件层级是Project/Config/DefaultXXX.ini。
|
||||
- **引擎模块:** Config
|
||||
- **元数据类型:** bool
|
||||
- **作用机制:** 在ClassFlags中增加[CLASS_DefaultConfig](../../../../Flags/EClassFlags/CLASS_DefaultConfig.md)
|
||||
- **关联项:** [Config](../Config.md)
|
||||
- **常用程度:★★★**
|
||||
|
||||
指定保存到的配置文件层级是Project/Config/DefaultXXX.ini。
|
||||
|
||||
- 而不是默认的Saved/XXX.ini
|
||||
- 一般用在编辑器里把Settings自动保存到Project/Config/DefaultXXX.ini里去
|
||||
|
||||
## 示例代码:
|
||||
|
||||
```cpp
|
||||
UCLASS(Config = MyGame,DefaultConfig)
|
||||
class INSIDER_API UMyClass_DefaultConfig :public UDeveloperSettings
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
/** Gets the settings container name for the settings, either Project or Editor */
|
||||
virtual FName GetContainerName() const override { return TEXT("Project"); }
|
||||
/** Gets the category for the settings, some high level grouping like, Editor, Engine, Game...etc. */
|
||||
virtual FName GetCategoryName() const override { return TEXT("MyGame"); }
|
||||
/** The unique name for your section of settings, uses the class's FName. */
|
||||
virtual FName GetSectionName() const override { return TEXT("MyGame"); }
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Config)
|
||||
int32 MyPropertyWithConfig = 123;
|
||||
};
|
||||
|
||||
//保存的结果:
|
||||
//Config/DefaultMyGame.ini
|
||||
[/Script/Insider.MyClass_DefaultConfig]
|
||||
MyPropertyWithConfig=888
|
||||
|
||||
```
|
||||
|
||||
## 示例结果:
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
代码里要使用Settings->TryUpdateDefaultConfigFile();,但发现TryUpdateDefaultConfigFile不管有没有DefaultConfig都可以调用,都可以保存到Default里。因此应该调用哪个SaveConfig(TryUpdateDefaultConfigFile,UpdateGlobalUserConfigFile,UpdateProjectUserConfigFile)是可以手动指定的。
|
||||
|
||||
但是在编辑器里编辑的时候,则可以通过写好的代码来处理好逻辑。如SSettingsEditor.cpp里NotifyPostChange中调用Section->Save();则可以在内部再调用如下代码:
|
||||
|
||||
```cpp
|
||||
bool FSettingsSection::Save()
|
||||
{
|
||||
if (ModifiedDelegate.IsBound() && !ModifiedDelegate.Execute())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SaveDelegate.IsBound())
|
||||
{
|
||||
return SaveDelegate.Execute();
|
||||
}
|
||||
|
||||
//更新到正确的文件里
|
||||
if (SettingsObject.IsValid())
|
||||
{
|
||||
if (SettingsObject->GetClass()->HasAnyClassFlags(CLASS_DefaultConfig))
|
||||
{
|
||||
SettingsObject->TryUpdateDefaultConfigFile();
|
||||
}
|
||||
else if (SettingsObject->GetClass()->HasAnyClassFlags(CLASS_GlobalUserConfig))
|
||||
{
|
||||
SettingsObject->UpdateGlobalUserConfigFile();
|
||||
}
|
||||
else if (SettingsObject->GetClass()->HasAnyClassFlags(CLASS_ProjectUserConfig))
|
||||
{
|
||||
SettingsObject->UpdateProjectUserConfigFile();
|
||||
}
|
||||
else
|
||||
{
|
||||
SettingsObject->SaveConfig();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 84 KiB |
@@ -0,0 +1,70 @@
|
||||
# EditorConfig
|
||||
|
||||
- **功能描述:** 用来在编辑器状态下保存信息。
|
||||
- **引擎模块:** Config, Editor
|
||||
- **元数据类型:** string="abc"
|
||||
- **作用机制:** 在Meta中增加[EditorConfig](../../../../Meta/Config/EditorConfig.md)
|
||||
- **常用程度:★**
|
||||
|
||||
用来在编辑器状态下保存信息。
|
||||
|
||||
一般用在EditorTarget的Module里,用于配置相应编辑器的信息,比如列宽,收藏夹之类的,用json保存。
|
||||
|
||||
保存在:C:\Users\{user name}\AppData\Local\UnrealEngine\Editor。当前有:
|
||||
|
||||

|
||||
|
||||
在源码里搜索后,使用的时候必须继承于基类:
|
||||
|
||||
```cpp
|
||||
/** Inherit from this class to simplify saving and loading properties from editor configs. */
|
||||
UCLASS()
|
||||
class EDITORCONFIG_API UEditorConfigBase : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
/** Load any properties of this class into properties marked with metadata tag "EditorConfig" from the class's EditorConfig */
|
||||
bool LoadEditorConfig();
|
||||
|
||||
/** Save any properties of this class in properties marked with metadata tag "EditorConfig" into the class's EditorConfig. */
|
||||
bool SaveEditorConfig() const;
|
||||
};
|
||||
```
|
||||
|
||||
## 示例代码:
|
||||
|
||||
```cpp
|
||||
UCLASS(EditorConfig = "MyEditorGame")
|
||||
class INSIDER_API UMyClass_EditorConfig : public UEditorConfigBase
|
||||
{
|
||||
public:
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (EditorConfig))
|
||||
int32 MyPropertyWithConfig = 123;
|
||||
};
|
||||
|
||||
void UMyClass_EditorConfig_Test::TestConfigSave()
|
||||
{
|
||||
//must run after editor initialization
|
||||
auto* testObject = NewObject<UMyClass_EditorConfig>(GetTransientPackage(), TEXT("testObject_EditorConfig"));
|
||||
testObject->MyPropertyWithConfig = 777;
|
||||
testObject->SaveEditorConfig();
|
||||
|
||||
}
|
||||
|
||||
void UMyClass_EditorConfig_Test::TestConfigLoad()
|
||||
{
|
||||
auto* testObject = NewObject<UMyClass_EditorConfig>(GetTransientPackage(), TEXT("testObject_EditorConfig"));
|
||||
testObject->LoadEditorConfig();
|
||||
}
|
||||
|
||||
//运行Save后的保存结果:C:\Users\jack.fu\AppData\Local\UnrealEngine\Editor\MyEditorGame.json
|
||||
|
||||
{
|
||||
"$type": "MyClass_EditorConfig",
|
||||
"MyPropertyWithConfig": 777
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 128 KiB |
@@ -0,0 +1,74 @@
|
||||
# GlobalUserConfig
|
||||
|
||||
- **功能描述:** 指定保存到的配置文件层级是全局用户设置 Engine/Config/UserXXX.ini。
|
||||
- **引擎模块:** Config
|
||||
- **元数据类型:** bool
|
||||
- **作用机制:** 在ClassFlags中增加[CLASS_GlobalUserConfig](../../../../Flags/EClassFlags/CLASS_GlobalUserConfig.md)
|
||||
- **关联项:** [Config](../Config.md)
|
||||
- **常用程度:★★★**
|
||||
|
||||
指定保存到的配置文件层级是全局用户设置 Engine/Config/UserXXX.ini。
|
||||
|
||||
## 示例代码:
|
||||
|
||||
属性用Config或者GlobalConfig都是可以的。
|
||||
|
||||
```cpp
|
||||
UCLASS(Config = MyGame, GlobalUserConfig)
|
||||
class INSIDER_API UMyClass_GlobalUserConfig:public UDeveloperSettings
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
/** Gets the settings container name for the settings, either Project or Editor */
|
||||
virtual FName GetContainerName() const override { return TEXT("Project"); }
|
||||
/** Gets the category for the settings, some high level grouping like, Editor, Engine, Game...etc. */
|
||||
virtual FName GetCategoryName() const override { return TEXT("MyGame"); }
|
||||
/** The unique name for your section of settings, uses the class's FName. */
|
||||
virtual FName GetSectionName() const override { return TEXT("MyGlobalGame"); }
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Config)
|
||||
int32 MyPropertyWithConfig = 123;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, GlobalConfig)
|
||||
int32 MyPropertyWithGlobalConfig = 456;
|
||||
};
|
||||
|
||||
保存到C:\Users\jack.fu\AppData\Local\Unreal Engine\Engine\Config\UserMyGame.ini
|
||||
[/Script/Insider.UMyClass_GlobalUserConfig]
|
||||
MyPropertyWithGlobalConfig=999
|
||||
```
|
||||
|
||||
## 示例效果:
|
||||
|
||||

|
||||
|
||||
## 源码例子:
|
||||
|
||||
```cpp
|
||||
UCLASS(config=Engine, globaluserconfig)
|
||||
class ANDROIDPLATFORMEDITOR_API UAndroidSDKSettings : public UObject
|
||||
{
|
||||
public:
|
||||
GENERATED_UCLASS_BODY()
|
||||
|
||||
// Location on disk of the Android SDK (falls back to ANDROID_HOME environment variable if this is left blank)
|
||||
UPROPERTY(GlobalConfig, EditAnywhere, Category = SDKConfig, Meta = (DisplayName = "Location of Android SDK (the directory usually contains 'android-sdk-')"))
|
||||
FDirectoryPath SDKPath;
|
||||
|
||||
// Location on disk of the Android NDK (falls back to NDKROOT environment variable if this is left blank)
|
||||
UPROPERTY(GlobalConfig, EditAnywhere, Category = SDKConfig, Meta = (DisplayName = "Location of Android NDK (the directory usually contains 'android-ndk-')"))
|
||||
FDirectoryPath NDKPath;
|
||||
|
||||
// Location on disk of Java (falls back to JAVA_HOME environment variable if this is left blank)
|
||||
UPROPERTY(GlobalConfig, EditAnywhere, Category = SDKConfig, Meta = (DisplayName = "Location of JAVA (the directory usually contains 'jdk')"))
|
||||
FDirectoryPath JavaPath;
|
||||
|
||||
// Which SDK to package and compile Java with (a specific version or (without quotes) 'latest' for latest version on disk, or 'matchndk' to match the NDK API Level)
|
||||
UPROPERTY(GlobalConfig, EditAnywhere, Category = SDKConfig, Meta = (DisplayName = "SDK API Level (specific version, 'latest', or 'matchndk' - see tooltip)"))
|
||||
FString SDKAPILevel;
|
||||
|
||||
// Which NDK to compile with (a specific version or (without quotes) 'latest' for latest version on disk). Note that choosing android-21 or later won't run on pre-5.0 devices.
|
||||
UPROPERTY(GlobalConfig, EditAnywhere, Category = SDKConfig, Meta = (DisplayName = "NDK API Level (specific version or 'latest' - see tooltip)"))
|
||||
FString NDKAPILevel;
|
||||
};
|
||||
```
|
After Width: | Height: | Size: 86 KiB |
@@ -0,0 +1,73 @@
|
||||
# PerObjectConfig
|
||||
|
||||
- **功能描述:** 在已经有config配置文件名字的情况下,指定应该按每个对象实例来存储值,而不是一个类一个存储值。
|
||||
- **引擎模块:** Config
|
||||
- **元数据类型:** bool
|
||||
- **作用机制:** 在ClassFlags中增加[CLASS_PerObjectConfig](../../../Flags/EClassFlags/CLASS_PerObjectConfig.md)
|
||||
- **关联项:** [Config](Config.md)
|
||||
- **常用程度:★★★★★**
|
||||
|
||||
在已经有config配置文件名字的情况下,指定应该按每个对象实例来存储值,而不是一个类一个存储值。
|
||||
|
||||
- 此类的配置信息将按对象存储,在.ini文件中,每个对象都有一个分段,根据对象命名,格式为[ObjectName ClassName]。
|
||||
- 此说明符会传播到子类。指定该配置是对每个对象都单独保存。
|
||||
|
||||
## 示例代码:
|
||||
|
||||
注意ObjectName必须一致
|
||||
|
||||
```cpp
|
||||
UCLASS(Config = Game,PerObjectConfig)
|
||||
class INSIDER_API UMyClass_PerObjectConfig :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
int32 MyProperty = 123;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Config)
|
||||
int32 MyPropertyWithConfig = 123;
|
||||
};
|
||||
|
||||
void UMyClass_Config_Test::TestPerObjectConfigSave()
|
||||
{
|
||||
UMyClass_PerObjectConfig* testObject1 = NewObject<UMyClass_PerObjectConfig>(GetTransientPackage(), TEXT("testObject1"));
|
||||
testObject1->MyPropertyWithConfig = 456;
|
||||
testObject1->SaveConfig();
|
||||
|
||||
UMyClass_PerObjectConfig* testObject2 = NewObject<UMyClass_PerObjectConfig>(GetTransientPackage(), TEXT("testObject2"));
|
||||
testObject2->MyPropertyWithConfig = 789;
|
||||
testObject2->SaveConfig();
|
||||
|
||||
}
|
||||
|
||||
void UMyClass_Config_Test::TestPerObjectConfigLoad()
|
||||
{
|
||||
UMyClass_PerObjectConfig* testObject1 = NewObject<UMyClass_PerObjectConfig>(GetTransientPackage(), TEXT("testObject1"));
|
||||
//testObject1->LoadConfig(); //不需要显式调用LoadConfig
|
||||
|
||||
UMyClass_PerObjectConfig* testObject2 = NewObject<UMyClass_PerObjectConfig>(GetTransientPackage(), TEXT("testObject2"));
|
||||
//testObject2->LoadConfig();
|
||||
}
|
||||
|
||||
//\Saved\Config\WindowsEditor\Game.ini
|
||||
[testObject1 MyClass_PerObjectConfig]
|
||||
MyPropertyWithConfig=456
|
||||
|
||||
[testObject2 MyClass_PerObjectConfig]
|
||||
MyPropertyWithConfig=789
|
||||
```
|
||||
|
||||
## 原理:
|
||||
|
||||
对象构造的末期会尝试去读取配置。
|
||||
|
||||
```cpp
|
||||
void FObjectInitializer::PostConstructInit()
|
||||
{
|
||||
//在NewObject构造中后面会调用
|
||||
if (bIsCDO || Class->HasAnyClassFlags(CLASS_PerObjectConfig))
|
||||
{
|
||||
Obj->LoadConfig(NULL, NULL, bIsCDO ? UE::LCPF_ReadParentSections : UE::LCPF_None);
|
||||
}
|
||||
}
|
||||
```
|
@@ -0,0 +1,53 @@
|
||||
# ProjectUserConfig
|
||||
|
||||
- **功能描述:** 指定保存到的配置文件层级是项目用户设置 Project/Config/UserXXX.ini。
|
||||
- **引擎模块:** Config
|
||||
- **元数据类型:** bool
|
||||
- **作用机制:** 在ClassFlags中增加[CLASS_ProjectUserConfig](../../../../Flags/EClassFlags/CLASS_ProjectUserConfig.md)
|
||||
- **关联项:** [Config](../Config.md)
|
||||
- **常用程度:★★★**
|
||||
|
||||
指定保存到的配置文件层级是项目用户设置 Project/Config/UserXXX.ini。
|
||||
|
||||
## 示例代码:
|
||||
|
||||
保存的目录是\Hello\Config\UserMyGame.ini
|
||||
|
||||
```cpp
|
||||
UCLASS(Config = MyGame, ProjectUserConfig)
|
||||
class INSIDER_API UMyClass_ProjectUserConfig :public UDeveloperSettings
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
/** Gets the settings container name for the settings, either Project or Editor */
|
||||
virtual FName GetContainerName() const override { return TEXT("Project"); }
|
||||
/** Gets the category for the settings, some high level grouping like, Editor, Engine, Game...etc. */
|
||||
virtual FName GetCategoryName() const override { return TEXT("MyGame"); }
|
||||
/** The unique name for your section of settings, uses the class's FName. */
|
||||
virtual FName GetSectionName() const override { return TEXT("MyProjectGame"); }
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Config)
|
||||
int32 MyPropertyWithConfig = 123;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, GlobalConfig)
|
||||
int32 MyPropertyWithGlobalConfig = 456;
|
||||
};
|
||||
|
||||
//结果:\Hello\Config\UserMyGame.ini
|
||||
[/Script/Insider.MyClass_ProjectUserConfig]
|
||||
MyPropertyWithConfig=777
|
||||
MyPropertyWithGlobalConfig=888
|
||||
```
|
||||
|
||||
## 示例效果:
|
||||
|
||||

|
||||
|
||||
## 在源码中搜索:
|
||||
|
||||
```cpp
|
||||
UCLASS(config = Engine, projectuserconfig, meta = (DisplayName = "Rendering Overrides (Local)"))
|
||||
class ENGINE_API URendererOverrideSettings : public UDeveloperSettings
|
||||
{
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 87 KiB |
@@ -0,0 +1,114 @@
|
||||
# Deprecated
|
||||
|
||||
- **功能描述:** 标明该类已经弃用。
|
||||
- **引擎模块:** Development
|
||||
- **元数据类型:** bool
|
||||
- **作用机制:** 在ClassFlags添加[CLASS_Deprecated](../../../../Flags/EClassFlags/CLASS_Deprecated.md)、[CLASS_NotPlaceable](../../../../Flags/EClassFlags/CLASS_NotPlaceable.md),在Meta添加[DeprecationMessage](../../../../Meta/Development/DeprecationMessage.md)、[DeprecatedProperty](../../../../Meta/Development/DeprecatedProperty/DeprecatedProperty.md)
|
||||
- **常用程度:★★★**
|
||||
|
||||
标明该类已经弃用。
|
||||
|
||||
弃用会导致:不可被创建,不可被序列化保存,在继承列表里被过滤掉。此说明符子类会继承下来,标明子类也是废弃的。标上Deprecated 的类需要在类名前加上UDEPRECATED_的显眼前缀,但是类名不会变,Actor加ADEPRECATED_,UObject加UDEPRECATED_。ClassFLags里会标上CLASS_Deprecated和CLASS_NotPlaceable。注意还是可以正常NewObject使用的。而SpawnActor会失败,报错: failed because class %s is deprecated。EditInline也都会被禁止。
|
||||
|
||||
## 示例代码1:
|
||||
|
||||
```cpp
|
||||
UCLASS(Blueprintable)
|
||||
class INSIDER_API UMyClass_Deprecated :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
};
|
||||
//改为:
|
||||
UCLASS(Blueprintable, Deprecated)
|
||||
class INSIDER_API UDEPRECATED_MyClass_Deprecated :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
};
|
||||
```
|
||||
|
||||
## 示例效果1:
|
||||
|
||||
依然可以NewObject。
|
||||
|
||||

|
||||
|
||||
## 示例代码2:
|
||||
|
||||
但要注意这个是UE的标记。源码里还看见很多UE_DEPRECATED宏的使用,则是在VS编译器级别的标记,会根据使用引用情况在编译的步骤中生成警告。
|
||||
|
||||
```cpp
|
||||
UCLASS(Blueprintable, BlueprintType)
|
||||
class INSIDER_API UMyClass_Deprecated_Test :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
|
||||
UE_DEPRECATED(5.2, "MyClass_Deprecated has been deprecated, please remove it.")
|
||||
UDEPRECATED_MyClass_Deprecated* MyProperty_Deprecated;
|
||||
|
||||
UE_DEPRECATED(5.2, "MyIntProperty has been deprecated, please remove it.")
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite,meta=(DeprecatedProperty, DeprecationMessage = "MyIntProperty has been deprecated."))
|
||||
int MyIntProperty;
|
||||
|
||||
UE_DEPRECATED(5.2, "MyClass_Deprecated has been deprecated, please remove it.")
|
||||
void MyFunc(UDEPRECATED_MyClass_Deprecated* obj){}
|
||||
|
||||
UFUNCTION(BlueprintCallable, meta = (DeprecatedProperty, DeprecationMessage="MyVoidFunc has been deprecated."))
|
||||
void MyVoidFunc(){}
|
||||
};
|
||||
|
||||
UCLASS(Blueprintable, BlueprintType)
|
||||
class INSIDER_API UMyClass_Deprecated_Usage :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
|
||||
void MyFunc()
|
||||
{
|
||||
UMyClass_Deprecated_Test* obj=NewObject<UMyClass_Deprecated_Test>();
|
||||
UDEPRECATED_MyClass_Deprecated* obj2 = NewObject<UDEPRECATED_MyClass_Deprecated>();
|
||||
obj->MyProperty_Deprecated= obj2;
|
||||
obj->MyProperty_Deprecated->MyFunc();
|
||||
|
||||
obj->MyIntProperty++;
|
||||
obj->MyFunc(obj2);
|
||||
obj->MyVoidFunc();
|
||||
}
|
||||
};
|
||||
|
||||
编译警告:
|
||||
warning C4996: 'UMyClass_Deprecated_Test::MyProperty_Deprecated': MyClass_Deprecated has been deprecated, please remove it. Please update your code to the new API before upgrading to the next release, otherwise your project will no longer compile.
|
||||
warning C4996: 'UMyClass_Deprecated_Test::MyProperty_Deprecated': MyClass_Deprecated has been deprecated, please remove it. Please update your code to the new API before upgrading to the next release, otherwise your project will no longer compile.
|
||||
warning C4996: 'UMyClass_Deprecated_Test::MyIntProperty': MyIntProperty has been deprecated, please remove it. Please update your code to the new API before upgrading to the next release, otherwise your project will no longer compile.
|
||||
warning C4996: 'UMyClass_Deprecated_Test::MyFunc': MyClass_Deprecated has been deprecated, please remove it. Please update your code to the new API before upgrading to the next release, otherwise your project will no longer compile.
|
||||
注意如果没有UE_DEPRECATED标记,则不会生成编译警告。
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite) int MyInt2Property_DEPRECATED;
|
||||
会触发:
|
||||
warning : Member variable declaration: Deprecated property 'MyInt2Property_DEPRECATED' should not be marked as blueprint visible without having a BlueprintGetter
|
||||
warning : Member variable declaration: Deprecated property 'MyInt2Property_DEPRECATED' should not be marked as blueprint writable without having a BlueprintSetter
|
||||
warning : Member variable declaration: Deprecated property 'MyInt2Property_DEPRECATED' should not be marked as visible or editable
|
||||
因此只能改成:
|
||||
UPROPERTY() int MyInt2Property_DEPRECATED;
|
||||
```
|
||||
|
||||
## 示例效果2:
|
||||
|
||||
属性和函数上加上Deprecated标记后,会在BP编译的时候生成警告。注意函数是先有一个正常的函数,在BP里连接完成之后再在C++里标记DeprecatedFunction才会生成警告,否则已经Deprecated的函数是无法再在BP里调用的。
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
源码中有众多CLASS_Deprecated的判断,比如SpawnActor:
|
||||
|
||||
```cpp
|
||||
AActor* UWorld::SpawnActor( UClass* Class, FTransform const* UserTransformPtr, const FActorSpawnParameters& SpawnParameters )
|
||||
{
|
||||
if( Class->HasAnyClassFlags(CLASS_Deprecated) )
|
||||
{
|
||||
UE_LOG(LogSpawn, Warning, TEXT("SpawnActor failed because class %s is deprecated"), *Class->GetName() );
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 97 KiB |
After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1,31 @@
|
||||
# EarlyAccessPreview
|
||||
|
||||
- **功能描述:** 标明该类是早期预览版,比试验版要更完善一些,但还是没到产品级。
|
||||
- **引擎模块:** Development
|
||||
- **元数据类型:** bool
|
||||
- **作用机制:** 在Meta中添加[DevelopmentStatus](../../../../Meta/Development/DevelopmentStatus.md),将类标记为EarlyAccess
|
||||
- **常用程度:★★★**
|
||||
|
||||
标明该类是早期预览版,比试验版要更完善一些,但还是没到产品级。
|
||||
|
||||
这个标记会在类的元数据上加上{ "DevelopmentStatus", "EarlyAccess" }。
|
||||
|
||||
## 示例代码:
|
||||
|
||||
```cpp
|
||||
//(BlueprintType = true, DevelopmentStatus = EarlyAccess, IncludePath = Class/Display/MyClass_Deprecated.h, IsBlueprintBase = true, ModuleRelativePath = Class/Display/MyClass_Deprecated.h)
|
||||
UCLASS(Blueprintable, EarlyAccessPreview)
|
||||
class INSIDER_API UMyClass_EarlyAccessPreview :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
int32 MyProperty;
|
||||
UFUNCTION(BlueprintCallable)
|
||||
void MyFunc() {}
|
||||
};
|
||||
```
|
||||
|
||||
## 示例结果:
|
||||
|
||||

|
After Width: | Height: | Size: 17 KiB |
@@ -0,0 +1,33 @@
|
||||
# Experimental
|
||||
|
||||
- **功能描述:** 标明该类是试验性版本,当前没有文档描述,之后有可能废弃掉。
|
||||
- **引擎模块:** Development
|
||||
- **元数据类型:** bool
|
||||
- **作用机制:** 在Meta中添加[DevelopmentStatus](../../../../Meta/Development/DevelopmentStatus.md),将类标记为Experimental
|
||||
- **常用程度:★★★**
|
||||
|
||||
标明该类是试验性版本,当前没有文档描述,之后有可能废弃掉。
|
||||
|
||||
源码里的例子是Paper2D的类。这个标记会在类的元数据上加上{ "DevelopmentStatus", "Experimental" }。
|
||||
|
||||
## 示例代码:
|
||||
|
||||
```cpp
|
||||
/*
|
||||
(BlueprintType = true, DevelopmentStatus = Experimental, IncludePath = Class/Display/MyClass_Deprecated.h, IsBlueprintBase = true, ModuleRelativePath = Class/Display/MyClass_Deprecated.h)
|
||||
*/
|
||||
UCLASS(Blueprintable, Experimental)
|
||||
class INSIDER_API UMyClass_Experimental :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
int32 MyProperty;
|
||||
UFUNCTION(BlueprintCallable)
|
||||
void MyFunc() {}
|
||||
};
|
||||
```
|
||||
|
||||
## 示例效果:
|
||||
|
||||

|
After Width: | Height: | Size: 16 KiB |
@@ -0,0 +1,112 @@
|
||||
# DefaultToInstanced
|
||||
|
||||
- **功能描述:** 指定该类的所有实例属性都默认是UPROPERTY(instanced),即都默认创建新的实例,而不是对对象的引用。
|
||||
- **引擎模块:** Instance
|
||||
- **元数据类型:** bool
|
||||
- **作用机制:** 在ClassFlags中添加[CLASS_DefaultToInstanced](../../../../Flags/EClassFlags/CLASS_DefaultToInstanced.md)
|
||||
- **常用程度:★★★★**
|
||||
|
||||
指定该类的所有实例属性都默认是UPROPERTY(instanced),即都默认创建新的实例,而不是对对象的引用。
|
||||
|
||||
UPROPERTY(instanced)的含义是造成Property的CPF_InstancedReference,即为该属性创建对象实例。
|
||||
|
||||
所谓实例指的是为该UObject指针创建一个对象,而不是默认的去找到引擎内已有的对象的来引用。
|
||||
|
||||
也常常和EditInlineNew配合使用,以便在细节面板中可以创建对象实例。
|
||||
|
||||
UActorComponent本身就是带有DefaultToInstanced的。
|
||||
|
||||
## 示例代码:
|
||||
|
||||
```cpp
|
||||
UCLASS(Blueprintable)
|
||||
class INSIDER_API UMyClass_NotDefaultToInstanced :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
int32 MyProperty;
|
||||
};
|
||||
|
||||
// ClassFlags: CLASS_MatchedSerializers | CLASS_Native | CLASS_RequiredAPI | CLASS_DefaultToInstanced | CLASS_TokenStreamAssembled | CLASS_Intrinsic | CLASS_Constructed
|
||||
UCLASS(Blueprintable, DefaultToInstanced)
|
||||
class INSIDER_API UMyClass_DefaultToInstanced :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
int32 MyProperty;
|
||||
};
|
||||
|
||||
// ClassFlags: CLASS_MatchedSerializers | CLASS_Native | CLASS_EditInlineNew | CLASS_RequiredAPI | CLASS_DefaultToInstanced | CLASS_TokenStreamAssembled | CLASS_Intrinsic | CLASS_Constructed
|
||||
UCLASS(Blueprintable, DefaultToInstanced, EditInlineNew)
|
||||
class INSIDER_API UMyClass_DefaultToInstanced_EditInlineNew :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
int32 MyProperty;
|
||||
};
|
||||
|
||||
UCLASS(Blueprintable, EditInlineNew)
|
||||
class INSIDER_API UMyClass_NotDefaultToInstanced_EditInlineNew :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
int32 MyProperty;
|
||||
};
|
||||
|
||||
UCLASS(Blueprintable, BlueprintType)
|
||||
class INSIDER_API UMyClass_DefaultToInstanced_Test :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "NormalProperty")
|
||||
UMyClass_NotDefaultToInstanced* MyObject_NotDefaultToInstanced;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "NormalProperty")
|
||||
UMyClass_DefaultToInstanced* MyObject_DefaultToInstanced;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Instanced, Category = "NormalProperty | Instanced")
|
||||
UMyClass_NotDefaultToInstanced* MyObject_NotDefaultToInstanced_Instanced;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Instanced, Category = "NormalProperty | Instanced")
|
||||
UMyClass_DefaultToInstanced* MyObject_DefaultToInstanced_Instanced;
|
||||
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "EditInlineNew")
|
||||
UMyClass_NotDefaultToInstanced_EditInlineNew* MyObject_NotDefaultToInstanced_EditInlineNew;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "EditInlineNew")
|
||||
UMyClass_DefaultToInstanced_EditInlineNew* MyObject_DefaultToInstanced_EditInlineNew;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Instanced, Category = "EditInlineNew | Instanced")
|
||||
UMyClass_NotDefaultToInstanced_EditInlineNew* MyObject_NotDefaultToInstanced_EditInlineNew_Instanced;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Instanced, Category = "EditInlineNew | Instanced")
|
||||
UMyClass_DefaultToInstanced_EditInlineNew* MyObject_DefaultToInstanced_EditInlineNew_Instanced;
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## 示例效果:
|
||||
|
||||
- MyObject_NotDefaultToInstanced和MyObject_NotDefaultToInstanced_EditInlineNew因为属性没有instanced的标记,因此打开是一个选择对象引用的列表。
|
||||
- MyObject_DefaultToInstanced因为类上有DefaultToInstanced,因此该属性是Instanced。当然我们也可以手动给属性加上Instanced标记,正如MyObject_NotDefaultToInstanced_Instanced和MyObject_DefaultToInstanced_Instanced。出现了创建实例的窗口,但是还不能创建在细节面板里直接创建对象。
|
||||
- MyObject_DefaultToInstanced_EditInlineNew,MyObject_NotDefaultToInstanced_EditInlineNew_Instanced,MyObject_DefaultToInstanced_EditInlineNew_Instanced这3个都可以直接在细节面板创建对象实例。是因为这个类本身要有EditInlineNew,另外这个属性要有Instanced(要嘛在该类上设置DefaultToInstanced以此该类的所有属性都自动是Instanced,或者在属性上单个设置Instanced)
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
```cpp
|
||||
UObject* FObjectInstancingGraph::InstancePropertyValue(UObject* SubObjectTemplate, UObject* CurrentValue, UObject* Owner, EInstancePropertyValueFlags Flags)
|
||||
{
|
||||
if (CurrentValue->GetClass()->HasAnyClassFlags(CLASS_DefaultToInstanced))
|
||||
{
|
||||
bCausesInstancing = true; // these are always instanced no matter what
|
||||
}
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 240 KiB |
@@ -0,0 +1,80 @@
|
||||
# EditInlineNew
|
||||
|
||||
- **功能描述:** 指定该类的对象可以在属性细节面板里直接内联创建,要和属性的Instanced配合。
|
||||
- **引擎模块:** Instance
|
||||
- **元数据类型:** bool
|
||||
- **作用机制:** 在ClassFlags中添加[CLASS_EditInlineNew](../../../../Flags/EClassFlags/CLASS_EditInlineNew.md)
|
||||
- **关联项:** NotEditInlineNew (NotEditInlineNew.md)
|
||||
- **常用程度:★★★★★**
|
||||
|
||||
指定该类的对象可以在属性细节面板里直接内联创建。
|
||||
|
||||
如果想在细节面板里直接创建对象,属性上也必须先标记Instanced或ShowInnerProperties。
|
||||
|
||||
EditInlineNew主要是用在UObject的子类上,一般不标EditInlineNew的是用在Actor或资产的引用上。注意EditInlineNew是表明增加从属性细节面板里直接创建对象实例的能力,而非限制只能在属性细节面板里创建,当然也可以自己手动NewObject再赋值给对象引用属性。
|
||||
|
||||
这个跟UPROPERTY上的Instanced能力是独立的。如果UCLASS上不加EditInlineNew,但是属性上加上Instanced,则在手动NewObject赋值该属性后,该属性也会展开内部属性来提供编辑功能。因为Instanced的属性会自动的在property上加上EditInline的meta。
|
||||
|
||||
此说明符会传播到所有子类;子类可通过 NotEditInlineNew 说明符覆盖它。
|
||||
|
||||
## 示例代码:
|
||||
|
||||
```cpp
|
||||
UCLASS(Blueprintable, EditInlineNew)
|
||||
class INSIDER_API UMyClass_EditInlineNew :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
int32 MyProperty;
|
||||
};
|
||||
|
||||
UCLASS(Blueprintable, NotEditInlineNew)
|
||||
class INSIDER_API UMyClass_NotEditInlineNew :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
int32 MyProperty;
|
||||
};
|
||||
|
||||
UCLASS(Blueprintable, BlueprintType)
|
||||
class INSIDER_API UMyClass_Edit_Test :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Instanced, Category = InstancedProperty)
|
||||
UMyClass_EditInlineNew* MyEditInlineNew;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Instanced, Category = InstancedProperty)
|
||||
UMyClass_NotEditInlineNew* MyNotEditInlineNew;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = NormalProperty)
|
||||
UMyClass_EditInlineNew* MyEditInlineNew_NotInstanced;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = NormalProperty)
|
||||
UMyClass_NotEditInlineNew* MyNotEditInlineNew_NotInstanced;
|
||||
};
|
||||
```
|
||||
|
||||
## 示例效果:
|
||||
|
||||
EditInlineNew支持直接C++或BP子类创建对象实例,然后在上面编辑实例。
|
||||
|
||||
而NotEditInlineNew的属性则无法找到支持的类来创建对象。
|
||||
|
||||
如果属性上没有Instanced则只能尝试去引用(找不到对象)。
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
判断该类是否有CLASS_EditInlineNew来决定是否可内联创建编辑。
|
||||
|
||||
```cpp
|
||||
template <typename TClass, typename TIsChildOfFunction>
|
||||
bool FPropertyEditorInlineClassFilter::IsClassAllowedHelper(TClass InClass, TIsChildOfFunction IsClassChildOf, TSharedRef< FClassViewerFilterFuncs > InFilterFuncs)
|
||||
{
|
||||
const bool bMatchesFlags = InClass->HasAnyClassFlags(CLASS_EditInlineNew) &&
|
||||
!InClass->HasAnyClassFlags(CLASS_Hidden | CLASS_HideDropDown | CLASS_Deprecated) &&
|
||||
(bAllowAbstract || !InClass->HasAnyClassFlags(CLASS_Abstract));
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 214 KiB |
@@ -0,0 +1,8 @@
|
||||
# NotEditInlineNew
|
||||
|
||||
- **功能描述:** 不能通过EditInline按钮创建
|
||||
- **引擎模块:** Instance
|
||||
- **元数据类型:** bool
|
||||
- **作用机制:** 在ClassFlags中移除[CLASS_EditInlineNew](../../../Flags/EClassFlags/CLASS_EditInlineNew.md)
|
||||
- **关联项:** EditInlineNew (EditInlineNew.md)
|
||||
- **常用程度:★**
|
@@ -0,0 +1,70 @@
|
||||
# Within
|
||||
|
||||
- **功能描述:** 指定对象创建的时候必须依赖于OuterClassName的对象作为Outer。
|
||||
- **引擎模块:** Instance
|
||||
- **元数据类型:** string="abc"
|
||||
- **作用机制:** 保存在UClass* UClass::ClassWithin=XXX的XXX中
|
||||
- **常用程度:★★★**
|
||||
|
||||
指定对象创建的时候必须依赖于OuterClassName的对象作为Outer。
|
||||
|
||||
此类的对象无法在OuterClassName对象的实例之外存在。这意味着,要创建此类的对象,需要提供OuterClassName的一个实例作为其Outer对象。
|
||||
|
||||
本类在这种情况一般是用来当做子对象来使用的。
|
||||
|
||||
## 示例代码:
|
||||
|
||||
```cpp
|
||||
UCLASS(Within= MyClass_Within_Outer)
|
||||
class INSIDER_API UMyClass_Within :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
};
|
||||
|
||||
UCLASS()
|
||||
class INSIDER_API UMyClass_Within_Outer :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
};
|
||||
|
||||
|
||||
```
|
||||
|
||||
## 示例结果:
|
||||
|
||||
```cpp
|
||||
//错误!Fatal error: Object MyClass_Within None created in Package instead of MyClass_Within_Outer
|
||||
UMyClass_Within* obj=NewObject<UMyClass_Within>();
|
||||
|
||||
//正确:
|
||||
UMyClass_Within_Outer* objOuter = NewObject<UMyClass_Within_Outer>();
|
||||
UMyClass_Within* obj=NewObject<UMyClass_Within>(objOuter);
|
||||
```
|
||||
|
||||
## 原理:
|
||||
|
||||
生成的UClass的字段:UClass* ClassWithin会保存这个信息,然后在创建的时候StaticAllocateObject会测试 check(bCreatingCDO || !InOuter || InOuter->IsA(InClass->ClassWithin))。因此需要先创建Within的对象。
|
||||
|
||||
```cpp
|
||||
bool StaticAllocateObjectErrorTests( const UClass* InClass, UObject* InOuter, FName InName, EObjectFlags InFlags)
|
||||
{
|
||||
if ( (InFlags & (RF_ClassDefaultObject|RF_ArchetypeObject)) == 0 )
|
||||
{
|
||||
if ( InOuter != NULL && !InOuter->IsA(InClass->ClassWithin) )
|
||||
{
|
||||
UE_LOG(LogUObjectGlobals, Fatal, TEXT("%s"), *FString::Printf( TEXT("Object %s %s created in %s instead of %s"), *InClass->GetName(), *InName.ToString(), *InOuter->GetClass()->GetName(), *InClass->ClassWithin->GetName()) );
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
在源码里可以搜索到很多Within的用法
|
||||
|
||||
UCLASS(Within=Engine, config=Engine, transient)
|
||||
class ENGINE_API ULocalPlayer
|
||||
|
||||
UCLASS(Abstract, DefaultToInstanced, Within=UserWidget)
|
||||
class UMG_API UUserWidgetExtension : public UObject
|
||||
{
|
@@ -0,0 +1,69 @@
|
||||
# ConversionRoot
|
||||
|
||||
- **功能描述:** 在场景编辑器里允许Actor在自身以及子类之间做转换
|
||||
- **引擎模块:** Scene
|
||||
- **元数据类型:** bool
|
||||
- **作用机制:** 在Meta中增加[IsConversionRoot](../../../../Meta/Blueprint/IsConversionRoot.md)
|
||||
- **常用程度:★**
|
||||
|
||||
一般是用在Actor上,在Actor转换的时候用来限制转换的级别。比如ASkeletalMeshActor,AStaticMeshActor等。
|
||||
|
||||
常常ComponentWrapperClass一起出现。
|
||||
|
||||
根据代码来说,meta中的IsConversionRoot会限制只传达到这一层,不继续往根上查找。
|
||||
|
||||
只有配有ConversionRoot的Actor才会允许Convert Actor,否则是禁用的。
|
||||
|
||||
## 示例代码:
|
||||
|
||||
```cpp
|
||||
//(BlueprintType = true, IncludePath = Class/Trait/MyClass_ConversionRoot.h, IsBlueprintBase = true, IsConversionRoot = true, ModuleRelativePath = Class/Trait/MyClass_ConversionRoot.h)
|
||||
UCLASS(Blueprintable,BlueprintType, ConversionRoot)
|
||||
class INSIDER_API AMyActor_ConversionRoot :public AActor
|
||||
{
|
||||
GENERATED_BODY()
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## 示例效果:
|
||||
|
||||
在蓝图中创建其子类BP_ConversionRoot_Child1和BP_ConversionRoot_Child2。然后把BP_ConversionRoot_Child1拖放进场景里创建个Actor,也创建个普通的蓝图Actor作为对比。
|
||||
|
||||

|
||||
|
||||
在关卡中选择Child1,会允许ConvertActor,在ConverstionRoot的自身以及所有子类之间做转换。
|
||||
|
||||

|
||||
|
||||
如果是普通的Actor,因为没有定义ConversionRoot,则不能做转换。
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
在关卡中的Actor选择:关卡中选择一个Actor,然后DetailsPanel里会显示ConverActor属性栏,可以选择另外一个Actor来进行改变。
|
||||
TSharedRef<SWidget> FActorDetails::MakeConvertMenu( const FSelectedActorInfo& SelectedActorInfo )
|
||||
这个函数就是用来创建Select Type的Combo Button的菜单的。内部会调用CreateClassPickerConvertActorFilter:
|
||||
|
||||
```cpp
|
||||
UClass* FActorDetails::GetConversionRoot( UClass* InCurrentClass ) const
|
||||
{
|
||||
UClass* ParentClass = InCurrentClass;
|
||||
|
||||
while(ParentClass)
|
||||
{
|
||||
if( ParentClass->GetBoolMetaData(FName(TEXT("IsConversionRoot"))) )
|
||||
{
|
||||
break;
|
||||
}
|
||||
ParentClass = ParentClass->GetSuperClass();
|
||||
}
|
||||
|
||||
return ParentClass;
|
||||
}
|
||||
|
||||
void FActorDetails::CreateClassPickerConvertActorFilter(const TWeakObjectPtr<AActor> ConvertActor, class FClassViewerInitializationOptions* ClassPickerOptions)
|
||||
Filter->AllowedChildOfRelationship.Add(RootConversionClass);//限定这个基类以下的其他子类
|
||||
|
||||
```
|
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 196 KiB |
@@ -0,0 +1,65 @@
|
||||
# NotPlaceable
|
||||
|
||||
- **功能描述:** 标明该Actor不可被放置在关卡里
|
||||
- **引擎模块:** Behavior
|
||||
- **元数据类型:** bool
|
||||
- **作用机制:** 在ClassFlags中添加[CLASS_NotPlaceable](../../../../Flags/EClassFlags/CLASS_NotPlaceable.md)
|
||||
- **关联项:** Placeable (Placeable.md)
|
||||
- **常用程度:★★★**
|
||||
|
||||
标明该Actor不可被放置在关卡里,没法拖放到场景里。使继承自基类的Placeable说明符无效。会在ClassFlagss里标记上CLASS_NotPlaceable,这个标记是可以继承的,意味着其所有的子类默认都不可放置。例如AWorldSettings其实就是一个notplaceable的Actor。
|
||||
|
||||
但是注意该类依然可以通过SpawnActor动态生成到关卡中。
|
||||
|
||||
NotPlaceable的类是不出现在PlaceMode的类选择里去的。
|
||||
|
||||
## 示例代码:
|
||||
|
||||
```cpp
|
||||
UCLASS(Blueprintable,BlueprintType, NotPlaceable)
|
||||
class INSIDER_API AMyActor_NotPlaceable :public AActor
|
||||
{
|
||||
GENERATED_BODY()
|
||||
};
|
||||
```
|
||||
|
||||
## 示例效果:
|
||||
|
||||
拖动到场景里会发现不能创建Actor。
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
如果直接是C++类AMyActor_NotPlaceable ,是可以直接从ContentBrowser拖到场景里去的。看源码可知,只有BP继承下来的子类才有受到这个限制。
|
||||
|
||||
```cpp
|
||||
TArray<AActor*> FLevelEditorViewportClient::TryPlacingActorFromObject( ULevel* InLevel, UObject* ObjToUse, bool bSelectActors, EObjectFlags ObjectFlags, UActorFactory* FactoryToUse, const FName Name, const FViewportCursorLocation* Cursor )
|
||||
{
|
||||
|
||||
bool bPlace = true;
|
||||
if (ObjectClass->IsChildOf(UBlueprint::StaticClass()))
|
||||
{
|
||||
UBlueprint* BlueprintObj = StaticCast<UBlueprint*>(ObjToUse);
|
||||
bPlace = BlueprintObj->GeneratedClass != NULL;
|
||||
if(bPlace)
|
||||
{
|
||||
check(BlueprintObj->ParentClass == BlueprintObj->GeneratedClass->GetSuperClass());
|
||||
if (BlueprintObj->GeneratedClass->HasAnyClassFlags(CLASS_NotPlaceable | CLASS_Abstract))
|
||||
{
|
||||
bPlace = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bPlace)
|
||||
{
|
||||
PlacedActor = FActorFactoryAssetProxy::AddActorForAsset( ObjToUse, bSelectActors, ObjectFlags, FactoryToUse, Name );
|
||||
if ( PlacedActor != NULL )
|
||||
{
|
||||
PlacedActors.Add(PlacedActor);
|
||||
PlacedActor->PostEditMove(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 155 KiB |
@@ -0,0 +1,33 @@
|
||||
# Placeable
|
||||
|
||||
- **功能描述:** 标明该Actor可以放置在关卡里。
|
||||
- **引擎模块:** Scene
|
||||
- **元数据类型:** bool
|
||||
- **作用机制:** 在ClassFlags中移除[CLASS_NotPlaceable](../../../../Flags/EClassFlags/CLASS_NotPlaceable.md)
|
||||
- **关联项:** [NotPlaceable](../NotPlaceable/NotPlaceable.md)
|
||||
- **常用程度:★★★**
|
||||
|
||||
标明该Actor可以放置在关卡里。
|
||||
|
||||
默认情况下是placeable的,因此源码里目前没有用到Placeable的地方。
|
||||
|
||||
子类可使用NotPlaceable说明符覆盖此标志,正如AInfo之类的上面自己设置NotPlaceable。
|
||||
|
||||
指示可在编辑器中创建此类,而且可将此类放置到关卡、UI场景或蓝图(取决于类类型)中。此标志会传播到所有子类;
|
||||
|
||||
placeable没法清除父类的notplaceable标记。
|
||||
|
||||
## 示例代码:
|
||||
|
||||
```cpp
|
||||
UCLASS(Blueprintable, BlueprintType,placeable)
|
||||
class INSIDER_API AMyActor_Placeable :public AMyActor_NotPlaceable
|
||||
{
|
||||
GENERATED_BODY()
|
||||
};
|
||||
error : The 'placeable' specifier cannot override a 'nonplaceable' base class. Classes are assumed to be placeable by default. Consider whether using the 'abstract' specifier on the base class would work.
|
||||
```
|
||||
|
||||
## 示例效果:
|
||||
|
||||

|
After Width: | Height: | Size: 125 KiB |
@@ -0,0 +1,154 @@
|
||||
# MatchedSerializers
|
||||
|
||||
- **功能描述:** 指定类支持文本结构序列化
|
||||
- **引擎模块:** Serialization
|
||||
- **元数据类型:** bool
|
||||
- **作用机制:** 在ClassFlags中增加[CLASS_MatchedSerializers](../../../../Flags/EClassFlags/CLASS_MatchedSerializers.md),在Meta中添加[MatchedSerializers](../../../../Meta/Serialization/MatchedSerializers.md)
|
||||
- **常用程度:** 0
|
||||
|
||||
该标识符只允许在NoExportTypes.h中使用,属于是引擎自用的内部标识符。
|
||||
|
||||
基本上大部分的类都拥有该标记,除了自身不导出的类,一般包括NoExportTypes.h定义的(除非手动加上MatchedSerializers,比如UObject),或者靠DECLARE_CLASS_INTRINSIC直接在源码里定义的元数据。
|
||||
|
||||
因此实际上大部分的类都拥有该标记。因为在UHT中只要不是NoExport的,就会自动的加上这个标记。
|
||||
|
||||
```cpp
|
||||
// Force the MatchedSerializers on for anything being exported
|
||||
if (!ClassExportFlags.HasAnyFlags(UhtClassExportFlags.NoExport))
|
||||
{
|
||||
ClassFlags |= EClassFlags.MatchedSerializers;
|
||||
}
|
||||
```
|
||||
|
||||
## 结构化序列化器:
|
||||
|
||||
如果一个类支持文本格式,则StructuredArchive的结构的意思是会把类里的字段树形展开来序列化展示出来,从而方便人类理解。而如果不支持文本格式,则会把所有的字段值压进一个二进制buffer里(Data字段),这也是runtime时候用的方式。
|
||||
|
||||
测试代码:
|
||||
|
||||
```cpp
|
||||
|
||||
UCLASS(Blueprintable, BlueprintType,editinlinenew)
|
||||
class INSIDER_API UMyClass_MatchedSerializersSub :public UObject
|
||||
{
|
||||
public:
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
int32 MyInt_Default = 123;
|
||||
};
|
||||
|
||||
UCLASS(Blueprintable, BlueprintType)
|
||||
class INSIDER_API UMyClass_MatchedSerializersTestAsset:public UDataAsset
|
||||
{
|
||||
public:
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
int32 MyInt_Default = 123;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite,Instanced)
|
||||
UMyClass_MatchedSerializersSub* SubObject;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
UStruct* MyStructType;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
UClass* MyClassType;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
UEnum* MyEnumType;
|
||||
};
|
||||
|
||||
void UMyClass_MatchedSerializers_Test::ApplyClassFlag()
|
||||
{
|
||||
UMyClass_MatchedSerializersTestAsset::StaticClass()->ClassFlags |= CLASS_MatchedSerializers;
|
||||
UMyClass_MatchedSerializersSub::StaticClass()->ClassFlags |= CLASS_MatchedSerializers;
|
||||
}
|
||||
|
||||
void UMyClass_MatchedSerializers_Test::RemoveClassFlag()
|
||||
{
|
||||
UMyClass_MatchedSerializersTestAsset::StaticClass()->ClassFlags &= ~CLASS_MatchedSerializers;
|
||||
UMyClass_MatchedSerializersSub::StaticClass()->ClassFlags &= ~CLASS_MatchedSerializers;
|
||||
}
|
||||
```
|
||||
|
||||
在编辑器中创建测试数据Asset
|
||||
|
||||

|
||||
|
||||
然后在Editor选项里打开TextAssetFormatSupport(UEditorExperimentalSettings::bTextAssetFormatSupport)
|
||||
|
||||

|
||||
|
||||
然后在资产上就出现3个菜单支持把资产导出为文本。
|
||||
|
||||

|
||||
|
||||
ExportToTextFormat会在蓝图资产的同目录生成一个.utxt的文件,格式为json。通过动态的增删CLASS_MatchedSerializers这个标记来对比这个标记产生的差异:
|
||||
|
||||

|
||||
|
||||
可以发现,序列化出来的内容有明显的差异,不带有CLASS_MatchedSerializers标记的产生的右侧结果,把所有的字段值压进一个二进制buffer里(Data字段)。
|
||||
|
||||
## 内部机制原理:
|
||||
|
||||
CLASS_MatchedSerializers这个标记在UClass::IsSafeToSerializeToStructuredArchives中被使用,标明采用结构序列化器。是否支持文本导入导出,只在编辑器情况下使用。
|
||||
|
||||
在发生作用的只有SavePackage2.cpp和LinkerLoad.cpp,因此是只发生在保存UPackage的时候,作为子类对象。所以不能用简单的内存里Archive序列化来进行测试。
|
||||
|
||||
```cpp
|
||||
bool UClass::IsSafeToSerializeToStructuredArchives(UClass* InClass)
|
||||
{
|
||||
while (InClass)
|
||||
{
|
||||
if (!InClass->HasAnyClassFlags(CLASS_MatchedSerializers))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
InClass = InClass->GetSuperClass();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//LinkerLoad.cpp
|
||||
bool bClassSupportsTextFormat = UClass::IsSafeToSerializeToStructuredArchives(Object->GetClass());
|
||||
if (IsTextFormat())//如果Ar序列化是文本格式
|
||||
{
|
||||
FStructuredArchiveSlot ExportSlot = GetExportSlot(Export.ThisIndex);
|
||||
|
||||
if (bClassSupportsTextFormat) //如果类本身支持文本格式
|
||||
{
|
||||
Object->GetClass()->SerializeDefaultObject(Object, ExportSlot);
|
||||
}
|
||||
else
|
||||
{
|
||||
FStructuredArchiveChildReader ChildReader(ExportSlot);
|
||||
FArchiveUObjectFromStructuredArchive Adapter(ChildReader.GetRoot());
|
||||
Object->GetClass()->SerializeDefaultObject(Object, Adapter.GetArchive());
|
||||
}
|
||||
}
|
||||
|
||||
//SavePackage2.cpp
|
||||
#if WITH_EDITOR
|
||||
bool bSupportsText = UClass::IsSafeToSerializeToStructuredArchives(Export.Object->GetClass());
|
||||
#else
|
||||
bool bSupportsText = false;
|
||||
#endif
|
||||
|
||||
if (bSupportsText)
|
||||
{
|
||||
Export.Object->GetClass()->SerializeDefaultObject(Export.Object, ExportSlot);
|
||||
}
|
||||
else
|
||||
{
|
||||
FArchiveUObjectFromStructuredArchive Adapter(ExportSlot);
|
||||
Export.Object->GetClass()->SerializeDefaultObject(Export.Object, Adapter.GetArchive());
|
||||
Adapter.Close();
|
||||
}
|
||||
```
|
||||
|
||||
文本格式只在编辑器环境下生效。
|
||||
|
||||
可以从源码看到,如果类本身支持文本格式序列化,则在Ar是文本格式的时候,直接可以序列化,采用默认的SerializeTaggedProperties。否则得采用FArchiveUObjectFromStructuredArchive 来适配一下,把对象指针转换为object path+ int32 Index的组合。
|
||||
|
||||
在引擎中打印出所有包含或不包含CLASS_MatchedSerializers的类,发现UStruct继承链下面的类开始包含(但是UClass却不包含),而上面UField的类则不包含,比如各种Property。类列表见Doc下txt文件。
|
After Width: | Height: | Size: 4.5 KiB |
After Width: | Height: | Size: 112 KiB |
After Width: | Height: | Size: 138 KiB |
After Width: | Height: | Size: 38 KiB |
@@ -0,0 +1,8 @@
|
||||
# NonTransient
|
||||
|
||||
- **功能描述:** 使继承自基类的Transient说明符无效。
|
||||
- **引擎模块:** Serialization
|
||||
- **元数据类型:** bool
|
||||
- **作用机制:** 在ClassFlags中移除[CLASS_Transient](../../../Flags/EClassFlags/CLASS_Transient.md)
|
||||
- **关联项:** [Transient](Transient/Transient.md)
|
||||
- **常用程度:** ★★★
|
@@ -0,0 +1,275 @@
|
||||
# Optional
|
||||
|
||||
- **功能描述:** 标记该类的对象是可选的,在Cooking的时候可以选择是否要忽略保存它们。
|
||||
|
||||
- **引擎模块:** Serialization
|
||||
- **作用机制:** 在ClassFlags中添加[CLASS_Optional](../../../../Flags/EClassFlags/CLASS_Optional.md)
|
||||
- **常用程度:** ★
|
||||
|
||||
标记该类的对象是可选的,在Cooking的时候可以选择是否要忽略保存它们。
|
||||
|
||||
- 一般为EditorOnly的数据,如MetaData等,在游戏运行时不存在,保存在其他的特定文件中。
|
||||
- Optional的对象一般也包在WITH_EDITORONLY_DATA宏里,只在编辑器下使用。
|
||||
- 引擎在cook的时候,会根据EDITOROPTIONAL的配置来加上SAVE_Optional,从而选择是否一起序列化该对象值,比如metadata。
|
||||
|
||||
## 示例代码:
|
||||
|
||||
```cpp
|
||||
//ClassFlags: CLASS_Optional | CLASS_MatchedSerializers | CLASS_Native | CLASS_RequiredAPI | CLASS_TokenStreamAssembled | CLASS_Intrinsic | CLASS_Constructed
|
||||
UCLASS(Optional)
|
||||
class INSIDER_API UMyClass_Optional :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
int32 MyProperty = 123;
|
||||
};
|
||||
|
||||
UCLASS()
|
||||
class INSIDER_API UMyClass_NotOptional :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
int32 MyProperty = 123;
|
||||
};
|
||||
|
||||
UCLASS()
|
||||
class INSIDER_API UMyClass_Optional_Test :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
|
||||
#if WITH_EDITORONLY_DATA
|
||||
UPROPERTY()
|
||||
UMyClass_Optional* MyOptionalObject;
|
||||
|
||||
#endif // WITH_EDITORONLY_DATA
|
||||
|
||||
public:
|
||||
UPROPERTY()
|
||||
UMyClass_NotOptional* MyNotOptionalObject;
|
||||
public:
|
||||
static void CreatePackageAndSave();
|
||||
static void LoadPackageAndTest();
|
||||
};
|
||||
|
||||
void UMyClass_Optional_Test::CreatePackageAndSave()
|
||||
{
|
||||
FString packageName = TEXT("/Game/MyOptionTestPackage");
|
||||
FString assetPath = FPackageName::LongPackageNameToFilename(packageName, FPackageName::GetAssetPackageExtension());
|
||||
|
||||
IFileManager::Get().Delete(*assetPath, false, true);
|
||||
|
||||
UPackage* package = CreatePackage(*packageName);
|
||||
FSavePackageArgs saveArgs{};
|
||||
//saveArgs.TopLevelFlags = EObjectFlags::RF_Public | EObjectFlags::RF_Standalone;
|
||||
saveArgs.Error = GError;
|
||||
saveArgs.SaveFlags=SAVE_NoError;
|
||||
|
||||
//SAVE_Optional = 0x00008000, ///< Indicate that we to save optional exports. This flag is only valid while cooking. Optional exports are filtered if not specified during cooking.
|
||||
|
||||
UMyClass_Optional_Test* testObject = NewObject<UMyClass_Optional_Test>(package, TEXT("testObject"));
|
||||
|
||||
#if WITH_EDITORONLY_DATA
|
||||
testObject->MyOptionalObject = NewObject<UMyClass_Optional>(testObject, TEXT("MyOptionalObject"));
|
||||
testObject->MyOptionalObject->MyProperty = 456;
|
||||
#endif
|
||||
|
||||
testObject->MyNotOptionalObject = NewObject<UMyClass_NotOptional>(testObject, TEXT("MyNotOptionalObject"));
|
||||
|
||||
testObject->MyNotOptionalObject->MyProperty = 456;
|
||||
|
||||
FString str = UInsiderSubsystem::Get().PrintObject(package, EInsiderPrintFlags::All);
|
||||
FString str2 = UInsiderSubsystem::Get().PrintObject(testObject, EInsiderPrintFlags::All);
|
||||
FString str3 = UInsiderSubsystem::Get().PrintObject(UMyClass_Optional::StaticClass(), EInsiderPrintFlags::All);
|
||||
FString str4 = UInsiderSubsystem::Get().PrintObject(UMyClass_NotOptional::StaticClass(), EInsiderPrintFlags::All);
|
||||
|
||||
bool result = UPackage::SavePackage(package, testObject, *assetPath, saveArgs);
|
||||
|
||||
}
|
||||
|
||||
void UMyClass_Optional_Test::LoadPackageAndTest()
|
||||
{
|
||||
FString packageName = TEXT("/Game/MyOptionTestPackage");
|
||||
FString assetPath = FPackageName::LongPackageNameToFilename(packageName, FPackageName::GetAssetPackageExtension());
|
||||
|
||||
UPackage* package = LoadPackage(nullptr, *assetPath, LOAD_None);
|
||||
package->FullyLoad();
|
||||
|
||||
UMyClass_Optional_Test* newTestObject = LoadObject<UMyClass_Optional_Test>(package, TEXT("testObject"), *assetPath);
|
||||
//UMyClass_Transient_Test* newTestObject = nullptr;
|
||||
|
||||
/*const TArray<FObjectExport>& exportMap = package->GetLinker()->ExportMap;
|
||||
for (const auto& objExport : exportMap)
|
||||
{
|
||||
if (objExport.ObjectName == TEXT("testObject"))
|
||||
{
|
||||
newTestObject = Cast<UMyClass_Transient_Test>(objExport.Object);
|
||||
break;
|
||||
}
|
||||
}*/
|
||||
FString str = UInsiderSubsystem::Get().PrintObject(package, EInsiderPrintFlags::All);
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## 示例效果:
|
||||
|
||||
正常的SavePackage发现是没有作用的,依然会序列化保存。特殊的保存方式在Cook阶段,本例就没有专门测试了。
|
||||
|
||||

|
||||
|
||||
在源码里搜索Optional,可以看到一般是EditorOnlyData和CookedMetaData类在使用。
|
||||
|
||||
```cpp
|
||||
UCLASS(Optional, Within=Enum)
|
||||
class ENGINE_API UEnumCookedMetaData : public UObject
|
||||
UCLASS(Optional, Within=ScriptStruct)
|
||||
class ENGINE_API UStructCookedMetaData : public UObject
|
||||
UCLASS(Optional, Within=Class)
|
||||
class ENGINE_API UClassCookedMetaData : public UObject
|
||||
|
||||
UMaterialInterfaceEditorOnlyData* UMaterialInterface::CreateEditorOnlyData()
|
||||
{
|
||||
const UClass* EditorOnlyClass = GetEditorOnlyDataClass();
|
||||
check(EditorOnlyClass);
|
||||
check(EditorOnlyClass->HasAllClassFlags(CLASS_Optional));
|
||||
|
||||
const FString EditorOnlyName = MaterialInterface::GetEditorOnlyDataName(*GetName());
|
||||
const EObjectFlags EditorOnlyFlags = GetMaskedFlags(RF_PropagateToSubObjects);
|
||||
return NewObject<UMaterialInterfaceEditorOnlyData>(this, EditorOnlyClass, *EditorOnlyName, EditorOnlyFlags);
|
||||
}
|
||||
```
|
||||
|
||||
引擎里也有一些验证:
|
||||
|
||||
```cpp
|
||||
UnrealTypeDefinitionInfo.cpp:
|
||||
// Validate if we are using editor only data in a class or struct definition
|
||||
if (HasAnyClassFlags(CLASS_Optional))
|
||||
{
|
||||
for (TSharedRef<FUnrealPropertyDefinitionInfo> PropertyDef : GetProperties())
|
||||
{
|
||||
if (PropertyDef->GetPropertyBase().IsEditorOnlyProperty())
|
||||
{
|
||||
PropertyDef->LogError(TEXT("Cannot specify editor only property inside an optional class."));
|
||||
}
|
||||
else if (PropertyDef->GetPropertyBase().ContainsEditorOnlyProperties())
|
||||
{
|
||||
PropertyDef->LogError(TEXT("Do not specify struct property containing editor only properties inside an optional class."));
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
通过源码发现:
|
||||
|
||||
//SAVE_Optional = 0x00008000, ///< Indicate that we to save optional exports. This flag is only valid while cooking. Optional exports are filtered if not specified during cooking.
|
||||
|
||||
这个SAVE_Optional 作用于UUserDefinedEnum,UUserDefinedStruct,UBlueprintGeneratedClass的MetaData对象上。
|
||||
|
||||
```cpp
|
||||
void UUserDefinedStruct::PreSaveRoot(FObjectPreSaveRootContext ObjectSaveContext)
|
||||
{
|
||||
Super::PreSaveRoot(ObjectSaveContext);
|
||||
|
||||
if (ObjectSaveContext.IsCooking() && (ObjectSaveContext.GetSaveFlags() & SAVE_Optional))
|
||||
{
|
||||
//这个对象是以this为Outer的,标记RF_Standalone | RF_Public,会造成该子对象被序列化下来
|
||||
UStructCookedMetaData* CookedMetaData = NewCookedMetaData();
|
||||
CookedMetaData->CacheMetaData(this);
|
||||
|
||||
if (!CookedMetaData->HasMetaData())
|
||||
{
|
||||
PurgeCookedMetaData();//清理掉这个CookedMetaData对象
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
PurgeCookedMetaData();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
另外,在cook的时候,如果指定
|
||||
|
||||
bCookEditorOptional = Switches.Contains(TEXT("EDITOROPTIONAL")); // Produce the optional editor package data alongside the cooked data.
|
||||
|
||||
则会加上CookEditorOptional 的标识
|
||||
|
||||
CookFlags |= bCookEditorOptional ? ECookInitializationFlags::CookEditorOptional : ECookInitializationFlags::None;
|
||||
|
||||
再之后则会传达SAVE_Optional 给Package的SaveFlags
|
||||
|
||||
SaveFlags |= COTFS.IsCookFlagSet(ECookInitializationFlags::CookEditorOptional) ? SAVE_Optional : SAVE_None;
|
||||
|
||||
从而在包括Package的时候,IsSaveOptional()的判断会造成是否创建Optional的Realm,
|
||||
|
||||
```cpp
|
||||
TArray<ESaveRealm> FSaveContext::GetHarvestedRealmsToSave()
|
||||
{
|
||||
TArray<ESaveRealm> HarvestedContextsToSave;
|
||||
if (IsCooking())
|
||||
{
|
||||
HarvestedContextsToSave.Add(ESaveRealm::Game);
|
||||
if (IsSaveOptional())
|
||||
{
|
||||
HarvestedContextsToSave.Add(ESaveRealm::Optional);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
HarvestedContextsToSave.Add(ESaveRealm::Editor);
|
||||
}
|
||||
return HarvestedContextsToSave;
|
||||
}
|
||||
```
|
||||
|
||||
还有如果发现Object有CLASS_Optional,则不把它当做Export(子对象),而是当做Import(引用的对象),Optional对象有可能放在外部独立的文件中。
|
||||
|
||||
```cpp
|
||||
ESavePackageResult HarvestPackage(FSaveContext& SaveContext)
|
||||
{
|
||||
// If we have a valid optional context and we are saving it,
|
||||
// transform any harvested non optional export into imports
|
||||
// Mark other optional import package as well
|
||||
if (!SaveContext.IsSaveAutoOptional() &&
|
||||
SaveContext.IsSaveOptional() &&
|
||||
SaveContext.IsCooking() &&
|
||||
SaveContext.GetHarvestedRealm(ESaveRealm::Optional).GetExports().Num() &&
|
||||
SaveContext.GetHarvestedRealm(ESaveRealm::Game).GetExports().Num())
|
||||
{
|
||||
bool bHasNonOptionalSelfReference = false;
|
||||
FHarvestedRealm& OptionalContext = SaveContext.GetHarvestedRealm(ESaveRealm::Optional);
|
||||
for (auto It = OptionalContext.GetExports().CreateIterator(); It; ++It)
|
||||
{
|
||||
if (!It->Obj->GetClass()->HasAnyClassFlags(CLASS_Optional))
|
||||
{
|
||||
// Make sure the export is found in the game context as well
|
||||
if (FTaggedExport* GameExport = SaveContext.GetHarvestedRealm(ESaveRealm::Game).GetExports().Find(It->Obj))
|
||||
{
|
||||
// Flag the export in the game context to generate it's public hash
|
||||
GameExport->bGeneratePublicHash = true;
|
||||
// Transform the export as an import
|
||||
OptionalContext.AddImport(It->Obj);
|
||||
// Flag the package itself to be an import
|
||||
bHasNonOptionalSelfReference = true;
|
||||
}
|
||||
// if not found in the game context and the reference directly came from an optional object, record an illegal reference
|
||||
else if (It->bFromOptionalReference)
|
||||
{
|
||||
SaveContext.RecordIllegalReference(nullptr, It->Obj, EIllegalRefReason::ReferenceFromOptionalToMissingGameExport);
|
||||
}
|
||||
It.RemoveCurrent();
|
||||
}
|
||||
}
|
||||
// Also add the current package itself as an import if we are referencing any non optional export
|
||||
if (bHasNonOptionalSelfReference)
|
||||
{
|
||||
OptionalContext.AddImport(SaveContext.GetPackage());
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 35 KiB |
@@ -0,0 +1,184 @@
|
||||
# Transient
|
||||
|
||||
- **功能描述:** 指定该类的所有对象都略过序列化。
|
||||
|
||||
- **引擎模块:** Serialization
|
||||
- **元数据类型:** bool
|
||||
- **作用机制:** 在ClassFlags中添加[CLASS_Transient](../../../../Flags/EClassFlags/CLASS_Transient.md)
|
||||
- **关联项:** [NonTransient](../NonTransient.md)
|
||||
- **常用程度:** ★★★
|
||||
|
||||
指定该类的所有对象都略过序列化。
|
||||
|
||||
- 从不将属于此类的对象保存到磁盘。此说明符会传播到子类,但是可由NonTransient说明符覆盖。可以在子类被改写.
|
||||
- 会造成相应Object的RF_Transient标记。
|
||||
- 注意:UPROPERTY(Transient)只是指定这一个特定的属性不序列化。而UCLASS(Transient)是作用于该类的所有对象。
|
||||
|
||||
## 示例代码:
|
||||
|
||||
```cpp
|
||||
UCLASS(Blueprintable, Transient)
|
||||
class INSIDER_API UMyClass_Transient :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
int32 MyProperty = 123;
|
||||
};
|
||||
|
||||
UCLASS(Blueprintable, NonTransient)
|
||||
class INSIDER_API UMyClass_NonTransient :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
int32 MyProperty = 123;
|
||||
};
|
||||
|
||||
UCLASS(Blueprintable, BlueprintType)
|
||||
class INSIDER_API UMyClass_Transient_Test :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
UMyClass_Transient* MyTransientObject;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
UMyClass_NonTransient* MyNonTransientObject;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
int32 MyInt_Normal=123;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite,Transient)
|
||||
int32 MyInt_Transient =123;
|
||||
};
|
||||
```
|
||||
|
||||
序列化对象指针关键是采用FObjectProperty里的SerializeItem方法Slot << ObjectValue;
|
||||
|
||||
不能采用Actor的Class Default来测试,因为:
|
||||
|
||||
Transient properties are serialized for (Blueprint) Class Default Objects but should not be serialized for any 'normal' instances of classes.
|
||||
|
||||
```cpp
|
||||
UCLASS(Blueprintable, BlueprintType)
|
||||
class INSIDER_API AMyActor_Transient_Test :public AActor
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
UMyClass_Transient* MyTransientObject;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
UMyClass_NonTransient* MyNonTransientObject;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
int32 MyInt_Normal=123;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite,Transient)
|
||||
int32 MyInt_Transient =123;
|
||||
};
|
||||
```
|
||||
|
||||
也不能用FObjectAndNameAsStringProxyArchive测试,因为FObjectAndNameAsStringProxyArchive内部在序列化对象的时候,会先查找名字。
|
||||
|
||||
```cpp
|
||||
FArchive& FObjectAndNameAsStringProxyArchive::operator<<(UObject*& Obj)
|
||||
{
|
||||
if (IsLoading())
|
||||
{
|
||||
// load the path name to the object
|
||||
FString LoadedString;
|
||||
InnerArchive << LoadedString;
|
||||
// look up the object by fully qualified pathname
|
||||
Obj = FindObject<UObject>(nullptr, *LoadedString, false);
|
||||
// If we couldn't find it, and we want to load it, do that
|
||||
if(!Obj && bLoadIfFindFails)
|
||||
{
|
||||
Obj = LoadObject<UObject>(nullptr, *LoadedString);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// save out the fully qualified object name
|
||||
FString SavedString(Obj->GetPathName());
|
||||
InnerArchive << SavedString;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
```
|
||||
|
||||
因此采用Package的测试:
|
||||
|
||||
```cpp
|
||||
|
||||
FString packageName = TEXT("/Game/MyTestPackage");
|
||||
FString assetPath = FPackageName::LongPackageNameToFilename(packageName, FPackageName::GetAssetPackageExtension());
|
||||
UPackage* package = CreatePackage(*packageName);
|
||||
FSavePackageArgs saveArgs{};
|
||||
saveArgs.Error = GError;
|
||||
|
||||
//ObjectFlags: RF_NoFlags
|
||||
UMyClass_Transient_Test* testObject = NewObject<UMyClass_Transient_Test>(package, TEXT("testObject"));
|
||||
//ObjectFlags: RF_Transient
|
||||
testObject->MyTransientObject = NewObject<UMyClass_Transient>(testObject, TEXT("MyTransientObject"));
|
||||
//ObjectFlags: RF_NoFlags
|
||||
testObject->MyNonTransientObject = NewObject<UMyClass_NonTransient>(testObject, TEXT("MyNonTransientObject"));
|
||||
|
||||
testObject->MyTransientObject->MyProperty = 456;
|
||||
testObject->MyNonTransientObject->MyProperty = 456;
|
||||
testObject->MyInt_Normal = 456;
|
||||
testObject->MyInt_Transient = 456;
|
||||
|
||||
bool result = UPackage::SavePackage(package, testObject, *assetPath, saveArgs);
|
||||
```
|
||||
|
||||
在保存完成之后,重新加载Package:
|
||||
|
||||
```cpp
|
||||
FString packageName = TEXT("/Game/MyTestPackage");
|
||||
FString assetPath = FPackageName::LongPackageNameToFilename(packageName, FPackageName::GetAssetPackageExtension());
|
||||
|
||||
UPackage* package = LoadPackage(nullptr, *assetPath, LOAD_None);
|
||||
package->FullyLoad();
|
||||
|
||||
UMyClass_Transient_Test* newTestObject=LoadObject<UMyClass_Transient_Test>(package, TEXT("testObject"),*assetPath);
|
||||
```
|
||||
|
||||
## 示例效果:
|
||||
|
||||
可以看到MyTransientObject 并没有被序列化到磁盘上,因此不会加载出来。
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
在SavePackage的时候:RF_Transient会导致这个对象不会被HarvestExport,不会被放进SaveContext里
|
||||
|
||||
```cpp
|
||||
void FPackageHarvester::TryHarvestExport(UObject* InObject)
|
||||
{
|
||||
// Those should have been already validated
|
||||
check(InObject && InObject->IsInPackage(SaveContext.GetPackage()));
|
||||
|
||||
// Get the realm in which we should harvest this export
|
||||
EIllegalRefReason Reason = EIllegalRefReason::None;
|
||||
ESaveRealm HarvestContext = GetObjectHarvestingRealm(InObject, Reason);
|
||||
if (!SaveContext.GetHarvestedRealm(HarvestContext).IsExport(InObject))
|
||||
{
|
||||
SaveContext.MarkUnsaveable(InObject);
|
||||
bool bExcluded = false;
|
||||
if (!InObject->HasAnyFlags(RF_Transient))
|
||||
{
|
||||
bExcluded = ConditionallyExcludeObjectForTarget(SaveContext, InObject, HarvestContext);
|
||||
}
|
||||
if (!InObject->HasAnyFlags(RF_Transient) && !bExcluded)
|
||||
{
|
||||
// It passed filtering so mark as export
|
||||
HarvestExport(InObject, HarvestContext);
|
||||
}
|
||||
|
||||
// If we have a illegal ref reason, record it
|
||||
if (Reason != EIllegalRefReason::None)
|
||||
{
|
||||
SaveContext.RecordIllegalReference(CurrentExportDependencies.CurrentExport, InObject, Reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 24 KiB |
@@ -0,0 +1,81 @@
|
||||
# HideDropDown
|
||||
|
||||
- **功能描述:** 在类选择器中隐藏此类
|
||||
- **引擎模块:** TypePicker
|
||||
- **元数据类型:** bool
|
||||
- **作用机制:** 在ClassFlags中添加[CLASS_HideDropDown](../../../../Flags/EClassFlags/CLASS_HideDropDown.md)
|
||||
- **常用程度:★★**
|
||||
|
||||
在类选择器中隐藏此类,通常是TSubClassOf触发,或者Class变量触发的类选择窗口。这个时候,这个标识符可以阻止其出现。在源码里的使用,通常是一些旧的废弃的类或者Test类,Abstract类和基类。
|
||||
|
||||
## 示例代码:
|
||||
|
||||
```cpp
|
||||
UCLASS(Blueprintable)
|
||||
class INSIDER_API UMyClass_HideDropDownBase :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
};
|
||||
|
||||
UCLASS(Blueprintable, hidedropdown)
|
||||
class INSIDER_API UMyClass_HideDropDown :public UMyClass_HideDropDownBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
int32 MyProperty;
|
||||
};
|
||||
|
||||
UCLASS(Blueprintable, hidedropdown)
|
||||
class INSIDER_API UMyClass_NoHideDropDown :public UMyClass_HideDropDownBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
int32 MyProperty;
|
||||
};
|
||||
|
||||
UCLASS(Blueprintable)
|
||||
class INSIDER_API UMyClass_HideDropDown_Test :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
TSubclassOf<UMyClass_HideDropDownBase> DropDownClass;
|
||||
};
|
||||
```
|
||||
|
||||
## 示例结果:
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
HideDropDown会造成CLASS_HideDropDown标记,从而在类型UI定制化的列表里把该类剔除出去。
|
||||
|
||||
```cpp
|
||||
template <typename TClass>
|
||||
bool FPropertyEditorClassFilter::IsClassAllowedHelper(TClass InClass)
|
||||
{
|
||||
bool bMatchesFlags = !InClass->HasAnyClassFlags(CLASS_Hidden | CLASS_HideDropDown | CLASS_Deprecated) &&
|
||||
(bAllowAbstract || !InClass->HasAnyClassFlags(CLASS_Abstract));
|
||||
|
||||
if (bMatchesFlags && InClass->IsChildOf(ClassPropertyMetaClass)
|
||||
&& (!InterfaceThatMustBeImplemented || InClass->ImplementsInterface(InterfaceThatMustBeImplemented)))
|
||||
{
|
||||
auto PredicateFn = [InClass](const UClass* Class)
|
||||
{
|
||||
return InClass->IsChildOf(Class);
|
||||
};
|
||||
|
||||
if (DisallowedClassFilters.FindByPredicate(PredicateFn) == nullptr &&
|
||||
(AllowedClassFilters.Num() == 0 || AllowedClassFilters.FindByPredicate(PredicateFn) != nullptr))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
```
|
After Width: | Height: | Size: 40 KiB |
@@ -0,0 +1,14 @@
|
||||
# CustomConstructor
|
||||
|
||||
- **功能描述:** 阻止构造函数声明自动生成。
|
||||
- **引擎模块:** UHT
|
||||
- **元数据类型:** bool
|
||||
- **作用机制:** 在ClassFlags中添加[CLASS_CustomConstructor](../../../Flags/EClassFlags/CLASS_CustomConstructor.md)
|
||||
|
||||
UHT不会生成 NO_API UMyClass_ModuleAPI(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());的默认构造函数。但是这个一般都是配合GENERATED_UCLASS_BODY使用的,因为GENERATED_BODY会自动生成默认构造函数。一般在自己需要自定义这个函数的时候使用。(但其实用GENERATED_BODY也行)
|
||||
|
||||
当前已经弃用:
|
||||
|
||||
```cpp
|
||||
CLASS_CustomConstructor UE_DEPRECATED(5.1, "CLASS_CustomConstructor should no longer be used. It is no longer being set by engine code.") = 0x00008000u,
|
||||
```
|
@@ -0,0 +1,56 @@
|
||||
# CustomFieldNotify
|
||||
|
||||
- **功能描述:** 阻止UHT为该类生成FieldNotify的相关代码。
|
||||
|
||||
- **引擎模块:** UHT
|
||||
- **元数据类型:** bool
|
||||
- **作用机制:** 在ClassFlags中增加[HasCustomFieldNotify](../../../Flags/EClassFlags/HasCustomFieldNotify.md)
|
||||
- **常用程度:** 0
|
||||
|
||||
阻止UHT为该类生成FieldNotify的相关代码。
|
||||
|
||||
在源码里只在UWidget上使用,例如该类里面的bIsEnabled是FieldNotify,正常来说UHT要为其生成代码。但如果该类想自己手动书写这些UHT代码,则可以加上CustomFieldNotify来阻止UHT生成。UWidget的cpp里因为要用别的方式UE_FIELD_NOTIFICATION_IMPLEMENT_CLASS_DESCRIPTOR,因此要拒绝UHT生成。
|
||||
|
||||
如果自己的类也要自己UE_FIELD_NOTIFICATION_IMPLEMENT_CLASS_DESCRIPTOR,则可以用上CustomFieldNotify。
|
||||
|
||||
## 源码例子:
|
||||
|
||||
```cpp
|
||||
//E:\P4V\Engine\Source\Runtime\UMG\Public\FieldNotification\FieldNotificationDeclaration.h
|
||||
UCLASS(Abstract, BlueprintType, Blueprintable, CustomFieldNotify)
|
||||
class UMG_API UWidget : public UVisual, public INotifyFieldValueChanged
|
||||
{
|
||||
GENERATED_UCLASS_BODY()
|
||||
public:
|
||||
UE_FIELD_NOTIFICATION_DECLARE_CLASS_DESCRIPTOR_BASE_BEGIN(UMG_API)
|
||||
UE_FIELD_NOTIFICATION_DECLARE_FIELD(ToolTipText)
|
||||
UE_FIELD_NOTIFICATION_DECLARE_FIELD(Visibility)
|
||||
UE_FIELD_NOTIFICATION_DECLARE_FIELD(bIsEnabled)
|
||||
UE_FIELD_NOTIFICATION_DECLARE_ENUM_FIELD_BEGIN(ToolTipText)
|
||||
UE_FIELD_NOTIFICATION_DECLARE_ENUM_FIELD(Visibility)
|
||||
UE_FIELD_NOTIFICATION_DECLARE_ENUM_FIELD(bIsEnabled)
|
||||
UE_FIELD_NOTIFICATION_DECLARE_ENUM_FIELD_END()
|
||||
UE_FIELD_NOTIFICATION_DECLARE_CLASS_DESCRIPTOR_BASE_END();
|
||||
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, FieldNotify, Getter="GetIsEnabled", Setter="SetIsEnabled", BlueprintGetter="GetIsEnabled", BlueprintSetter="SetIsEnabled", Category="Behavior")
|
||||
uint8 bIsEnabled:1;
|
||||
|
||||
//cpp
|
||||
UE_FIELD_NOTIFICATION_IMPLEMENT_CLASS_DESCRIPTOR_ThreeFields(UWidget, ToolTipText, Visibility, bIsEnabled);
|
||||
|
||||
```
|
||||
|
||||
## 原理:
|
||||
|
||||
在判断条件上可见HasCustomFieldNotify的判断。
|
||||
|
||||
```cpp
|
||||
protected static bool NeedFieldNotifyCodeGen(UhtClass classObj)
|
||||
{
|
||||
return
|
||||
!classObj.ClassExportFlags.HasAnyFlags(UhtClassExportFlags.HasCustomFieldNotify) &&
|
||||
classObj.ClassExportFlags.HasAnyFlags(UhtClassExportFlags.HasFieldNotify);
|
||||
}
|
||||
|
||||
```
|
@@ -0,0 +1,8 @@
|
||||
# CustomThunkTemplates
|
||||
|
||||
- **功能描述:** Specifies the struct that contains the CustomThunk implementations
|
||||
|
||||
- **引擎模块:** UHT
|
||||
- **元数据类型:** bool
|
||||
|
||||
在源码里找不到引用的地方
|
@@ -0,0 +1,34 @@
|
||||
# Interface
|
||||
|
||||
- **功能描述:** 标识这个Class是个Interface。
|
||||
- **引擎模块:** UHT
|
||||
- **元数据类型:** bool
|
||||
- **作用机制:** 在ClassFlags中添加[CLASS_Interface](../../../Flags/EClassFlags/CLASS_Interface.md)
|
||||
- **常用程度:** 0
|
||||
|
||||
标识这个Class是个Interface。
|
||||
|
||||
只用在NoExportTypes.h中,我们自己的UInterface不需要手动设置。
|
||||
|
||||
是UHT在为UInterface生成的时候,设置在.generated.h里的。
|
||||
|
||||
## 源码例子:
|
||||
|
||||
```cpp
|
||||
UCLASS(abstract, noexport, intrinsic, interface, Config = Engine)
|
||||
class UInterface : public UObject
|
||||
{}
|
||||
```
|
||||
|
||||
## 原理:
|
||||
|
||||
```cpp
|
||||
bool FKismetEditorUtilities::IsClassABlueprintInterface(const UClass* Class)
|
||||
{
|
||||
if (Class->HasAnyClassFlags(CLASS_Interface) && !Class->HasAnyClassFlags(CLASS_NewerVersionExists))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
```
|
@@ -0,0 +1,36 @@
|
||||
# Intrinsic
|
||||
|
||||
- **功能描述:** 指定UHT完全不为此类生成代码,需要自己手写。
|
||||
|
||||
- **引擎模块:** UHT
|
||||
- **元数据类型:** bool
|
||||
- **作用机制:** 在ClassFlags中增加[CLASS_Intrinsic](../../../Flags/EClassFlags/CLASS_Intrinsic.md)
|
||||
- **常用程度:** 0
|
||||
|
||||
指定UHT完全不为此类生成代码,需要自己手写。
|
||||
|
||||
只在C++直接设定,一般新类不设定这个,标记这个的都是UE4内部原生的那些类,相当于已经在源码中手写了元数据代码。
|
||||
|
||||
noexport至少还会解析生成元数据,只是缺少注册。因此instric类的所有元数据flags要自己手动标记。但是intrinsic完全不生成代码。其generated.h和.gen.cpp里面都是空的。noexporttyps.h里的目前采用intrinsic的类只有UCLASS(noexport, Intrinsic)class UModel{},这还是被cpp不编译的。
|
||||
|
||||
```cpp
|
||||
//UCLASS(Intrinsic)
|
||||
//class INSIDER_API UMyClass_Intrinsic :public UObject //syntax error: missing ';' before '<class-head>'
|
||||
//{
|
||||
// GENERATED_BODY()
|
||||
//
|
||||
//};
|
||||
|
||||
//.h
|
||||
class INSIDER_API UMyClass_Intrinsic :public UObject
|
||||
{
|
||||
DECLARE_CLASS_INTRINSIC(UMyClass_Intrinsic, UObject, CLASS_MatchedSerializers, TEXT("/Script/Insider"))
|
||||
};
|
||||
//.cpp
|
||||
IMPLEMENT_INTRINSIC_CLASS(UMyClass_Intrinsic, INSIDER_API, UObject, INSIDER_API, "/Script/Insider", {})
|
||||
|
||||
class COREUOBJECT_API UInterface : public UObject
|
||||
{
|
||||
DECLARE_CLASS_INTRINSIC(UInterface, UObject, CLASS_Interface | CLASS_Abstract, TEXT("/Script/CoreUObject"))
|
||||
};
|
||||
```
|
@@ -0,0 +1,132 @@
|
||||
# MinimalAPI
|
||||
|
||||
- **功能描述:** 不dll导出该类的函数,只导出类型信息当作变量。
|
||||
|
||||
- **引擎模块:** DllExport
|
||||
- **元数据类型:** bool
|
||||
- **作用机制:** 在ClassFlags增加[CLASS_MinimalAPI](../../../../Flags/EClassFlags/CLASS_MinimalAPI.md)
|
||||
- **常用程度:** ★★★
|
||||
|
||||
不dll导出该类的函数,只导出类型信息当作变量。
|
||||
|
||||
- 其他引用的模块可以利用指针来做转换,但是不能调用上面的函数。但是蓝图里依然可以访问。
|
||||
- 好处是可以缩短编译信息和加快链接速度,因为没有了那么多dllexport函数。
|
||||
- 注意MinimalAPI不能和MODULENAME_API一起使用,因为MinimalAPI就是用来不导出的,而MODULENAME_API就是用来导出的。但是MinimalAPI的效果并不等价于不写MODULENAME_API的效果,因为MinimalAPI还会导出GetPrivateStaticClass用来允许NewObject。所以如果一个类完全不想让另一个模块知道,则不需要写任何导出。而如果想让另一个模块知道类型,但是完全不能调用函数,则可以用MinimalAPI来防止。
|
||||
- 游戏的模块推荐不导出。插件的模块外部的推荐导出,内部的基类可以考虑MinimalAPI,私有类则可以完全不导出。引擎里使用MinimalAPI还是非常多的,生成的效果是这些类可以作为变量使用,但不能继承和调用方法。
|
||||
- 一般是配合BlueprintType使用,这样就可以在蓝图中作为变量。
|
||||
- 可以正常在蓝图中调用函数和属性。因为蓝图调用是只需要反射信息就可以的,因为是自己模块把函数和属性的指针注册到系统里。
|
||||
|
||||
## 示例代码:
|
||||
|
||||
```cpp
|
||||
UCLASS()
|
||||
class UMyClass_NotMinimalAPI :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
int32 MyProperty;
|
||||
UFUNCTION(BlueprintCallable)
|
||||
void MyFunc();
|
||||
};
|
||||
|
||||
UCLASS(MinimalAPI)
|
||||
class UMyClass_MinimalAPI :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
int32 MyProperty;
|
||||
UFUNCTION(BlueprintCallable)
|
||||
void MyFunc();
|
||||
};
|
||||
|
||||
UCLASS(MinimalAPI, BlueprintType)
|
||||
class UMyClass_MinimalAPI_BlueprintType :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
int32 MyProperty;
|
||||
UFUNCTION(BlueprintCallable)
|
||||
void MyFunc() {}
|
||||
};
|
||||
|
||||
UCLASS(MinimalAPI)
|
||||
class UMyClass_MinimalAPI_BlueprintFunctionLibary :public UBlueprintFunctionLibrary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
|
||||
UFUNCTION(BlueprintCallable)
|
||||
static void MyFuncInMinimalAPI();
|
||||
|
||||
UFUNCTION(BlueprintCallable)
|
||||
static INSIDER_API void MyFuncInMinimalAPIWithAPI();
|
||||
};
|
||||
```
|
||||
|
||||
## 示例效果:
|
||||
|
||||
可以正常在蓝图中调用函数和属性。蓝图函数库中的方法也可以调用,说明UHT对MinimalAPI还是依然生成反射的调用信息的,蓝图调用是只需要反射信息就可以的,因为是自己模块把函数和属性的指针注册到系统里,因此并不需要dll导出。只不过在dll导出工具里查看dll导出的函数列表并没有该函数。
|
||||
|
||||

|
||||
|
||||
查看dll导出函数列表:
|
||||
|
||||
```cpp
|
||||
class UClass * __ptr64 __cdecl StaticClass<class UMyClass_MinimalAPI>(void)
|
||||
class UClass * __ptr64 __cdecl StaticClass<class UMyClass_MinimalAPI_BlueprintFunctionLibary>(void)
|
||||
class UClass * __ptr64 __cdecl StaticClass<class UMyClass_MinimalAPI_BlueprintType>(void)
|
||||
class UClass * __ptr64 __cdecl StaticClass<class UMyClass_NotMinimalAPI>(void)
|
||||
class UClass * __ptr64 __cdecl Z_Construct_UClass_UMyClass_MinimalAPI(void)
|
||||
class UClass * __ptr64 __cdecl Z_Construct_UClass_UMyClass_MinimalAPI_BlueprintFunctionLibary(void)
|
||||
class UClass * __ptr64 __cdecl Z_Construct_UClass_UMyClass_MinimalAPI_BlueprintFunctionLibary_NoRegister(void)
|
||||
class UClass * __ptr64 __cdecl Z_Construct_UClass_UMyClass_MinimalAPI_BlueprintType(void)
|
||||
class UClass * __ptr64 __cdecl Z_Construct_UClass_UMyClass_MinimalAPI_BlueprintType_NoRegister(void)
|
||||
class UClass * __ptr64 __cdecl Z_Construct_UClass_UMyClass_MinimalAPI_NoRegister(void)
|
||||
class UClass * __ptr64 __cdecl Z_Construct_UClass_UMyClass_NotMinimalAPI(void)
|
||||
class UClass * __ptr64 __cdecl Z_Construct_UClass_UMyClass_NotMinimalAPI_NoRegister(void)
|
||||
private: static class UClass * __ptr64 __cdecl UMyClass_MinimalAPI::GetPrivateStaticClass(void)
|
||||
private: static class UClass * __ptr64 __cdecl UMyClass_MinimalAPI_BlueprintFunctionLibary::GetPrivateStaticClass(void)
|
||||
private: static class UClass * __ptr64 __cdecl UMyClass_MinimalAPI_BlueprintType::GetPrivateStaticClass(void)
|
||||
public: __cdecl UMyClass_MinimalAPI::UMyClass_MinimalAPI(class FObjectInitializer const & __ptr64) __ptr64
|
||||
public: __cdecl UMyClass_MinimalAPI::UMyClass_MinimalAPI(class FVTableHelper & __ptr64) __ptr64
|
||||
public: __cdecl UMyClass_MinimalAPI_BlueprintFunctionLibary::UMyClass_MinimalAPI_BlueprintFunctionLibary(class FObjectInitializer const & __ptr64) __ptr64
|
||||
public: __cdecl UMyClass_MinimalAPI_BlueprintFunctionLibary::UMyClass_MinimalAPI_BlueprintFunctionLibary(class FVTableHelper & __ptr64) __ptr64
|
||||
public: __cdecl UMyClass_MinimalAPI_BlueprintType::UMyClass_MinimalAPI_BlueprintType(class FObjectInitializer const & __ptr64) __ptr64
|
||||
public: __cdecl UMyClass_MinimalAPI_BlueprintType::UMyClass_MinimalAPI_BlueprintType(class FVTableHelper & __ptr64) __ptr64
|
||||
public: static void __cdecl UMyClass_MinimalAPI_BlueprintFunctionLibary::MyFuncInMinimalAPIWithAPI(void)
|
||||
public: virtual __cdecl UMyClass_MinimalAPI::~UMyClass_MinimalAPI(void) __ptr64
|
||||
public: virtual __cdecl UMyClass_MinimalAPI_BlueprintFunctionLibary::~UMyClass_MinimalAPI_BlueprintFunctionLibary(void) __ptr64
|
||||
public: virtual __cdecl UMyClass_MinimalAPI_BlueprintType::~UMyClass_MinimalAPI_BlueprintType(void) __ptr64
|
||||
public: void __cdecl UMyClass_MinimalAPI::`default constructor closure'(void) __ptr64
|
||||
public: void __cdecl UMyClass_MinimalAPI_BlueprintFunctionLibary::`default constructor closure'(void) __ptr64
|
||||
public: void __cdecl UMyClass_MinimalAPI_BlueprintType::`default constructor closure'(void) __ptr64
|
||||
```
|
||||
|
||||

|
||||
|
||||
在跨模块调用的时候,因为没有dll导出,因此会触发链接错误。
|
||||
|
||||
```cpp
|
||||
UMyClass_MinimalAPI* a = NewObject<UMyClass_MinimalAPI>();
|
||||
|
||||
//第一种错误
|
||||
//error LNK2019: unresolved external symbol "public: void __cdecl UMyClass_MinimalAPI::MyFunc(void)" (?MyFunc@UMyClass_MinimalAPI@@QEAAXXZ) referenced in function "public: void __cdecl UMyClass_UseMinimalAPI::TestFunc(void)" (?TestFunc@UMyClass_UseMinimalAPI@@QEAAXXZ)
|
||||
//a->MyFunc();
|
||||
|
||||
a->MyProperty++;
|
||||
|
||||
//第二种错误
|
||||
//error LNK2019: unresolved external symbol "private: static class UClass * __cdecl UMyClass_NotMinimalAPI::GetPrivateStaticClass(void)" (?GetPrivateStaticClass@UMyClass_NotMinimalAPI@@CAPEAVUClass@@XZ)
|
||||
//referenced in function "class UMyClass_NotMinimalAPI * __cdecl NewObject<class UMyClass_NotMinimalAPI>(class UObject *)" (??$NewObject@VUMyClass_NotMinimalAPI@@@@YAPEAVUMyClass_NotMinimalAPI@@PEAVUObject@@@Z)
|
||||
auto* a = NewObject<UMyClass_NotMinimalAPI>();
|
||||
|
||||
//第三种错误
|
||||
//error LNK2019: unresolved external symbol "public: static void __cdecl UMyClass_MinimalAPI_BlueprintFunctionLibary::MyFuncInMinimalAPI(void)" (?MyFuncInMinimalAPI@UMyClass_MinimalAPI_BlueprintFunctionLibary@@SAXXZ)
|
||||
//referenced in function "public: void __cdecl UMyClass_UseMinimalAPI::TestFunc(void)" (?TestFunc@UMyClass_UseMinimalAPI@@QEAAXXZ)
|
||||
UMyClass_MinimalAPI_BlueprintFunctionLibary::MyFuncInMinimalAPI();
|
||||
|
||||
UMyClass_MinimalAPI_BlueprintFunctionLibary::MyFuncInMinimalAPIWithAPI();
|
||||
```
|
After Width: | Height: | Size: 77 KiB |
After Width: | Height: | Size: 66 KiB |
@@ -0,0 +1,32 @@
|
||||
# NoExport
|
||||
|
||||
- **功能描述:** 指定UHT不要用来自动生成注册的代码,而只是进行词法分析提取元数据。
|
||||
|
||||
- **引擎模块:** UHT
|
||||
- **元数据类型:** bool
|
||||
- **作用机制:** 在ClassFlags中增加EClassFlags: [CLASS_NoExport](../../../Flags/EClassFlags/CLASS_NoExport.md)
|
||||
- **常用程度:** 0
|
||||
|
||||
指定UHT不要用来自动生成注册的代码,而只是进行词法分析提取元数据。
|
||||
|
||||
引擎的NoExportTypes.h里大量都是这种类型,专门提供给UHT来提取信息的。一般会用#if !CPP //noexport class来包裹,来避免编译。同时在另一个地方会定义这个类。因为StaticRegisterNatives##TClass没有生成,所以GetPrivateStaticClass不能调用成功,所以不能NewObject。一般noexport和Intrinsic都是配合使用的。因为DECLARE_CLASS_INTRINSIC内部会声明static void StaticRegisterNatives##TClass() {} 来允许成功调用。
|
||||
|
||||
引擎里的结构倒是经常用noexport来阻止生成UHT注册。因为结构其实不需要调用GetPrivateStaticClass来创建元数据。只要有Z_Construct_UScriptStruct_XXX来生成构造相应的UScriptStruct对象就行。
|
||||
|
||||
## 测试代码:
|
||||
|
||||
```cpp
|
||||
UCLASS(noexport)
|
||||
class INSIDER_API UMyClass_NoExport :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
};
|
||||
```
|
||||
|
||||
## 测试结果:
|
||||
|
||||
```cpp
|
||||
编译的时候生成错误:
|
||||
error LNK2019: unresolved external symbol "private: static void __cdecl UMyClass_NoExport::StaticRegisterNativesUMyClass_NoExport(void)" (?StaticRegisterNativesUMyClass_NoExport@UMyClass_NoExport@@CAXXZ) referenced in function "private: static class UClass * __cdecl UMyClass_NoExport::GetPrivateStaticClass(void)" (?GetPrivateStaticClass@UMyClass_NoExport@@CAPEAVUClass@@XZ)
|
||||
```
|
@@ -0,0 +1,37 @@
|
||||
# UCLASS()
|
||||
|
||||
- **功能描述:** 留空的默认行为是不能在蓝图中被继承,不能在蓝图中定义变量,但拥有反射的功能。
|
||||
|
||||
- **引擎模块:** UHT
|
||||
- **元数据类型:** bool
|
||||
- **作用机制:** 在ClassFlags中增加[CLASS_MatchedSerializers](../../../Flags/EClassFlags/CLASS_MatchedSerializers.md), [CLASS_Native](../../../Flags/EClassFlags/CLASS_Native.md), [CLASS_RequiredAPI](../../../Flags/EClassFlags/CLASS_RequiredAPI.md), [CLASS_TokenStreamAssembled](../../../Flags/EClassFlags/CLASS_TokenStreamAssembled.md), [CLASS_Intrinsic](../../../Flags/EClassFlags/CLASS_Intrinsic.md), [CLASS_Constructed](../../../Flags/EClassFlags/CLASS_Constructed.md)
|
||||
- **关联项:** [不写UCLASS()](不写UCLASS().md)
|
||||
- **常用程度:★★★★★**
|
||||
|
||||
不能在蓝图中被继承,不能在蓝图中定义变量。
|
||||
|
||||
但依然都可以通过蓝图ConstructObject创建出来。对于想要拥有反射功能,但是并不想在蓝图中被使用会挺适合。
|
||||
|
||||
## 示例代码:
|
||||
|
||||
```cpp
|
||||
/*
|
||||
[MyClass_Default Class->Struct->Field->Object /Script/Insider.MyClass_Default] [IncludePath = Class/MyClass_Default.h, ModuleRelativePath = Class/MyClass_Default.h]
|
||||
ObjectFlags: RF_Public | RF_Standalone | RF_Transient
|
||||
Outer: Package /Script/Insider
|
||||
ClassFlags: CLASS_MatchedSerializers | CLASS_Native | CLASS_RequiredAPI | CLASS_TokenStreamAssembled | CLASS_Intrinsic | CLASS_Constructed
|
||||
Size: 48
|
||||
{
|
||||
public: void ExecuteUbergraph(int32 EntryPoint);
|
||||
};
|
||||
*/
|
||||
|
||||
UCLASS()
|
||||
class INSIDER_API UMyClass_Default :public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
};
|
||||
```
|
||||
|
||||
默认的拥有这些标志:CLASS_MatchedSerializers | CLASS_Native | CLASS_RequiredAPI | CLASS_TokenStreamAssembled | CLASS_Intrinsic | CLASS_Constructed
|
@@ -0,0 +1,21 @@
|
||||
# 不写UCLASS()
|
||||
|
||||
- **功能描述:** 只是作为一个普通的C++对象,没有反射功能。
|
||||
|
||||
- **引擎模块:** UHT
|
||||
- **元数据类型:** bool
|
||||
- **关联项:** [UCLASS()](UCLASS().md)
|
||||
- **常用程度:** ★
|
||||
|
||||
只是作为一个普通的C++对象,没有反射功能。
|
||||
|
||||
一般情况继承自UObject的最少也会有一个UCLASS(),这样才有反射功能。但是注意,如果调用UMyClass_NoUCLASS::StaticClass()会返回基类UObject的Class,因为子类没有覆盖。因此也可以说本类是没有生成自己的UClass元数据对象。
|
||||
|
||||
```cpp
|
||||
class INSIDER_API UMyClass_NoUCLASS :public UObject
|
||||
{
|
||||
|
||||
};
|
||||
```
|
||||
|
||||
UObject的Class默认的标记是:CLASS_Abstract | CLASS_MatchedSerializers | CLASS_Native | CLASS_TokenStreamAssembled | CLASS_Intrinsic | CLASS_Constructed。因此不能被NewObject生成对象。在手动去掉CLASS_Abstract后可以正常new,但是对象的名称依然是Object,显然这是因为使用的就是Object的Class。
|