[UE4C++] 深入unreal asset loading

当我们将png或其他格式的图档放到Content这个folder下面,我们会发现引擎会自动把它转成uassets的格式,之后我们就可以在editor中很方便的对它进行各种操作。但是,我们到底该怎么样用C++把这个档案动态的读进来?官方文件对这方面的文件实在是少的可怜,不过没关系,原始码就是我们最好的文件,深入引擎源码阅读之后,还是可以找到相关的方法。

简单来说,引擎中所有的asset分为被控管(managed)以及没被控管(unmanaged)二种:

被控管(managed)的asset副档名为.uasset、.umap,会出现在editor的content browser中,我们可以直接在editor中对它进行各种操作,这些档案包UMG、blueprint、material或汇进来的图档……等等。另外一些则是没被控管(unmanaged)的档案,如png、json……等等其他不是经过引擎处理所产生的档案。

被控管(managed)跟没被控管(unmanaged)的档案其实都是可以动态的用C++读进引擎里面的,只是不同的是它们所要求的档案路径不同。前者要求的是LongPackageName,而后者要求的是实际的档案路径。

 

被控管档案(managed)的读取方法

读取的方法目前我所知的有二种。

第一种是先在constructor中先拿到Class,然后再NewObject,下面的范例展示如果把一个做好的UMG读进来:

AMyPlayerController::AMyPlayerController ()
{
    ConstructorHelpers::FClassFinder<UUserWidget> PutNameHere ( TEXT ( " /Game/UMG/NewWidgetBlueprint " ));
    if (PutNameHere. Class ) {
        UserWidgetClass = PutNameHere. Class ;
    }
}

void  AMyPlayerController::BeginPlay ()
{
    Super::BeginPlay ();
    auto widget = CreateWidget<UUserWidget>( this , UserWidgetClass);
    widget-> AddToViewport ();

}

 

其中CreateWidget只是对NewObject做一些检查跟Widget相关的设定,最终还是会呼叫UUserWidget* NewWidget = NewObject<UUserWidget>(Outer, UserWidgetClass);。widget->AddToViewport()这个方法可以让这个UMG显示在营幕上。

只是这个方法有个缺点,就是所有需要用到的uasset都必须要先在constructor中先把其class读进来。若是在其他地方呼叫ConstructorHelpers::FClassFinder的话则会马上喷出一个错误:UE_CLOG(!ThreadContext.IsInConstructor, LogUObjectGlobals, Fatal, TEXT(“FObjectFinders can’t be used outside of constructors to find %s”), ObjectToFind);

因此要做到真正的动态读取的话,我们可以使用第二种方法:FStringAssetReference。以下的code试着去读取UMG、umap、flipbook跟UTexture2D:

{
  FStringAssetReference testAssetRef = " /Game/UMG/NewWidgetBlueprint " ;
  UObject* pObject = testAssetRef. TryLoad ();
  UWidgetBlueprint* pWidgetBlueprint = Cast<UWidgetBlueprint>(pObject);
  auto widget = CreateWidget<UUserWidget>( this , pWidgetBlueprint->GeneratedClass);
  // do something
  // widget->AddToViewport();
  FString pathName1 = FPackageName::LongPackageNameToFilename (pObject-> GetPathName (), FPackageName::GetAssetPackageExtension ());
  FString pathName2 = FPackageName::LongPackageNameToFilename (pObject-> GetOutermost ()-> GetPathName (), FPackageName::GetAssetPackageExtension ());
}

{

  FStringAssetReference testAssetRef = " /Game/UGameAssets/Assets/horizo​​n/Maps/MainMap " ;
  UObject* pObject = testAssetRef. TryLoad ();
  UWorld* pWorld = Cast<UWorld>(pObject);
  FString pathName1 = FPackageName::LongPackageNameToFilename (pObject-> GetPathName (), FPackageName::GetMapPackageExtension ());
  FString pathName2 = FPackageName::LongPackageNameToFilename (pObject-> GetOutermost ()-> GetPathName (), FPackageName::GetMapPackageExtension ());
}
{
  FStringAssetReference testAssetRef = " /Game/UGameAssets/Assets/horizo​​n/flipbook/hero_down " ;
  UPaperFlipbook* pObject = Cast<UPaperFlipbook>(testAssetRef. TryLoad ());
}
{
  FStringAssetReference testAssetRef = " /Game/UGameAssets/Assets/horizo​​n/spriter/MyTexture " ;
  UTexture2D* pObject = Cast<UTexture2D>(testAssetRef. TryLoad ());
}

 

我们可以看到FPackageName::LongPackageNameToFilename,从范例中应该大概可以看出来这个方法的用途是什么。它把我们喂入的package path转换成档案路径。

