Unreal Engine 4 —— Asset Manager介绍

时间:2021-02-04 18:03:36

这篇博客介绍了UE4中的Asset Manager相关的内容,大部分参考的是Fortnite的公开资料。


背景(题外话

如果不出意外的话,未来的一段时间将要回归UE4的全职开发了。用了将近9个月的unity,或多或少能感受到这两款商业引擎的设计理念的差异,也开阔了不少思路。在使用Unity 5的过程中发现它的Asset Management做的很不错,回想起来当初用UE4做Console的时候也没有遇到过这种需求 —— 不过这个功能在移动端应该是很常用的需求,因此也做个记录吧……

涉及到的类

  • Assest
    Asset指的是在Content Browser中看到的那些物件。贴图,BP,音频和地图等都属于Asset.
  • Asset Registry
    Asset Registry是资源注册表,其中存储了每个特定的asset的有用的信息。这些信息会在asset被储存的时候进行更新。
  • Streamable Managers
    Streamable Managers负责进行读取物件并将其放在内存中。
  • Primary Assets
    Primary Assets指的是在游戏中可以进行手动载入/释放的东西。包括地图文件以及一些游戏相关的物件,例如character classs或者inventory items.
  • Secondary Assets
    Secondary Assets指的是其他的那些Assets了,例如贴图和声音等。这一类型的assets是根据Primary Assets来自动进行载入的。
  • Asset Bundle
    Asset Bundle是一个Asset的列表,用于将一堆Asset在runtime的时候载入。
  • Asset Manager
    Asset Manager是一个可选的全局单例类,用于管理primary assetsasset bundles,这些东西在runtime的时候很有用。

Primary Assets

Primary Asset指的是可以针对于UObject::GetPrimaryAssetId()返回一个有效的值的UObject

对于默认的工程,所有在/Game/Maps路径下的Maps会被设为Primary Assets,而所有其他的assets如果需要设定为Primary Assets,都需要手动指定。

所有的Primary Assets是通过FPrimaryAssetId来进行引用的,FPrimaryAssetId拥有如下的属性:

  • PrimaryAssetType:这指的是一个用于描述物件的逻辑类型的名字,通常是基类的名字。例如,我们有两个继承自同一个本地类AWeapon的BPAWeaponRangedGrenade_CAWeaponMeleeHammer_C,那么它们会有同样的PrimaryAssetType——"Weapon"
  • PrimaryAssetName:这指的是用于描述这个asset的名字。通常来说,这就是这个object的名字(short name),但是对于例如说maps来说,这个值就是对应的asset path。
  • PrimaryAssetType:PrimaryAssetName可以组成整个游戏实例中的asset的唯一的描述。当客户端在和服务端通信的时候,就可以通过这个字符串来确认某个物件。例如,"Weapon:BattleAxe_Tier2"本质上和"/Game/Items/Weapons/Axes/BattleAxe_Tier2.BattleAxe_Tier2_C"是一样的。
  • FPrimaryAssetId中分别有两个FName的Tag,分别是PrimaryAssetTypeTagPrimaryAssetNameTag。因此,当一个Primary Asset被保存了之后,就可以直接在Asset Registry中通过这两个Tag来找到这个Asset。

Streamable Manager

FStreamableManager可以用来处理assets的读取,并且将其在被需要的时候保存在内存中。针对不同的操作,可以有不同的Streamable Manager。FStreamableManager可以与FStreamableHandle一起工作,来更好的处理物件在内存中的生命周期。

  • FStreamableHandle是一个用于处理assets读取的结构体。通常来讲,在streaming operation会返回一个shared pointer,这个shared pointer就是用于追踪这个结构体的。当一个handle是active的时候,它就可以确保它引用的那些assets是在内存中的了。
  • FStreamableHandle从Loading开始就被激活了,当这个handle被显式cancel或者release的时候,将被disactive。
  • FStreamableHandle::ReleaseHandle()可以用来被显示调用。但是当所有的指向这个handle的只能指针被销毁的时候被隐式调用。
  • FStreamableHandle::CancelHandle()是用来中断Loading过程的接口。这个函数被调用后,Loading将会被中断,并且将取消Loading完成过后的所有回调函数。
  • FStreamableHandle::WaitUntilComplete()将阻断线程,知道所需的asset被成功载入为止。这个函数被调用时,所需的asset的载入优先级将被设为最高(通过将其移到优先级队列的top来实现),并且不会影响其他的异步载入操作,因此通常比LoadObject函数更快。
  • RequestAsyncLoad是基本的stream操作。可以传入一个FStringAssetReference或者一个FStringAssetReference的列表。调用了这个操作之后,引擎会试图去Load这些Assets,并且在Loading完毕之后调用回调函数。同时,这个函数会返回一个Streamable Handleshared pointer来供后期调用。
  • RequestSyncLoadRequestAsyncLoad的同步版本。这个函数要么会进行异步载入并且调用WaitUntilComplete函数,要么直接调用LoadObject函数 —— 哪个更快就调哪个。
  • LoadSynchronous是另一种Loading的方式,这个函数会返回一个asset,并且这个函数可以有模版安全的版本。
  • 这些函数都有bManageActiveHandle的参数,默认为false —— 如果设定为true的话,会导致streamable manager本身就带有一个这个handle的reference。这样一来的话就需要手动管理以及release这个handle了。

Asset Manager

AssetManager是一个单例的UObject,它提供了在runtime的时候进行查询以及读取Primary Assets的操作。这个东西是原本用于取代ObjectLibraries当前提供的操作的,并且可以对FStreamableManager进行一层封装来处理Async Loading的操作。引擎内置的asset manager只能提供基本的管理操作,但是一些更加复杂的东西,例如caching需要自己实现。Asset Manager的基本操作如下:

  • Get():单例的Get操作。
  • ScanPathsForPrimaryAssets(Type, Paths, BaseClass):这个函数可以查询特定目录下的某类特定的primary asset,如果在Editor下则直接读取磁盘上的信息,如果在cooked工程中会从asset registry cache中读取asset信息。
  • GetPrimaryAssetPath(PrimaryAssetId) : 获得PrimaryAssetId对应的asset的object path。
  • GetPrimaryAssetIdForPath(StringReference): 获得object path对应的asset的primary asset id信息,以Type:Name的形式。
  • GetPrimaryAssetIdFromData(FAssetData):通过FAssetData来获得对应的Type:Name形式的Primary Asset Id
  • GetPrimaryAssetData(PrimaryAssetId):同理
  • GetPrimaryAssetDataList(Type):返回一个所有对应类型的asset列表。
  • GetPrimaryAssetObject(PrimaryAssetId):查询这个对应的UObject是否在内存中。如果这个UObject不再内存中,则返回nullptr。
  • LoadPrimaryAssets(AssetList, BundleState, Callback, Priority):异步载入这些primary assets和BundleState所引用的所有其他assets。返回一个FStreamableHandleshared_pointer用于追踪,并且在Loading完成后调用回调函数。
  • UnloadPrimaryAssets(AssetList):这个函数会调用这些primary assets的GC。
  • ChangeBundleStateForPrimaryAssets(AssetList, Add, Remove):这个函数可以用一种更复杂的方式去处理bundle state。

Asset Bundles

Asset Bundle在Unity中很常见了——总体来说就是显式的primary assets相关的assets列表。

  • 从底层来看,一个Bundle本质上其实就是一个FNameTArray<FStringAssetReferences>的map。每一个bundle都与一个Primary Asset Id相关。但是……这个东西也可以是一个动态的asset。
  • 如果需要对普通的Primary Asset生成对应的Asset Bundle,我们需要在自己的Object中加入一个FAssetBundleDataUStruct,并且在进行save操作的时候将这个UStruct进行填充。然后,这些数据就会被写在asset registry tag中,并且在这些asset数据被读取的时候,这个UStruct会被识别并处理。
  • AssetBundle meta tag可以被设定为确定的AssetPtr或者StringAssetReference
  • 可以通过调用AddDynamicAsset函数来在runtime的时候处理一些特定的asest bundle.
  • 在Asset Bundle中的任何东西,都会被认为是该primary asset的一部分。这在进行Chunking的时候会非常有用。

其他

整个的AssetManager是一个很大的系统,如果有个例子就更好了。

如果有时间就把Fortnite的例子翻译过来吧……

<全文完>