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

1,将UObject写到Package中

cpp 复制代码
//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

cpp 复制代码
	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加载与管理 - 知乎

相关推荐
蜀黍@猿39 分钟前
【C++ 基础】从C到C++有哪些变化
c++
Am心若依旧40941 分钟前
[c++11(二)]Lambda表达式和Function包装器及bind函数
开发语言·c++
zh路西法1 小时前
【C++决策和状态管理】从状态模式,有限状态机,行为树到决策树(一):从电梯出发的状态模式State Pattern
c++·决策树·状态模式
轩辰~1 小时前
网络协议入门
linux·服务器·开发语言·网络·arm开发·c++·网络协议
lxyzcm1 小时前
C++23新特性解析:[[assume]]属性
java·c++·spring boot·c++23
蜀黍@猿2 小时前
C/C++基础错题归纳
c++
雨中rain2 小时前
Linux -- 从抢票逻辑理解线程互斥
linux·运维·c++
ALISHENGYA3 小时前
全国青少年信息学奥林匹克竞赛(信奥赛)备考实战之分支结构(实战项目二)
数据结构·c++·算法
arong_xu3 小时前
现代C++锁介绍
c++·多线程·mutex
汤姆和杰瑞在瑞士吃糯米粑粑3 小时前
【C++学习篇】AVL树
开发语言·c++·学习