如何设计AssetManager?


26

设计一个AssestManager来保存对游戏图形,声音等的引用的最佳方法是什么?

这些资产是否应该存储在键/值映射对中?即我要“背景”资产,并且地图返回相关的位图?有没有更好的办法?

具体来说,我在写一个Android / Java游戏,但答案可能很笼统。

Answers:


16

这取决于您的游戏范围。对于大型游戏而言,资产管理者绝对必不可少,对于小型游戏而言,资产管理器则至关重要。

对于较大的标题,您必须管理以下问题:

  • 共享资产-砖纹理是否被多个模型使用?
  • 资产寿命-不再需要15分钟前加载的资产吗?参考计数您的资产以确保您知道什么时候完成等
  • 在DirectX 9中,如果某些资产类型已加载并且图形设备“丢失”(如果您按Ctrl + Alt + Del组合键,则会发生这种情况)-您的游戏将需要重新创建它们
  • 在需要资产之前先加载资产-没有这个,您将无法构建大型开放世界游戏
  • 批量加载资产-我们通常将大量资产打包到一个文件中,以缩短加载时间-在光盘周围寻找非常耗时

对于较小的标题来说,这些问题就不那么重要了,像XNA这样的框架中都包含资产管理器-重新发明它几乎没有意义。

如果您发现自己需要资产经理,那么实际上并没有一种万能的解决方案,但是我发现一个哈希图,其键为文件名的哈希*(降低并分隔所有“固定”值)对于我从事的项目效果很好。

通常不建议在应用程序中对文件名进行硬编码,通常最好使用另一种数据格式(例如xml)将文件名描述为“ ID”。

  • 有趣的是,每个项目通常会发生一次哈希冲突。

仅仅因为您需要管理资产并不一定需要AssetManagers,这是一个大写的重要名词,可能具有太多的方法,较差的性能以及混乱的内存语义。为了进行比较,请考虑一下,如果您有很多项目管理人员(通常是好的),然后又有很多项目经理(通常是坏的),会发生什么。

2
@Joe Wreschnig-您如何在不使用资产管理器的情况下满足icStatic提到的五个要求?
antinome

8

(此处尝试避免“不要使用资产管理器”的讨论,因为我认为这是不合时宜的。)

键/值映射是一种非常有用的方法。

我们有一个ResourceManager实现,可以在其中注册不同资源类型的工厂。

“ getResource”方法使用模板为所需的资源类型找到正确的Factory,并返回特定的ResourceHandle(再次使用该模板返回SpecificResourceHandle)。

资源由ResourceManager重新引用(在ResourceHandle内部),并在不再需要时释放。

我们编写的第一个插件是“ reload(XYZ)”方法,该方法使我们可以从正在运行的引擎外部更改资源,而无需更改任何代码或重新加载游戏。(当艺术家在控制台上工作时,这是必不可少的;)

大多数时候,我们只使用ResourceManager的实例,但是有时我们仅为关卡或地图创建一个新实例。这样,我们可以在levelResourceManager上调用“关闭”,并确保没有任何泄漏。

(简要)示例

// very abbreviated!
// this code would never survive our coding guidelines ;)

ResourceManager* pRm = new ResourceManager;
pRm->initialize( );
pRm->registerFactory( new TextureFactory );
// [...]
TextureHandle tex = pRm->getResource<Texture>( "test.otx" ); // in real code we use some macro magic here to use CRCs for filenames
tex->storeToHardware( 0 ); // channel 0

pRm->releaseResource( pRm );

// [...]
pRm->shutdown(); // will log any leaked resource

6

专用的Manager类几乎永远不是正确的工程工具。如果您只需要一次资产(如背景或地图),则只应请求一次,并在完成后使其正常消失。如果需要缓存特定类型的对象,则应使用工厂,该工厂首先检查缓存,然后加载某些内容,将其放入缓存中,然后返回它-该工厂可以只是访问静态变量的静态函数。 ,而不是一种自己的类型。

史蒂夫·叶格(Steve Yegge)(还有许多其他人)写了一个很好的故事,讲述如何通过单例模式结束无用的管理器类。http://sites.google.com/site/steveyegge2/singleton-considered-stupid


2
好的,当然。但是在Android(或其他游戏)之类的情况下,您需要在启动游戏之前而不是在游戏期间将大量图形/声音加载到内存中。在加载屏幕期间,如何使用您所说的(工厂)来执行此操作?只需在加载屏幕上击中工厂中的每个对象,即可对其进行缓存?
布莱恩丹尼

我不熟悉Android详细信息,但我不知道“开始游戏之前”的意思。在需要时(或当您很快需要时)而不是在启动程序时加载资源真的是不可能的吗?我发现这种可能性极小,否则,例如,您的纹理永远不可能超出Android的微薄RAM。

