This commit is contained in:
2025-08-02 12:09:34 +08:00
commit e70b01cdca
2785 changed files with 575579 additions and 0 deletions

View File

@@ -0,0 +1,166 @@
# AssetBundles
- **功能描述:** 标明该属性其引用的资产属于哪一些AssetBundles。
- **使用位置:** UPROPERTY
- **引擎模块:** Object Property
- **元数据类型:** strings="abc"
- **限制类型:** UPrimaryDataAsset内部的FSoftObjectPtrFSoftObjectPath
- **关联项:** [IncludeAssetBundles](../IncludeAssetBundles/IncludeAssetBundles.md)
- **常用程度:** ★★★
用于UPrimaryDataAsset内部的 SoftObjectPtr 或 SoftObjectPath 属性标明其引用的资产属于哪一些AssetBundles。
要理解这个的作用,需要先理解一些基本概念:
- PrimaryAsset指的是在游戏中可以进行手动载入/释放的东西。包括关卡文件(.umap以及一些游戏相关的物件例如角色或者背包里的物品。顾名思义主要资产指的是游戏里的主要根部资产其引用树下有一大堆其他资产。另一方面我们往往会主动加载或释放这些主要资产比如加载关卡加载怪物角色加载掉落道具。但我们一般不太会直接去加载材质贴图声音这种资产因为它们绝大多数是被主要资产引用着。我们在加载主要资产的时候就会自带的加载这些次要资产了。
- SecondaryAsset指的是其他的那些Assets了例如贴图和声音等。这一类型的assets是根据PrimaryAsset来自动进行载入的。我们一般来说不太需要对次要资产进行管理其会被主要资产根据引用关系来自动的加载。
- AssetBundle可以叫做资产包其实就是一个Asset的列表我们对每个资产包起个名字来区分比如UIGame这样其实也是对一些资产进行标签分类。这里的Asset我们不区分是PrimaryAsset还是SecondaryAsset因为这是从用途上进行区分的而不是加载方式。AssetBundle的作用是当我们加载PrimaryAsset的时候这个PrimaryAsset本身可能引用着另外一些SecondaryAsset资产各自有不同的用途。我们就可以把这些SecondaryAsset资产划分到不同的AssetBundle里这样我们在加载PrimaryAsset的时候可以通过额外提供AssetBundleName来更加精细化的控制SecondaryAsset资产的加载。
- PrimaryAsset里的指定AssetBundle的Asset属性必须是软引用否则是硬引用的话无论如何也会被加载进来。软引用的Asset在默认时候需要我们手动的进行加载通过附加AssetBundle就可以在加载PrimaryAsset的时候附带的加载该软引用资产。
## 测试代码:
```cpp
UCLASS(BlueprintType)
class INSIDER_API UMyProperty_Asset_Item :public UPrimaryDataAsset
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadWrite,EditAnywhere)
FString Name;
UPROPERTY(BlueprintReadWrite, EditAnywhere,meta=(AssetBundles="UI,Game"))
TSoftObjectPtr<UTexture2D> Icon;
UPROPERTY(BlueprintReadWrite, EditAnywhere,meta=(AssetBundles="Game"))
TSoftObjectPtr<UStaticMesh> Mesh;
public:
virtual FPrimaryAssetId GetPrimaryAssetId() const override;
};
```
## 测试效果:
- 首先我们在BP里定义了UMyProperty_Asset_Item 的资产并相应的配置上了引用的对象。如图所示有一个图标是给UI和Game用的有一个Mesh是专门给Game用的。大概设想一下在一些界面我们只需要道具的图标就可以了。
- 然后在LoadPrimaryAsset的时候可以指定LoadBundles的名字从而只加载特定的Bundle。如下图所示。
- 当指定Bundle为UI的时候可以看见Mesh并没有加载进来。
- 当指定Bundle为Game的时候可以看见Icon和Mesh都加载了进来。
- 要注意在编辑器下测试时候如果之前已经加载了Mesh因为还常驻在编辑器内存里。因此即使是使用名字UI也仍然会发现Mesh可以被引用到。
![AssetBundles](AssetBundles.jpg)
## 原理:
首先UPrimaryDataAsset里有一个AssetBundleData保存着当前引用的AssetBundle的信息这个信息是在编辑器环境下PreSave的时候保存的会在UAssetManager::InitializeAssetBundlesFromMetadata里进行meta的分析和映射。之后在UAssetManager的LoadPrimaryAsset时内部调用 ChangeBundleStateForPrimaryAssets然后检查AssetBundle把其他额外要一并加载的Asset添加到PathsToLoad从而最终完成一并加载的这个逻辑。
```cpp
void UAssetManager::InitializeAssetBundlesFromMetadata_Recursive(const UStruct* Struct, const void* StructValue, FAssetBundleData& AssetBundle, FName DebugName, TSet<const void*>& AllVisitedStructValues) const
{
static FName AssetBundlesName = TEXT("AssetBundles");
static FName IncludeAssetBundlesName = TEXT("IncludeAssetBundles");
//根据当前对象的值搜索拥有AssetBundles的属性的值最后AddBundleAssetBundleName就是设置的值而FoundRef是引用的对象的资产路径
TSet<FName> BundleSet;
TArray<const FProperty*> PropertyChain;
It.GetPropertyChain(PropertyChain);
for (const FProperty* PropertyToSearch : PropertyChain)
{
if (PropertyToSearch->HasMetaData(AssetBundlesName))
{
TSet<FName> LocalBundleSet;
TArray<FString> BundleList;
const FString& BundleString = PropertyToSearch->GetMetaData(AssetBundlesName);
BundleString.ParseIntoArrayWS(BundleList, TEXT(","));
for (const FString& BundleNameString : BundleList)
{
LocalBundleSet.Add(FName(*BundleNameString));
}
// If Set is empty, initialize. Otherwise intersect
if (BundleSet.Num() == 0)
{
BundleSet = LocalBundleSet;
}
else
{
BundleSet = BundleSet.Intersect(LocalBundleSet);
}
}
}
for (const FName& BundleName : BundleSet)
{
AssetBundle.AddBundleAsset(BundleName, FoundRef.GetAssetPath());
}
}
#if WITH_EDITORONLY_DATA
void UPrimaryDataAsset::UpdateAssetBundleData()
{
// By default parse the metadata
if (UAssetManager::IsInitialized())
{
AssetBundleData.Reset();
UAssetManager::Get().InitializeAssetBundlesFromMetadata(this, AssetBundleData);
}
}
void UPrimaryDataAsset::PreSave(FObjectPreSaveContext ObjectSaveContext)
{
Super::PreSave(ObjectSaveContext);
UpdateAssetBundleData();
if (UAssetManager::IsInitialized())
{
// Bundles may have changed, refresh
UAssetManager::Get().RefreshAssetData(this);
}
}
#endif
void UPrimaryDataAsset::PostLoad()
{
Super::PostLoad();
#if WITH_EDITORONLY_DATA
FAssetBundleData OldData = AssetBundleData;
UpdateAssetBundleData();
if (UAssetManager::IsInitialized() && OldData != AssetBundleData)
{
// Bundles changed, refresh
UAssetManager::Get().RefreshAssetData(this);
}
#endif
}
//加载asset的时候如果有FAssetBundleEntry则一起加到PathsToLoad里
TSharedPtr<FStreamableHandle> UAssetManager::ChangeBundleStateForPrimaryAssets(const TArray<FPrimaryAssetId>& AssetsToChange, const TArray<FName>& AddBundles, const TArray<FName>& RemoveBundles, bool bRemoveAllBundles, FStreamableDelegate DelegateToCall, TAsyncLoadPriority Priority)
{
if (!AssetPath.IsNull())
{
// Dynamic types can have no base asset path
PathsToLoad.Add(AssetPath);
}
for (const FName& BundleName : NewBundleState)
{
FAssetBundleEntry Entry = GetAssetBundleEntry(PrimaryAssetId, BundleName);
if (Entry.IsValid())
{
for (const FTopLevelAssetPath & Path : Entry.AssetPaths)
{
PathsToLoad.AddUnique(FSoftObjectPath(Path));
}
}
else
{
UE_LOG(LogAssetManager, Verbose, TEXT("ChangeBundleStateForPrimaryAssets: No assets for bundle %s::%s"), *PrimaryAssetId.ToString(), *BundleName.ToString());
}
}
}
```
参考文档:[https://dev.epicgames.com/documentation/en-us/unreal-engine/asset-management-in-unreal-engine?application_version=5.4](https://dev.epicgames.com/documentation/en-us/unreal-engine/asset-management-in-unreal-engine?application_version=5.4)

View File

@@ -0,0 +1,29 @@
# CollapsableChildProperties
- **功能描述:** 在TextureGraph模块中新增加的meta。用于折叠一个结构的内部属性。
- **使用位置:** UPROPERTY
- **元数据类型:** bool
- **限制类型:** TextureGraph插件内使用
- **关联项:** [ShowInnerProperties](ShowInnerProperties/ShowInnerProperties.md)
- **常用程度:** 0
在TextureGraph模块中新增加的meta。用于折叠一个结构的内部属性。
## 源码:
```cpp
bool STG_GraphPinOutputSettings::CollapsibleChildProperties() const
{
FProperty* Property = GetPinProperty();
bool Collapsible = false;
// check if there is a display name defined for the property, we use that as the Pin Name
if (Property && Property->HasMetaData("CollapsableChildProperties"))
{
Collapsible = true;
}
return Collapsible;
}
UPROPERTY(EditAnywhere, Category = NoCategory, meta = (TGType = "TG_Input", CollapsableChildProperties,ShowOnlyInnerProperties, FullyExpand, NoResetToDefault, PinDisplayName = "Settings") )
FTG_OutputSettings OutputSettings;
```

View File

@@ -0,0 +1,80 @@
# DisplayThumbnail
- **功能描述:** 指定是否在该属性左侧显示一个缩略图。
- **使用位置:** UPROPERTY
- **引擎模块:** Object Property
- **元数据类型:** bool
- **限制类型:** UObject*
- **关联项:** [ThumbnailSize](../ThumbnailSize.md)
- **常用程度:** ★★★
指定是否在该属性左侧显示一个缩略图。
## 测试代码:
```cpp
UCLASS(Blueprintable, BlueprintType)
class INSIDER_API AMyActor_Thumbnail_Test :public AActor
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (DisplayThumbnail = "false"))
UObject* MyObject;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (DisplayThumbnail = "true"))
UObject* MyObject_DisplayThumbnail;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
AActor* MyActor;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (DisplayThumbnail = "true"))
AActor* MyActor_DisplayThumbnail;
};
```
## 测试效果:
可见MyObject_DisplayThumbnail的左侧显示出了所选择资产的缩略图而MyObject因为设置了false因此是没有的。如果不设置DisplayThumbnail =false则默认也是会显示缩略图的。
MyActor_DisplayThumbnail出现了缩略图的图标但是发现并没有显示出正确的说了图。AActor在默认情况下是不显示缩略图的。
![Untitled](Untitled.png)
## 原理:
判断是否要显示缩略图就在这个函数。
默认情况下非Actor类型才会显示。另外SPropertyEditorAsset是用在资产类型属性上的其实就是Object属性。
```cpp
bool SPropertyEditorAsset::ShouldDisplayThumbnail(const FArguments& InArgs, const UClass* InObjectClass) const
{
if (!InArgs._DisplayThumbnail || !InArgs._ThumbnailPool.IsValid())
{
return false;
}
bool bShowThumbnail = InObjectClass == nullptr || !InObjectClass->IsChildOf(AActor::StaticClass());
// also check metadata for thumbnail & text display
const FProperty* PropertyToCheck = nullptr;
if (PropertyEditor.IsValid())
{
PropertyToCheck = PropertyEditor->GetProperty();
}
else if (PropertyHandle.IsValid())
{
PropertyToCheck = PropertyHandle->GetProperty();
}
if (PropertyToCheck != nullptr)
{
PropertyToCheck = GetActualMetadataProperty(PropertyToCheck);
return GetTagOrBoolMetadata(PropertyToCheck, TEXT("DisplayThumbnail"), bShowThumbnail);
}
return bShowThumbnail;
}
```

View File

@@ -0,0 +1,96 @@
# ExposeFunctionCategories
- **功能描述:** 指定该Object属性所属于的类里的某些目录下的函数可以直接在本类上暴露出来。
- **使用位置:** UPROPERTY
- **引擎模块:** Object Property
- **元数据类型:** strings="abc"
- **限制类型:** UObject*
- **常用程度:** ★★★
指定该Object属性所属于的类里的某些目录下的函数可以直接在本类上暴露出来。
一开始直接还挺难理解的其含义和作用的但这其实是一个便利性的功能而已。比如有类A里面定义一些函数然后类B里有个A的对象。这个时候如果想在B对象身上去调用A的函数就得手动先拖拉出B.ObjA然后再拖拉出其内部的函数。我们希望把当前B的应用上下文场景下可以把A里的某些函数直接比较方便的暴露到B里来调用。
其实就是引擎帮我们自动的拖拉出B.ObjA这一步操作而已。你如果想要调用A里的更多的函数也可以自己手动在B.ObjA身上拖拉右键出更多的函数。
源码里这种应用也比较多比较方便的例子是以下源码例子这样当前在ASkeletalMeshActor 身上就可以直接拖拉出USkeletalMeshComponent里ExposeFunctionCategories 所定义的目录的函数。
```cpp
UCLASS(ClassGroup=ISkeletalMeshes, Blueprintable, ComponentWrapperClass, ConversionRoot, meta=(ChildCanTick), MinimalAPI)
class ASkeletalMeshActor : public AActor
{
private:
UPROPERTY(Category = SkeletalMeshActor, VisibleAnywhere, BlueprintReadOnly, meta = (ExposeFunctionCategories = "Mesh,Components|SkeletalMesh,Animation,Physics", AllowPrivateAccess = "true"))
TObjectPtr<class USkeletalMeshComponent> SkeletalMeshComponent;
}
```
## 测试代码:
```cpp
UCLASS(BlueprintType)
class INSIDER_API UMyProperty_ExposeFunctionCategories :public UObject
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, Category = "FirstFunc")
void MyExposeFunc1() {}
UFUNCTION(BlueprintCallable, Category = "SecondFunc")
void MyExposeFunc2() {}
UFUNCTION(BlueprintCallable, Category = "ThirdFunc")
void MyExposeFunc3() {}
};
UCLASS(BlueprintType)
class INSIDER_API UMyProperty_ExposeFunctionCategories_Test :public UObject
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadOnly, meta = (ExposeFunctionCategories = "FirstFunc,ThirdFunc"))
UMyProperty_ExposeFunctionCategories* MyObject_Expose;
};
```
## 测试效果:
可以见到在UMyProperty_ExposeFunctionCategories_Test 类型的Object身上我直接输入MyExposeFunc就可以弹出“FirstFuncThirdFunc”这两个目录里的这两个函数而不会直接弹出MyExposeFunc2因为其没有直接被暴露出来。
而如果在MyObject_Expose这种内部对象上直接拖拉右键则可以见到所有内部定义的函数。注意这里虽然有两个条目的MyExposeFunc1但其实调用出来的是同一个函数实际并没有影响。
![Untitled](Untitled.png)
## 原理:
在蓝图右键菜单的构建过程中会判断某个操作是否要过滤掉。这里的IsUnexposedMemberAction就是判断这个函数是否应该被过滤掉。大致的逻辑是取得其函数对应的属性比如在UMyProperty_ExposeFunctionCategories_Test 这个Object身上递归到子对象其实有3个函数会进来参加测试。这3个函数MyExposeFunc 1 2 3各自有自己的Category但都对应MyObject_Expose这个Property因此其AllExposedCategories的值是我们定义的“FirstFunc,ThirdFunc”数组最终只有两个函数通过测试因此最后显示13两个函数。
```cpp
static bool BlueprintActionMenuUtilsImpl::IsUnexposedMemberAction(FBlueprintActionFilter const& Filter, FBlueprintActionInfo& BlueprintAction)
{
bool bIsFilteredOut = false;
if (UFunction const* Function = BlueprintAction.GetAssociatedFunction())
{
TArray<FString> AllExposedCategories;
for (FBindingObject Binding : BlueprintAction.GetBindings())
{
if (FProperty* Property = Binding.Get<FProperty>())
{
const FString& ExposedCategoryMetadata = Property->GetMetaData(FBlueprintMetadata::MD_ExposeFunctionCategories);
if (ExposedCategoryMetadata.IsEmpty())
{
continue;
}
TArray<FString> PropertyExposedCategories;
ExposedCategoryMetadata.ParseIntoArray(PropertyExposedCategories, TEXT(","), true);
AllExposedCategories.Append(PropertyExposedCategories);
}
}
const FString& FunctionCategory = Function->GetMetaData(FBlueprintMetadata::MD_FunctionCategory);
bIsFilteredOut = !AllExposedCategories.Contains(FunctionCategory);
}
return bIsFilteredOut;
}
```

View File

@@ -0,0 +1,19 @@
# FullyExpand
- **使用位置:** UPROPERTY
- **元数据类型:** bool
- **关联项:** [ShowInnerProperties](ShowInnerProperties/ShowInnerProperties.md)
但是没有发现该Meta被使用的原理代码。
在源码中搜索发现有多处应用,但实际上没有原理代码。
```cpp
/** The options that are available on the node. */
UPROPERTY(EditAnywhere, Instanced, Category = "Options", meta=(ShowInnerProperties, FullyExpand="true"))
TObjectPtr<UMovieGraphValueContainer> SelectOptions;
/** The currently selected option. */
UPROPERTY(EditAnywhere, Instanced, Category = "Options", meta=(ShowInnerProperties, FullyExpand="true"))
TObjectPtr<UMovieGraphValueContainer> SelectedOption;
```

View File

@@ -0,0 +1,96 @@
# HideAssetPicker
- **功能描述:** 隐藏Object类型引脚上的AssetPicker的选择列表
- **使用位置:** UFUNCTION
- **引擎模块:** Object Property
- **元数据类型:** strings="abc"
- **限制类型:** UObject*
- **常用程度:** ★★
隐藏Object类型引脚上的AssetPicker的选择列表。这在有时我们只是想要自己传递Object引用不希望用户选择到引擎里别的资产的时候会比较有用。因为Asset类型其实也是Object因此对于Object引用类型的参数叫做HideAssetPicker。
在源码里并没有找到有使用的地方,但是这个功能是可用的。
## 测试代码:
```cpp
UFUNCTION(BlueprintCallable)
static void MyFunc_NoHideAssetPicker(UObject* ObjectClass) {}
UFUNCTION(BlueprintCallable, meta = (HideAssetPicker = "ObjectClass"))
static void MyFunc_HideAssetPicker(UObject* ObjectClass) {}
```
## 蓝图效果:
默认的情况MyFunc_NoHideAssetPicker是可以弹出选择列表的。而MyFunc_HideAssetPicker则就隐藏了起来。
![Untitled](Untitled.png)
## 原理:
判断一个函数引脚是否允许打开AssetPicker的逻辑是
- 必须是个object 类型
- 如果是UActorComponent则不显示
- 如果是Actor类型那么得在关卡蓝图中且该Actor是placeable的才显示。
- 如果用HideAssetPicker显式指定了该参数则该参数也不显示。
```cpp
bool UEdGraphSchema_K2::ShouldShowAssetPickerForPin(UEdGraphPin* Pin) const
{
bool bShow = true;
if (Pin->PinType.PinCategory == PC_Object)
{
UClass* ObjectClass = Cast<UClass>(Pin->PinType.PinSubCategoryObject.Get());
if (ObjectClass)
{
// Don't show literal buttons for component type objects
bShow = !ObjectClass->IsChildOf(UActorComponent::StaticClass());
if (bShow && ObjectClass->IsChildOf(AActor::StaticClass()))
{
// Only show the picker for Actor classes if the class is placeable and we are in the level script
bShow = !ObjectClass->HasAllClassFlags(CLASS_NotPlaceable)
&& FBlueprintEditorUtils::IsLevelScriptBlueprint(FBlueprintEditorUtils::FindBlueprintForNode(Pin->GetOwningNode()));
}
if (bShow)
{
if (UK2Node_CallFunction* CallFunctionNode = Cast<UK2Node_CallFunction>(Pin->GetOwningNode()))
{
if (UFunction* FunctionRef = CallFunctionNode->GetTargetFunction())
{
const UEdGraphPin* WorldContextPin = CallFunctionNode->FindPin(FunctionRef->GetMetaData(FBlueprintMetadata::MD_WorldContext));
bShow = ( WorldContextPin != Pin );
// Check if we have explictly marked this pin as hiding the asset picker
const FString& HideAssetPickerMetaData = FunctionRef->GetMetaData(FBlueprintMetadata::MD_HideAssetPicker);
if(!HideAssetPickerMetaData.IsEmpty())
{
TArray<FString> PinNames;
HideAssetPickerMetaData.ParseIntoArray(PinNames, TEXT(","), true);
const FString PinName = Pin->GetName();
for(FString& ParamNameToHide : PinNames)
{
ParamNameToHide.TrimStartAndEndInline();
if(ParamNameToHide == PinName)
{
bShow = false;
break;
}
}
}
}
}
else if (Cast<UK2Node_CreateDelegate>( Pin->GetOwningNode()))
{
bShow = false;
}
}
}
}
return bShow;
}
```

View File

@@ -0,0 +1,109 @@
# IncludeAssetBundles
- **功能描述:** 用于UPrimaryDataAsset的子对象属性指定应该继续递归到该子对象里去探测AssetBundle数据。
- **使用位置:** UPROPERTY
- **引擎模块:** Object Property
- **元数据类型:** string="abc"
- **限制类型:** UPrimaryDataAsset内部的ObjectPtr属性
- **关联项:** [AssetBundles](../AssetBundles/AssetBundles.md)
- **常用程度:** ★★
用于UPrimaryDataAsset的子对象属性指定应该继续递归到该子对象里去探测AssetBundle数据。
这样这些指对象内部的 FSoftObjectPtr 或 FSoftObjectPath 属性其上面标明的AssetBundle的数据才会被解析添加到UPrimaryDataAsset的AssetBundleData里。
- 默认情况下InitializeAssetBundlesFromMetadata_Recursive只会分析到UPrimaryDataAsset的本身这一层级的属性比如下面的Icon和Mesh属性。
- 而如果再嵌套了一层就是UPrimaryDataAsset下面拥有只对象UMyProperty_Asset_ChildObject而UMyProperty_Asset_ChildObject 里面又包含FSoftObjectPath 希望它被属于AssetBundles 的一部分在加载UPrimaryDataAsset的时候同时一并加载。这个时候就需要告诉引擎需要继续分析这个子对象。
- 注意到UMyProperty_Asset_ChildObject我都是用TObjectPtr是个硬引用该对象在UMyProperty_Asset_Item 被加载的时候也会自然被加载进来。因此无论如何UMyProperty_Asset_ChildObject 都会被加载进来。但是UMyProperty_Asset_ChildObject 内部的ChildIcon是用TSoftObjectPtr是软引用因此必须依赖AssetBundle的机制才会被加载。
## 测试代码:
```cpp
UCLASS(BlueprintType)
class INSIDER_API UMyProperty_Asset_ChildObject :public UDataAsset
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadWrite, EditAnywhere, meta = (AssetBundles = "Client"))
TSoftObjectPtr<UTexture2D> ChildIcon;
};
UCLASS(BlueprintType)
class INSIDER_API UMyProperty_Asset_Item :public UPrimaryDataAsset
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadWrite, EditAnywhere)
FString Name;
UPROPERTY(BlueprintReadWrite, EditAnywhere, meta = (AssetBundles = "UI,Game"))
TSoftObjectPtr<UTexture2D> Icon;
UPROPERTY(BlueprintReadWrite, EditAnywhere, meta = (AssetBundles = "Game"))
TSoftObjectPtr<UStaticMesh> Mesh;
public:
UPROPERTY(BlueprintReadWrite, EditAnywhere)
TObjectPtr<UMyProperty_Asset_ChildObject> MyChildObject_NotIncludeAssetBundles;
UPROPERTY(BlueprintReadWrite, EditAnywhere, meta = (IncludeAssetBundles))
TObjectPtr<UMyProperty_Asset_ChildObject> MyChildObject_IncludeAssetBundles;
public:
virtual FPrimaryAssetId GetPrimaryAssetId() const override;
};
```
## 测试效果:
配置的数据图的下部分分别配置了两张图片。但在LoadPrimaryAsset后只有MyChildObject_IncludeAssetBundles内部的ChildIcon才被加载进来。
![IncludeAssetBundles](IncludeAssetBundles.jpg)
如果分析UMyProperty_Asset_Item 的AssetBunbleData数据会发现其Client只包含第二张Stone图片的路径。这是因为只有第二张图片才被分析到并包含进来。
```cpp
{
BundleName = "Client";
BundleAssets =
{
{
AssetPath =
{
PackageName = "/Game/Asset/Image/T_Shop_Stone";
AssetName = "T_Shop_Stone";
};
SubPathString = "";
};
},
AssetPaths =
{
{
PackageName = "/Game/Asset/Image/T_Shop_Stone";
AssetName = "T_Shop_Stone";
};
},
};
```
## 原理:
UPrimaryDataAsset下的属性如果是个Object属性只当有IncludeAssetBundles的时候才会进一步递归向下探测。
```cpp
void UAssetManager::InitializeAssetBundlesFromMetadata_Recursive(const UStruct* Struct, const void* StructValue, FAssetBundleData& AssetBundle, FName DebugName, TSet<const void*>& AllVisitedStructValues) const
{
static FName AssetBundlesName = TEXT("AssetBundles");
static FName IncludeAssetBundlesName = TEXT("IncludeAssetBundles");
//根据当前对象的值搜索拥有AssetBundles的属性的值最后AddBundleAssetBundleName就是设置的值而FoundRef是引用的对象的资产路径
else if (const FObjectProperty* ObjectProperty = CastField<FObjectProperty>(Property))
{
if (ObjectProperty->PropertyFlags & CPF_InstancedReference || ObjectProperty->GetOwnerProperty()->HasMetaData(IncludeAssetBundlesName))
{
const UObject* Object = ObjectProperty->GetObjectPropertyValue(PropertyValue);
if (Object != nullptr)
{
InitializeAssetBundlesFromMetadata_Recursive(Object->GetClass(), Object, AssetBundle, Object->GetFName(), AllVisitedStructValues);
}
}
}
}
```

View File

@@ -0,0 +1,156 @@
# LoadBehavior
- **功能描述:** 用在UCLASS上标记这个类的加载行为使得相应的TObjectPtr属性支持延迟加载。可选的加载行为默认为Eager可改为LazyOnDemand。
- **使用位置:** UCLASS
- **引擎模块:** Object Property
- **元数据类型:** string="abc"
- **限制类型:** TObjectPtr
- **常用程度:** ★
用在UCLASS上标记这个类的加载行为使得相应的TObjectPtr属性支持延迟加载。可选的加载行为默认为Eager可改为LazyOnDemand。
- 默认Eager的逻辑和我们常见的资源硬引用的逻辑相同就是如果A硬引用了B在加载A的时候就会递归把B也加载进来。
- 改为LazyOnDemand的逻辑是只有在这个资源真正被需要触发Get的时候才会去加载该资源。这也是种硬引用但是是延迟加载的。同样A硬引用了B在加载A的时候不会直接立马就加载B而是先记录下来这个引用关系信息B的ObjectPath。在A里真正需要访问B的时候这个时候因为已经事先记录知道了B在哪里因此就可以在这个时候再去把B加载进来。如果加载的够快对用户是无感的。LazyOnDemand只在编辑器下生效这么做的好处是可以尽快的打开编辑器而不是等待所有资源都加载进来。因为其实并不是所有资源都要第一时间加载解析进来访问。
- 同FSoftObjectPtr的区别是后者是软引用需要用户手动的自己判断时机加载。而LazyOnDemand是自动的延迟加载用户是无感的不需要做额外的操作。
- LoadBehavior只作用于TObjectPtr属性UObject*属性总是直接都加载的。因为只有TObjectPtr里实现了UObject*的引用路径信息编码,才可以支持延迟加载。
- LoadBehavior也只支持在编辑器环境。因为在RuntimeTObjectPtr会退化成UObject*,也就全部都是直接加载了。
- LoadBehavior一般标记在资产类型的类上。源码里标记的类有DataAsset,DataTable,CurveTable,SoundCue,SoundWave,DialogueWave,AnimMontage。因此假如你自己自定义了资产类也包含了许多数据就可以用LazyOnDemand来优化在编辑器下的加载速度。
- 要测试LoadBehavior要打开引擎的的LazyLoadImports功能默认情况下是关闭的。打开的方式可在DefaultEngine.ini下增加Core.System.Experimental节下LazyLoadImports=True的设置。源码可参考IsImportLazyLoadEnabled这个方法。
- 在测试的时候要小心如果是双击打开DataAsset资产因为在属性细节面板里要展示属性的值在属性上会调用GetObjectPropertyValue_InContainer因此会触发ObjectHandleResolve导致TObjectPtr的Resolve。
## 测试代码:
如下专门定义了UMyDataAsset_Eager 和UMyDataAsset_LazyOnDemand 两种DataAsset并标注了不同的LoadBehavior 以做对比。
```cpp
//(BlueprintType = true, IncludePath = Property/MyProperty_Asset.h, IsBlueprintBase = true, LoadBehavior = Eager, ModuleRelativePath = Property/MyProperty_Asset.h)
UCLASS(Blueprintable, Blueprintable, meta = (LoadBehavior = "Eager"))
class INSIDER_API UMyDataAsset_Eager :public UDataAsset
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadWrite, EditAnywhere)
float Score;
};
//(BlueprintType = true, IncludePath = Property/MyProperty_Asset.h, IsBlueprintBase = true, LoadBehavior = LazyOnDemand, ModuleRelativePath = Property/MyProperty_Asset.h)
UCLASS(Blueprintable, Blueprintable, meta = (LoadBehavior = "LazyOnDemand"))
class INSIDER_API UMyDataAsset_LazyOnDemand :public UDataAsset
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadWrite, EditAnywhere)
float Score;
};
UCLASS(BlueprintType)
class INSIDER_API UMyClass_LoadBehaviorTest :public UDataAsset
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadWrite, EditAnywhere)
TObjectPtr<UMyDataAsset_LazyOnDemand> MyLazyOnDemand_AssetPtr;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
TObjectPtr<UMyDataAsset_Eager> MyEager_AssetPtr;
public:
UPROPERTY(BlueprintReadWrite, EditAnywhere,meta = (LoadBehavior = "Eager"))
TObjectPtr<UMyDataAsset_LazyOnDemand> MyLazyOnDemand_AssetPtr_EagerOnProperty;
UPROPERTY(BlueprintReadWrite, EditAnywhere,meta = (LoadBehavior = "LazyOnDemand"))
TObjectPtr<UMyDataAsset_Eager> MyEager_AssetPtr_LazyOnDemandOnProperty;
public:
UPROPERTY(BlueprintReadWrite, EditAnywhere)
UMyDataAsset_LazyOnDemand* MyLazyOnDemand_Asset;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
UMyDataAsset_Eager* MyEager_Asset;
public:
UFUNCTION(BlueprintCallable)
static void LoadBehaviorTest();
};
void UMyClass_LoadBehaviorTest::LoadBehaviorTest()
{
UPackage* pk = LoadPackage(nullptr, TEXT("/Game/Class/Behavior/LoadBehavior/DA_LoadBehaviorTest"), 0);
UMyClass_LoadBehaviorTest* obj = LoadObject<UMyClass_LoadBehaviorTest>(pk, TEXT("DA_LoadBehaviorTest"));
}
//开启功能
DefaultEngine.ini
[Core.System.Experimental]
LazyLoadImports=True
```
## 测试结果:
在编辑器运行起来之后手动调用LoadBehaviorTest来加载这个UMyClass_LoadBehaviorTest 的DataAsset。查看不同类型属性的对象值。可以发现
- 其中MyLazyOnDemand_AssetPtr和MyLazyOnDemand_AssetPtr_EagerOnProperty的ObjectPtr的值是还没有Resolved的其他的都可以查看到直接对象的值。
- 可以得出的结论有只有在UCLASS上标记LazyOnDemand才可以使得延迟加载生效。在属性上标记LoadBehavior 并不会起作用。直接UObject*的属性统统都会直接加载。
![Untitled](Untitled.png)
## 原理:
在LinkerLoadImportBehavior.cpp里可看见判断LoadBehavior的FindLoadBehavior方法因此发现其只作用在UCLASS上。
另外也可在TObjectPtr的Get函数里发现ResolveObjectHandle的调用。这是触发Resolve的地方。
也注意到UE_WITH_OBJECT_HANDLE_LATE_RESOLVE 的定义是WITH_EDITORONLY_DATA因此是在编辑器环境下生效。
```cpp
//D:\github\UnrealEngine\Engine\Source\Runtime\CoreUObject\Private\UObject\LinkerLoadImportBehavior.cpp
enum class EImportBehavior : uint8
{
Eager = 0,
// @TODO: OBJPTR: we want to permit lazy background loading in the future
//LazyBackground,
LazyOnDemand,
};
EImportBehavior FindLoadBehavior(const UClass& Class)
{
//Package class can't have meta data because of UHT
if (&Class == UPackage::StaticClass())
{
return EImportBehavior::LazyOnDemand;
}
static const FName Name_LoadBehavior(TEXT("LoadBehavior"));
if (const FString* LoadBehaviorMeta = Class.FindMetaData(Name_LoadBehavior))
{
if (*LoadBehaviorMeta == TEXT("LazyOnDemand"))
{
return EImportBehavior::LazyOnDemand;
}
return EImportBehavior::Eager;
}
else
{
//look in super class to see if it has lazy load on
const UClass* Super = Class.GetSuperClass();
if (Super != nullptr)
{
return FindLoadBehavior(*Super);
}
return EImportBehavior::Eager;
}
}
#define UE_WITH_OBJECT_HANDLE_LATE_RESOLVE WITH_EDITORONLY_DATA
inline UObject* ResolveObjectHandle(FObjectHandle& Handle)
{
#if UE_WITH_OBJECT_HANDLE_LATE_RESOLVE || UE_WITH_OBJECT_HANDLE_TRACKING
UObject* ResolvedObject = ResolveObjectHandleNoRead(Handle);
UE::CoreUObject::Private::OnHandleRead(ResolvedObject);
return ResolvedObject;
#else
return ReadObjectHandlePointerNoCheck(Handle);
#endif
}
```

Binary file not shown.

View File

@@ -0,0 +1,20 @@
# MustBeLevelActor
- **使用位置:** UPROPERTY
- **引擎模块:** Object Property
- **元数据类型:** bool
意思是这个必须是场景里的Actor而不是LevelScriptActor的意思。
触发时机在用箭头选择的当前选中actor的时候。
## 源码中遇见:
```cpp
if (FObjectPropertyBase* ObjectProperty = CastField<FObjectPropertyBase>(Property))
{
ObjectClass = ObjectProperty->PropertyClass;
bMustBeLevelActor = ObjectProperty->GetOwnerProperty()->GetBoolMetaData(TEXT("MustBeLevelActor"));
RequiredInterface = ObjectProperty->GetOwnerProperty()->GetClassMetaData(TEXT("MustImplement"));
}
```

View File

@@ -0,0 +1,246 @@
# ShowInnerProperties
- **功能描述:** 在属性细节面板中显示对象引用的内部属性
- **使用位置:** UPROPERTY
- **引擎模块:** Object Property
- **元数据类型:** bool
- **限制类型:** UObject*
- **关联项:** [ShowOnlyInnerProperties](../ShowOnlyInnerProperties/ShowOnlyInnerProperties.md), [FullyExpand](../FullyExpand.md), [CollapsableChildProperties](../CollapsableChildProperties.md)
- **常用程度:** ★★★★★
在属性细节面板中显示对象引用的内部属性。
默认情况下对象引用属性的内部属性在细节面板里是不会显示出来的只是孤零零的显示一个对象名字。但你如果想直接显示出其内部属性然后可以编辑的话就需要ShowInnerProperties这个meta的作用。
但ShowInnerProperties作用有两个限定条件一是这个属性得是UObject*,二是这个属性不是个容器。
同时也注意到Struct属性是默认就会显示内部属性的因此也不需要再设置ShowInnerProperties。
**和EditInineNew的区别是什么**
这种效果和在UCLASS上设置EditInineNew配合其对象引用属性上设置Instanced达成的效果很相似。区别是UCLASS上设置EditInineNew会使得一个类的对象属性引用可以在属性面板里创建对象 而UPROPERTY上的Instanced会使得这个属性自动的增加EditInline的meta因此也会产生显示内部属性的同样效果。因此结论上来说和ShowInnerProperties像的是本质是EditInline这个meta。但EditInline的效果多了一层是它支持对象容器而ShowInnerProperties只支持单个对象引用属性。
## 测试代码:
```cpp
USTRUCT(BlueprintType)
struct FMyPropertyInner
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite)
int32 StructInnerInt = 123;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FString StructInnerString;
};
UCLASS(BlueprintType)
class INSIDER_API UMyProperty_InnerSub :public UObject
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite)
int32 ObjectInnerInt = 123;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FString ObjectInnerString;
};
UCLASS(BlueprintType, EditInlineNew)
class INSIDER_API UMyProperty_InnerSub_EditInlineNew :public UObject
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite)
int32 ObjectInnerInt = 123;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FString ObjectInnerString;
};
UCLASS(Blueprintable, BlueprintType)
class INSIDER_API UMyProperty_Inner :public UDataAsset
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FMyPropertyInner InnerStruct;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (ShowInnerProperties))
FMyPropertyInner InnerStruct_ShowInnerProperties;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
UMyProperty_InnerSub* InnerObject;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (ShowInnerProperties))
UMyProperty_InnerSub* InnerObject_ShowInnerProperties;
//(Category = MyProperty_Inner, EditInline = , ModuleRelativePath = Property/MyProperty_Inner.h)
//CPF_Edit | CPF_BlueprintVisible | CPF_ZeroConstructor | CPF_NoDestructor | CPF_HasGetValueTypeHash | CPF_NativeAccessSpecifierPublic
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (EditInline))
UMyProperty_InnerSub* InnerObject_EditInline;
//(Category = MyProperty_Inner, EditInline = true, ModuleRelativePath = Property/MyProperty_Inner.h)
//CPF_Edit | CPF_BlueprintVisible | CPF_ExportObject | CPF_ZeroConstructor | CPF_InstancedReference | CPF_NoDestructor | CPF_PersistentInstance | CPF_HasGetValueTypeHash | CPF_NativeAccessSpecifierPublic
UPROPERTY(EditAnywhere, BlueprintReadWrite, Instanced)
UMyProperty_InnerSub* InnerObject_Instanced;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
UMyProperty_InnerSub_EditInlineNew* InnerObject_EditInlineNewClass;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (EditInline))
UMyProperty_InnerSub_EditInlineNew* InnerObject_EditInlineNewClass_EditInline;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Instanced)
UMyProperty_InnerSub_EditInlineNew* InnerObject_EditInlineNewClass_Instanced;
public:
UFUNCTION(CallInEditor)
void ClearInnerObject();
UFUNCTION(CallInEditor)
void InitInnerObject();
};
void UMyProperty_Inner::ClearInnerObject()
{
InnerObject = nullptr;
InnerObject_ShowInnerProperties = nullptr;
InnerObject_EditInline = nullptr;
InnerObject_Instanced = nullptr;
InnerObject_EditInlineNewClass = nullptr;
InnerObject_EditInlineNewClass_EditInline = nullptr;
InnerObject_EditInlineNewClass_Instanced = nullptr;
Modify();
FPropertyEditorModule& PropertyEditorModule = FModuleManager::GetModuleChecked<FPropertyEditorModule>("PropertyEditor");
PropertyEditorModule.NotifyCustomizationModuleChanged();
}
void UMyProperty_Inner::InitInnerObject()
{
InnerObject = NewObject<UMyProperty_InnerSub>(this);
InnerObject_ShowInnerProperties = NewObject<UMyProperty_InnerSub>(this);
InnerObject_EditInline = NewObject<UMyProperty_InnerSub>(this);
InnerObject_Instanced = NewObject<UMyProperty_InnerSub>(this);
InnerObject_EditInlineNewClass = NewObject<UMyProperty_InnerSub_EditInlineNew>(this);
InnerObject_EditInlineNewClass_EditInline = NewObject<UMyProperty_InnerSub_EditInlineNew>(this);
//InnerObject_EditInlineNewClass_Instanced = NewObject<UMyProperty_InnerSub_EditInlineNew>(this);
Modify();
FPropertyEditorModule& PropertyEditorModule = FModuleManager::GetModuleChecked<FPropertyEditorModule>("PropertyEditor");
PropertyEditorModule.NotifyCustomizationModuleChanged();
}
```
## 蓝图效果:
![Untitled](Untitled.png)
可以观察到:
- 结构的属性默认支持展开内部属性
- 带有ShowInnerProperties的UMyProperty_InnerSub* InnerObject_ShowInnerProperties;支持展开属性
- 带有EditInline和Instanced的UMyProperty_InnerSub* 也都支持展开内部属性也可以观察到他们的meta是一致的都带有EditInline=true
- 只有EditInlineNew的UCLASS的UMyProperty_InnerSub_EditInlineNew* InnerObject_EditInlineNewClass;其对象引用不支持展开属性说明在类上设置EditInlineNew并没有作用。
- 但是我们也观察到InnerObject_EditInlineNewClass_Instanced的设置里支持直接创建对象因为其类上有EditInlineNew。而InnerObject_Instanced上并不支持直接创建对象因为其类UMyProperty_InnerSub上并没有EditInlineNew因此不会出现在可选框里。
## 扩展例子:
在源码中搜索观察到UChildActorComponent::ChildActorTemplate上也会带有ShowInnerProperties则就是一个典型的应用以便让我们直接在熟悉细节面板里直接编辑ChildActor的属性数据。
但假如我们去掉这个ShowInnerProperties我们可以来前后对比一下效果
```cpp
class UChildActorComponent : public USceneComponent
{
UPROPERTY(VisibleDefaultsOnly, DuplicateTransient, Category=ChildActorComponent, meta=(ShowInnerProperties))
TObjectPtr<AActor> ChildActorTemplate;
}
void UMyProperty_Inner::RemoveActorMeta()
{
FProperty* prop = UChildActorComponent::StaticClass()->FindPropertyByName(TEXT("ChildActorTemplate"));
prop->RemoveMetaData(TEXT("ShowInnerProperties"));
}
void UMyProperty_Inner::AddActorMeta()
{
FProperty* prop = UChildActorComponent::StaticClass()->FindPropertyByName(TEXT("ChildActorTemplate"));
prop->SetMetaData(TEXT("ShowInnerProperties"), TEXT(""));
}
```
## 对比效果:
![Untitled](Untitled%201.png)
可以发现去除ShowInnerProperties后ChildActorTemplate属性退化成一个普通的对象引用我们无法在上面直接编辑对象的内部属性。
## 原理:
源码里最典型的例子是ChildActorTemplate这样就可以直接显示出内部的属性。
```cpp
class UChildActorComponent : public USceneComponent
{
UPROPERTY(VisibleDefaultsOnly, DuplicateTransient, Category=ChildActorComponent, meta=(ShowInnerProperties))
TObjectPtr<AActor> ChildActorTemplate;
}
```
作用的源码:
```cpp
void FPropertyNode::InitNode(const FPropertyNodeInitParams& InitParams)
{
const bool bIsObjectOrInterface = CastField<FObjectPropertyBase>(MyProperty) || CastField<FInterfaceProperty>(MyProperty);
// we are EditInlineNew if this property has the flag, or if inside a container that has the flag.
bIsEditInlineNew = GotReadAddresses && bIsObjectOrInterface && !MyProperty->HasMetaData(Name_NoEditInline) &&
(MyProperty->HasMetaData(Name_EditInline) || (bIsInsideContainer && OwnerProperty->HasMetaData(Name_EditInline)));
bShowInnerObjectProperties = bIsObjectOrInterface && MyProperty->HasMetaData(Name_ShowInnerProperties);
if (bIsEditInlineNew)
{
SetNodeFlags(EPropertyNodeFlags::EditInlineNew, true);
}
else if (bShowInnerObjectProperties)
{
SetNodeFlags(EPropertyNodeFlags::ShowInnerObjectProperties, true);
}
}
void FItemPropertyNode::InitExpansionFlags(void)
{
FProperty* MyProperty = GetProperty();
if (TSharedPtr<FPropertyNode>& ValueNode = GetOrCreateOptionalValueNode())
{
// This is a set optional, so check its SetValue instead.
MyProperty = ValueNode->GetProperty();
}
bool bExpandableType = CastField<FStructProperty>(MyProperty)
|| (CastField<FArrayProperty>(MyProperty) || CastField<FSetProperty>(MyProperty) || CastField<FMapProperty>(MyProperty));
if (bExpandableType
|| HasNodeFlags(EPropertyNodeFlags::EditInlineNew)
|| HasNodeFlags(EPropertyNodeFlags::ShowInnerObjectProperties)
|| (MyProperty->ArrayDim > 1 && ArrayIndex == -1))
{
SetNodeFlags(EPropertyNodeFlags::CanBeExpanded, true);
}
}
void FPropertyNode::RebuildChildren()
{
if (HasNodeFlags(EPropertyNodeFlags::CanBeExpanded) && (ChildNodes.Num() == 0))
{
InitChildNodes();
if (ExpandedPropertyItemSet.Size() > 0)
{
FPropertyNodeUtils::SetExpandedItems(ThisAsSharedRef, ExpandedPropertyItemSet);
}
}
}
```
特别注意这里的bShowInnerObjectProperties的判断条件是bIsObjectOrInterface 且有meta因此该特性只作用于对象引用上。然后如果判断有EPropertyNodeFlags::ShowInnerObjectProperties则继续设置EPropertyNodeFlags::CanBeExpanded最后导致展开对象的属性。

View File

@@ -0,0 +1,64 @@
# ShowOnlyInnerProperties
- **功能描述:** 把结构属性的内部属性直接上提一个层级直接展示
- **使用位置:** UPROPERTY
- **元数据类型:** bool
- **限制类型:** FStruct属性
- **关联项:** [ShowInnerProperties](../ShowInnerProperties/ShowInnerProperties.md)
- **常用程度:** ★★★
把结构属性的内部属性直接上提一个层级直接展示,而不是像默认一样归属于一个可展开的父级结构。
## 测试代码:
```cpp
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FMyPropertyInner InnerStruct;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (ShowOnlyInnerProperties))
FMyPropertyInner InnerStruct_ShowOnlyInnerProperties;
```
## 效果对比:
![Untitled](Untitled.png)
可以发现InnerStruct_ShowOnlyInnerProperties的内部属性直接就显示在对象的当前层级上而InnerStruct的属性有一个结构名称作为Category来展开。
## 原理:
在遇见FStructProperty的时候会开始判断ShowOnlyInnerProperties来决定是否要创建一个可展开的Category或者还是直接把内部属性展示出来。有了ShowOnlyInnerProperties就会直接递归迭代到其内部属性。
```cpp
void DetailLayoutHelpers::UpdateSinglePropertyMapRecursive(FPropertyNode& InNode, FName CurCategory, FComplexPropertyNode* CurObjectNode, FUpdatePropertyMapArgs& InUpdateArgs)
{
static FName ShowOnlyInners("ShowOnlyInnerProperties");
// Whether or not to push out struct properties to their own categories or show them inside an expandable struct
// This recursively applies for any nested structs that have the ShowOnlyInners metadata
const bool bPushOutStructProps = bIsStruct && !bIsCustomizedStruct && Property->HasMetaData(ShowOnlyInners);
if (bRecurseIntoChildren || LocalUpdateFavoriteSystemOnly)
{
// Built in struct properties or children of arras
UpdateSinglePropertyMapRecursive(ChildNode, CurCategory, CurObjectNode, ChildArgs);
}
}
void FObjectPropertyNode::GetCategoryProperties(const TSet<UClass*>& ClassesToConsider, const FProperty* CurrentProperty, bool bShouldShowDisableEditOnInstance, bool bShouldShowHiddenProperties,
const TSet<FName>& CategoriesFromBlueprints, TSet<FName>& CategoriesFromProperties, TArray<FName>& SortedCategories)
{
if (CurrentProperty->HasMetaData(Name_ShowOnlyInnerProperties))
{
const FStructProperty* StructProperty = CastField<const FStructProperty>(CurrentProperty);
if (StructProperty)
{
for (TFieldIterator<FProperty> It(StructProperty->Struct); It; ++It)
{
GetCategoryProperties(ClassesToConsider, *It, bShouldShowDisableEditOnInstance, bShouldShowHiddenProperties, CategoriesFromBlueprints, CategoriesFromProperties, SortedCategories);
}
}
}
}
```

View File

@@ -0,0 +1,35 @@
# ThumbnailSize
- **功能描述:** 改变缩略图的大小。
- **使用位置:** UCLASS, UPROPERTY
- **引擎模块:** Object Property
- **元数据类型:** bool
- **关联项:** [DisplayThumbnail](DisplayThumbnail/DisplayThumbnail.md)
改变缩略图的大小。但发现并不会起作用。
## 原理:
```cpp
void SObjectPropertyEntryBox::Construct( const FArguments& InArgs )
{
// check if the property metadata wants us to display a thumbnail
const FString& DisplayThumbnailString = PropertyHandle->GetProperty()->GetMetaData(TEXT("DisplayThumbnail"));
if(DisplayThumbnailString.Len() > 0)
{
bDisplayThumbnail = DisplayThumbnailString == TEXT("true");
}
// check if the property metadata has an override to the thumbnail size
const FString& ThumbnailSizeString = PropertyHandle->GetProperty()->GetMetaData(TEXT("ThumbnailSize"));
if ( ThumbnailSizeString.Len() > 0 )
{
FVector2D ParsedVector;
if ( ParsedVector.InitFromString(ThumbnailSizeString) )
{
ThumbnailSize.X = (int32)ParsedVector.X;
ThumbnailSize.Y = (int32)ParsedVector.Y;
}
}
}
```

Binary file not shown.

View File

@@ -0,0 +1,86 @@
# Untracked
- **功能描述:** 使得TSoftObjectPtr和FSoftObjectPath的软对象引用类型的属性不跟踪记录资产的 。
- **使用位置:** UPROPERTY
- **引擎模块:** Object Property
- **元数据类型:** bool
- **限制类型:** TSoftObjectPtrFSoftObjectPath
- **常用程度:** ★
使得TSoftObjectPtr和FSoftObjectPath的软对象引用类型的属性不跟踪记录资产的 。
一般默认情况属性上的软对象引用也是会产生资产的引用依赖虽然在Load本身的时候不会像硬引用一样也去加载其他软引用对象。但是因为引用关系依然存在因此在cook的时候或者资产重定向的时候都会去检查这些软引用对象确保其也会被cook进去或者正常的处理。
而当你想在属性上记录“引用”一些资产以便之后加载使用但是又不想产生真正的资产引用依赖这个时候就可以用untracked。源码中应用的不多这是比较稀少的情况下。
和transient标记的区别是transient属性在序列化的时候也不会序列化因为其ctrl+S保存后重启编辑器会丢失值。transient属性既不产生资产引用关系也序列化保存值Untracked属性会序列化保存值但不产生资产引用关系。
## 测试代码:
```cpp
UCLASS(Blueprintable, BlueprintType)
class INSIDER_API UMyProperty_Soft :public UDataAsset
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite)
TSoftObjectPtr<UStaticMesh> MyStaticMesh;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (Untracked))
TSoftObjectPtr<UStaticMesh> MyStaticMeshUntracked;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FSoftObjectPath MySoftMesh;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (Untracked))
FSoftObjectPath MySoftMeshUntracked;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Transient)
TSoftObjectPtr<UStaticMesh> MyStaticMeshTransient;
};
```
## 蓝图效果:
在蓝图中建立一个UMyProperty_Soft DataAsset资产然后设置其属性值。然后查看其引用的资源会发现Untracked的属性其设置的资产并没有出现在引用关系中。当然Transient的属性也不在引用关系中。
![Untitled](Untitled.png)
## 原理:
Untracked元数据会设置为ESoftObjectPathCollectType::NeverCollect的选项。继续搜索会发现带有NeverCollect的FSoftObjectPath其上面的资产package 不会被算到资产引用里从而不会带到upackage Import表里。源码中有多处地方带有这个NeverCollect 的类似判断。
```cpp
bool FSoftObjectPathThreadContext::GetSerializationOptions(FName& OutPackageName, FName& OutPropertyName, ESoftObjectPathCollectType& OutCollectType, ESoftObjectPathSerializeType& OutSerializeType, FArchive* Archive) const
{
#if WITH_EDITOR
bEditorOnly = Archive->IsEditorOnlyPropertyOnTheStack();
static FName UntrackedName = TEXT("Untracked");
if (CurrentProperty && CurrentProperty->GetOwnerProperty()->HasMetaData(UntrackedName))
{
// Property has the Untracked metadata, so set to never collect references if it's higher than NeverCollect
CurrentCollectType = FMath::Min(ESoftObjectPathCollectType::NeverCollect, CurrentCollectType);
}
#endif
}
FArchive& FImportExportCollector::operator<<(FSoftObjectPath& Value)
{
FName CurrentPackage;
FName PropertyName;
ESoftObjectPathCollectType CollectType;
ESoftObjectPathSerializeType SerializeType;
FSoftObjectPathThreadContext& ThreadContext = FSoftObjectPathThreadContext::Get();
ThreadContext.GetSerializationOptions(CurrentPackage, PropertyName, CollectType, SerializeType, this);
if (CollectType != ESoftObjectPathCollectType::NeverCollect && CollectType != ESoftObjectPathCollectType::NonPackage)
{
FName PackageName = Value.GetLongPackageFName();
if (PackageName != RootPackageName && !PackageName.IsNone())
{
AddImport(Value, CollectType);
}
}
return *this;
}
```