这篇博客介绍了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 assets
和asset bundles
,这些东西在runtime的时候很有用。
Primary Assets
Primary Asset
指的是可以针对于UObject::GetPrimaryAssetId()
返回一个有效的值的UObject
。
对于默认的工程,所有在/Game/Maps
路径下的Maps会被设为Primary Assets,而所有其他的assets如果需要设定为Primary Assets,都需要手动指定。
所有的Primary Assets
是通过FPrimaryAssetId
来进行引用的,FPrimaryAssetId
拥有如下的属性:
-
PrimaryAssetType
:这指的是一个用于描述物件的逻辑类型的名字,通常是基类的名字。例如,我们有两个继承自同一个本地类AWeapon
的BPAWeaponRangedGrenade_C
和AWeaponMeleeHammer_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,分别是PrimaryAssetTypeTag
和PrimaryAssetNameTag
。因此,当一个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 Handle
的shared pointer
来供后期调用。 -
RequestSyncLoad
是RequestAsyncLoad
的同步版本。这个函数要么会进行异步载入并且调用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。返回一个FStreamableHandle
的shared_pointer
用于追踪,并且在Loading完成后调用回调函数。 -
UnloadPrimaryAssets(AssetList)
:这个函数会调用这些primary assets的GC。 -
ChangeBundleStateForPrimaryAssets(AssetList, Add, Remove)
:这个函数可以用一种更复杂的方式去处理bundle state。
Asset Bundles
Asset Bundle在Unity中很常见了——总体来说就是显式的primary assets相关的assets列表。
- 从底层来看,一个Bundle本质上其实就是一个
FName
到TArray<FStringAssetReferences>
的map。每一个bundle都与一个Primary Asset Id
相关。但是……这个东西也可以是一个动态的asset。 - 如果需要对普通的Primary Asset生成对应的Asset Bundle,我们需要在自己的Object中加入一个
FAssetBundleData
的UStruct
,并且在进行save操作的时候将这个UStruct
进行填充。然后,这些数据就会被写在asset registry tag中,并且在这些asset数据被读取的时候,这个UStruct
会被识别并处理。 - AssetBundle meta tag可以被设定为确定的
AssetPtr
或者StringAssetReference
。 - 可以通过调用
AddDynamicAsset
函数来在runtime的时候处理一些特定的asest bundle. - 在Asset Bundle中的任何东西,都会被认为是该primary asset的一部分。这在进行Chunking的时候会非常有用。
其他
整个的AssetManager是一个很大的系统,如果有个例子就更好了。
如果有时间就把Fortnite的例子翻译过来吧……
<全文完>