喂入PackageFileNamePath: /Game/UMG/NewWidgetBlueprint
转成FileNamePath: ../../../../../../Users/dorgon/Documents/Unreal Projects/MyProject/Content/UMG/NewWidgetBlueprint.uasset

上述的方法虽然我们可以发现在editor模式下可以运行的很好,但是在打包出来的版本却发现会无法读入UMG或blueprint这二个asset。这是因为在cooked build中,引擎会把这二种assets编译成带有_C suffix的Blueprint generated class。因此为了读取这二类的档案,我们需要换用下面的方法才行:

FStringClassReference clsRef = " Blueprint'/Game/MyBlueprint.MyBlueprint_C' " ;
// AMyActor is my own c++ implemented actor that inherited from AActor
UClass* myBlueprintActorClass = clsRef.TryLoadClass<AMyActor>();
auto myBlueprintActor = GetWorld()->SpawnActor<AMyActor>(myBlueprintActorClass);

 

必须要注意的话,档名后面必须带有_C才行,它指的是该assets是由blueprint所产生的class。

没被控管档案(Unmanaged)的读取方法

其实要读取这类档案主要的概念是要PackageFileNamePath转成FileNamePath,例如:

FPackageName::TryConvertLongPackageNameToFilename(LongPackageName, outRealFilePath, TEXT(“”), false);

之后再拿outRealFilePath去将档案读进来:

TArray<uint8> RawFileData;
FFileHelper::LoadFileToArray(RawFileData, *outRealFilePath));

其中RawFileData拿进来之后看要做什么对应的处理就看自己了,下面提供一个plugin有将png或其他格式的图档读进来的范例:

VictoryPlugin

 

PackageName

或许有人会问,为什么/Game/开头就代表读取Content底下?其实这是因为UnrealEngine4对于档案命名系统做了一层抽象的管理,我们其实可以复盖掉/Game/所指向的是哪个folder,从上面的范例我们可以看到FPackageName::TryConvertLongPackageNameToFilename这个方法,其实引擎内部管理了一个TArray<FPathPair> ContentRootToPath;,其预设的内容如下:

- ContentRootToPath Num= 10 	TArray<FPathPair,FDefaultAllocator>   
+ [ 0 ] {RootPath= L" /Paper2D/ " ContentPath= L" ../../../Engine/Plugins/2D/Paper2D/Content/ " } FPathPair
+ [ 1 ] {RootPath= L" /Engine/ " ContentPath= L" ../../../Engine/Content/ " } FPathPair
+ [ 2 ] {RootPath= L" /Engine/ " ContentPath= L" ../../../Engine/Shaders/ " } FPathPair
+ [ 3 ] {RootPath= L" /Game/ " ContentPath= L" C:/Users/Dorgon/Documents/Unreal Projects/MyProject/Content/ " } FPathPair
+ [ 4 ] {RootPath= L" /Script/ " ContentPath= L" C:/Users/Dorgon/Documents/Unreal Projects/MyProject/Script/ " } FPathPair
+ [ 5 ] {RootPath= L" /Temp/ " ContentPath= L" C:/Users/Dorgon/Documents/Unreal Projects/MyProject/Saved/ " } FPathPair
+ [ 6 ] {RootPath= L" /Game/ " ContentPath= L" ../../../MyProject/Content/ " } FPathPair
+ [ 7 ] {RootPath= L" /Script/ " ContentPath= L" ../../../MyProject/Script/ " } FPathPair
+ [ 8 ] {RootPath= L" /Temp/ " ContentPath= L" ../../../MyProject/Saved/ " } FPathPair
+ [ 9 ] {RootPath= L" /Config/ " ContentPath= L" ../../../MyProject/Config/ " } FPathPair

 

我们可以用下面这个方法来覆概掉/Game/所指向的是哪个folder:

FPackageName::RegisterMountPoint(“/Game/”, FPaths::GameContentDir() + “UGameAssets/Assets/”);

由于TryConvertLongPackageNameToFilename是寻序去match,我们新加入的会再最上面,因此/Game/路径就会先读到指向我们所设定的folder了。

基于这个概念,我们也可以加入我们自己的package name,例如,如果我们有dlc被在dlc folder下面的话该怎么办?我们只要Register该关键字就行了:

FPackageName::RegisterMountPoint(“DLC”, FPaths::GamePersistentDownloadDir());

之后就可以这样用:

FStringAssetReference testAssetRef = “/DLC/UMG/NewWidgetBlueprint”;
UObject* pObject = testAssetRef.TryLoad();

 

当然若该folder没用的话,我们还可以UnRegister它:

FPackageName::UnRegisterMountPoint(“DLC”, FPaths::GamePersistentDownloadDir());

Author: 90cg

1 thought on “[UE4C++] 深入unreal asset loading

Comments are closed.