@Joe看看我有关“加载屏幕”的其他问题:gamedev.stackexchange.com/questions/1171/…击中一个空的缓存意味着需要很长时间才能进入磁盘,并且可能会在首次调用时导致某些FPS性能下降。如果您已经提前知道将要命中的内容,不妨在加载过程中将其命中以对其进行预缓存,对吗?
布莱恩丹尼

同样,我无法与Android通话,但是通常情况下,进入磁盘正是您可以不击中FPS而做的事情,因为进入磁盘的线程根本不会占用任何CPU。您只需要事先预算足够多的预算,就不会弹出窗口。如果您要预先缓存所有内容是因为您提前知道需要什么,那么您实际上就不需要AssetManager,因为您根本不需要管理资产-它们已经在手。

1
@乔,工厂不是“专职经理”吗?
MSN

2

我一直认为一个好的资产管理者应该有几种运作方式。这些模式很可能是遵循通用接口的独立源模块。两种基本的操作模式是:

  • 生产模式-所有资产都是本地的,并且剥离了所有元数据
  • 开发模式-资产与其他元数据一起存储在数据库(例如MySQL等)中。该数据库将是具有本地数据库缓存共享数据库的两层系统。内容创建者将能够编辑和更新共享数据库,并自动将其传播给开发人员/质量检查系统。还应该可以创建占位符内容。由于所有内容都在数据库中,因此可以对数据库进行查询并生成报告以分析生产状态。

您需要一个可以从共享数据库中获取所有资产并创建生产数据集的工具。

在我作为开发人员的那几年,我从未见过这样的事情,尽管我只为少数公司工作,所以我的观点并没有真正的代表性。

更新资料

好的,请投反对票。我将对此设计进行扩展。

首先,您实际上不需要工厂类,因为如果您有:

TextureHandle tex = pRm->getResource<Texture>( "test.otx" );

您知道类型,只需执行以下操作:

TextureHandle tex = new TextureHandle ("test.otx");

但是然后,我在上面试图说的是,您无论如何都不会使用显式文件名,要加载的纹理将由使用该纹理的模型指定,因此您实际上不需要人类可读的名称,它可以是32位整数值,这对于CPU来说要容易得多。因此,在TextureHandle的构造函数中,您将需要:

if (texture already loaded)
  update texture reference count
else
  asset_stream = new AssetStream (resource_id)
  asset_stream->ReadBytes
  create texture
  set texture ref count to 1

AssetStream使用resource_id参数查找数据的位置。它执行此操作的方式取决于您所运行的环境:

在开发中:流在数据库中查找ID(例如,使用SQL)以获取文件名,然后打开文件,该文件可以在本地缓存,或者如果本地文件不存在或从服务器拉出,则从服务器中提取过时了。

在Release中:流在键/值表中查找ID,以获取偏移量/大小到大型打包文件(如Doom的WAD文件)中。


我否决了您的意见,因为您建议将所有内容都用主键而不是真正的VCS拼凑到SQL表中。我也考虑使用不透明的ID而不是字符串名称进行过早的优化。我在两个大型项目上使用了字符串,除了翻译键之外,所有资产都使用了字符串,而翻译键中有数十万个非常长的字符串键(然后只能移植到控制台)。它们通常是规范化的,因此我们可以使用指针比较而不是字符串比较,但是字符串比较通常由内存获取的成本而不是实际的比较决定。

@Joe:我仅以SQL为例,然后只有在开发环境中,您才可以使用VCS。我只建议使用SQL数据库,因为这样您便可以向存储的对象中添加额外的信息,并使用SQL函数从数据库中查询信息(比其他任何东西都有更大的管理优势)。至于不透明的ID作为过早的优化-我猜有些人可能会这样看,但是我认为从它开始而不是在开发的后期展示它会更容易。如果您使用ID或字符串,我认为这不会对开发造成太大影响。
Skizz 2010年

2

我想为资产做的事情是建立一个总经理。受Doom引擎的启发,集总是包含资产的数据片段,存储在集总文件中,该文件声明了集的名称,长度,类型(位图,声音,着色器等)和内容类型(文件,另一个集总,内部块文件本身)。在启动时,这些块将输入到二叉树中,但尚未加载。每个图(也是一个块)都有一个依赖关系列表,这些依赖关系只是该图需要工作的块名称。除非已加载这些块,否则它们将在加载地图时加载。此外,加载地图的相邻地图的集总(不是同时加载),而是在引擎出于某种原因空转时加载。这样可以使地图无缝连接,并且没有加载屏幕。

我的方法非常适合开放世界地图,但是基于关卡的游戏无法从该方法获得的无缝性中受益。希望这可以帮助!

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.