UE 将UObject写到Package,加载Package获取UObject

发布于:2024-05-13 ⋅ 阅读:(99) ⋅ 点赞:(0)

1,将UObject写到Package中

//CachedMesh 就是要写的UObject对象	
UStaticMesh* CachedMesh = CachedFontData.GetGlyphMesh(ShapedGlyph.GlyphIndex, TextParamValue.bOutline, TextParamValue.Extrude, TextParamValue.Bevel, TextParamValue.BevelType, TextParamValue.BevelSegments, TextParamValue.ArcRadius);
				
FString objectName = TEXT("MyCreateObject");

// 创建包
//FString packageName = TEXT("/Game/") + saveFolder/* + TEXT("/Test3D_") + FString::FromInt(index)*/;
FString packageName = TEXT("/、Meshs/Text3D")/* + saveFolder + TEXT("/Test3D_") + FString::FromInt(index)*/;
UPackage* package = CreatePackage(*packageName);
package->MarkPackageDirty();
FString StaticMeshName = (TEXT("Test3D_") + CachedMesh->GetFName().ToString() + TEXT("_") + FString::FromInt(ShapedGlyph.GlyphIndex));
UStaticMesh* AssetOnly = DuplicateObject(CachedMesh, package, FName(*StaticMeshName));
AssetOnly->SetFlags(RF_Public | RF_NeedPostLoadSubobjects | RF_Standalone);
FString packageNameEX;
packageName = packageName + TEXT("/Test3D_") + CachedMesh->GetFName().ToString() + TEXT("_") + FString::FromInt(ShapedGlyph.GlyphIndex);
FPackageName::TryConvertLongPackageNameToFilename(packageName, packageNameEX, TEXT(".uasset"));

// 保存Package
FSavePackageArgs SaveArgs = { nullptr, nullptr, EObjectFlags::RF_Standalone | EObjectFlags::RF_Public, SAVE_None, false, true, true, FDateTime::MinValue(), GError, nullptr };
UPackage::Save(package, AssetOnly, *packageNameEX, SaveArgs);

2,加载Package,读取其中的UObject

	TextParam TextParamValue{};
	FString packageName = TEXT("/Meshs/Text3D");
	IFileManager& MyFileManager = IFileManager::Get();
	FString packageNameEX;
	FPackageName::TryConvertLongPackageNameToFilename(packageName, packageNameEX, TEXT(""));
	if (MyFileManager.DirectoryExists(*packageNameEX))
	{
		TArray<FString> AssetName;
		MyFileManager.FindFiles(AssetName, *packageNameEX, TEXT("*.uasset"));
		FPXCachedFontData& CachedFontData = GetCachedFontData(DefaultFont);
		for (int i = 0; i < AssetName.Num(); i++)
		{
			TSet<FName> LoadTags = {};
			FString PackageName = packageNameEX + TEXT("/") + AssetName[i];
			UPackage* FoundPackage = FindObjectFast<UPackage>(nullptr, FName(*PackageName));
			if (FoundPackage == nullptr)
			{
				FLinkerInstancingContext InstancingContext(MoveTemp(LoadTags));
				FoundPackage = LoadPackage(nullptr, *PackageName, LOAD_None, nullptr, &InstancingContext);
				if (FoundPackage == nullptr)
					continue;
			}
			FString StaticMeshName = AssetName[i];
			StaticMeshName.RemoveFromEnd(TEXT(".uasset"));
			FoundPackage->GetLinker()->LoadAllObjects(true);
			UObject* CurrentObject = FoundPackage;
			CurrentObject = StaticFindObjectFastInternal(nullptr, CurrentObject, *StaticMeshName);
			UStaticMesh* StaticMesh = dynamic_cast<UStaticMesh*>(CurrentObject);
			if (StaticMesh == nullptr)
				continue;
			StaticMeshName = StaticMesh->GetName();
			TArray<FString> stringArray;
			StaticMeshName.ParseIntoArray(stringArray, TEXT("_"), false);
            //这步不需要,是项目的代码
			CachedFontData.AddGlyphMesh(StaticMesh, FCString::Atoi(*(stringArray.Last())), TextParamValue.bOutline, TextParamValue.Extrude, TextParamValue.Bevel, TextParamValue.BevelType, TextParamValue.BevelSegments, TextParamValue.ArcRadius);
		}
	}

3,代码解析

LoadObject

LoadObject主要是把磁盘中的东西加载到一个UObject里面去,因为涉及的对象可能会引用其他的资产,所以会触发一个递归的加载。过程如下:

  1. CreatePackage:因为每个UObject的最外层outer都是UPackage,所以会先调用CreatePackage创建一个UPackage,比如我们加载的路径是"Game/http://xx.xxx",那么"Game/xx"就是包名,创建的具体方法就是NewObject<UPacakge>。
  2. FLinkerLoad::CreateLinker:创建一个LinkerLoad,LinkerLoad主要负责去磁盘中加载UObject的数据,然后管理对应资产之间的一些引用。FLinkerLoad继承了FLinkTables,里面有一个导入和导出模块的定义。相关代码在下方
  3. FLinkerLoad::LoadAllObjects:加载包里所有的UObject,这里就是根据上面的ExportMap中的UClass信息,生成对应的UObject,然后把数据反序列化进去。
  4. StaticFindObjectFast:根据ObjectName找到加载完成的UObject并返回
class FLinkerTables
{
	/** The list of FObjectImports found in the package */
	//这个是这个包引用的对象,如果资产引用了其他资产,在这里会有体现
	TArray<FObjectImport> ImportMap;
	/** The list of FObjectExports found in the package */
	//这个是这个包要导出的对象,我们LoadObject需要加载的对象都在这里面记录
	TArray<FObjectExport> ExportMap;
}

LoadClass

LoadClass本质就是调用了LoadObject,代码如下:

UClass* StaticLoadClass( UClass* BaseClass, UObject* InOuter, const TCHAR* InName, const TCHAR* Filename, uint32 LoadFlags, UPackageMap* Sandbox )
{
    UClass* Class = LoadObject<UClass>( InOuter, InName, Filename, LoadFlags, Sandbox );
    return Class;
}

关于LoadClass的路径,LoadClass填路径要按照这样的格式"Game/xxx.xx_C",相比LoadObject,路径名后面多了一个"_C"。这种路径分为两个部分,一个是包名,也就是"."(点)前面的部分为。一个是ObjectName,UClass的ObjectName多个"_C"。在LoadObject的时候,会先把所有相关的UObject加载好,在FUObjectHashTables进行管理,然后根据这个ObjectName进行查找,所以如果LoadClass的时候后面不加个"_C",UClass是被加载到内存里的,但是就是在后面查找的时候会查找不到。

FindObject

有了上面对UObject的管理,UE也提供了FindObject这个接口用于查找Object,相关代码在StaticFindObjectFastInternalThreadSafe里面。内容也很简单,就不贴具体代码了,就是在上面提到的FUObjectHashTables里面找,先从HashOuter,找不到就到PackageToObjectListMap,再找不到就到Hash里面找。

参考:UE5:UObject加载与管理 - 知乎