作者:Perit http://www.cnblogs.com/Perit
对于当前游戏场景来说,多数采用是小场景,或规模不算大的场景,这些场景在资源管理方面的特点就是引擎可以在载入场景时完成资源读取,创建,以及初始化操作。在渲染场景时,所有对象,或即将要创建出来的游戏对象所用资源大都可以预先加载创建。而对于大规模场景来说显然由于资源多,无法采用预加载方案。那么采用分块资源管理方案是一种自然的思想。
那么采用分块资源方案会带来哪些新的问题呢。
第一就是分块场景是什么形状的, 多大合适,
最常见的应该就是正四方形的场景块了,像六角形的只是听说过,还没有见过真正用于产品中的,主要原因是要考虑场景的分割管理,地形LOD处理等因素。而分块场景的大小要根据你的场景块的加载时间,保证在尽可能小的时间里加载尽可能大的场景块,因此如果你的场景比较复杂,物体摆放密度比较大,场景块就要分割小一些,反之尽可能将场景块画大些,可以减小场景的加载和卸载频率。对于场景块的管理采用加载主角周围一定范围内的场景块,如3x3或5x5等.
第二就是场景资源的生命周期问题
资源的生命周期按照申请使用资源一方来说可以分为,属于分块场景自已的资源,还有就是属于多个分块场景共用的全局的场景资源,分块的场景自有资源一般指的是当前块自身使用的资源,这种资源一般在读取创建场景分块时生成的。随着分块的释放也跟着释放。而全局的场景资源则是在申请时检查资源是否已经创建完成,释放时检查资源是否还被的别的场景块使用,如果没有可以选择释放,或者延迟释放等策略。那么这种资源生命周期的管理方法无疑就是采用对象引用计数大法。在申请资源时,先检索对应资源管理器中是否存在该资源,如果有将资源引用数加一返回该资源,没有的话将该资源申请放入资源请求队列,在卸载资源时资源引用数减一,如是引用数为零时表明资源已不被引擎使用,可以卸载掉。
第三是资源加载的时机,同步还是异步加载。
记的在云风博客中看到哪位提到通过巧妙设计资源的预加载文件信息,可以动态采用预加载方案实现后台资源的悄悄加载,这样在资源申请时很大可能性已经完成了资源的加载操作,剩下的就是资源创建等后续操作,因此资源申请速度非常快。这个办法的关键问题是资源的预加载信息的设计,因为有的资源本身又会引用新的资源,一般情况如果不解析第一个资源格式,就不会知道其引用的其它资源,也就无法搜集齐所有的潜在的要加载的资源,因此在游戏资源的编辑时就要完成这种预加载信息的制作。因此这个方案的关键点还是预加载信息的设计是否合理,这就说明了资源格式的重要性。
采用资源预读的方法一般来说仅完成资源由外存读入内存的操作,但一般对于第一次文件IO操作来说,是非常费时的,由其这种IO操作每个分块场景可能好几百次,因此这个预读方法就能省掉了大部分的加载时间,而资源的解析和创建一般相对资源加载来说要快很多,因此解决了资源加载的问题后,我们就有可能实现资源解析和创建在主线程同步完成,而并不会带来太多的延迟。由于采用主线程同步资源创建方案,因此逻辑上会比较容易处理。可靠性比较高,不容易出错。
但是这样的效果是否足够好呢?当我们场景比较简单时效果应该明显,但是当前场景的复杂度在不断提升,资源格式也在不断复杂,资源的创建所需时间也在加长时,我们就需要进一步提升资源的创建速度,上面我们已经完成了第一步,资源的异步加载,完成了资源到内存的操作,接下来我们要进行资源的多线程解析和异步创建操作。所谓异步解析就是将加载到内存的资源放入资源解析队列,由资源解析线程完成资源格式的的解析,资源完成解析后送到资源创建队列,等待下一步的资源创建操作。因为我们一般对3D设备的操作都要在设备创建线程中完成,因此引擎如果采用多线程渲染,这一步的操作将会在渲染线程中完成。
由于考虑到资源的创建也许有可能会用时多,比如超过20ms,这时游戏就可能会卡一下子,因此资源的创建我们采用平滑创建方法,即将线程创建资源划分为小的时间片,每帧开始时检查资源创建时间片是否用完,如果没有用完则从资源创建队列中取出一个进行创建操作,完成后更新时间片,检查是否还有时间创建下一个资源,直到时间片用完或者资源创建队列为空,这种方法目的就是实现资源的平滑加载,在资源创建完成后,大部分资源即可以马上被使用了,然而有的资源还要再进行初始化操作才能使用,因此这种资源就从资源创建队列移到了资源初始化队列,资源初始化队列队象根据初始化要求选择由哪个线程完成初始化操作。当初始化操作完成后,我们的资源才真正可用。
这种资源异步加载,解析,创建,初始化操作,必然要求我们在创建游戏对象时,不可能保证马上返回一个可用的游戏对象,除非该对象的资源已经被加载完成。因此要有一定的机制保证我们的游戏对象能够知道什么时侯才可以使用资源。比如采用回调,或者简单采用游戏对象在使用前对资源进行有效性检查。
关于资源的释放策略
在我们的资源不再被任何对象使用时,我们的资源便进入生命周期的最后一个阶断—消毁。然而,我们要注意的事:
1) 资源对象的释放本身也是需要消耗时间的,虽然比创建要快很多,但是如果数量众多也不能忽视,因此要采用平滑释放的方法。
2) 资源的释放也有可能涉及到3D投备的操作,因此要用相关线程完成释放操作,
3) 资源真的需要马上释放么,是否可以缓冲一断时间防止刚被释放又马上加载。如果这样,那资源加载时就要先检查资源待释放队列,看有否需要复活的资源。若没有再按原先的方案继续执行。
我们是否达到的我们原来的目的?
到目前为止,我们的方法很好的解决了资源的加载和生成。场景已经可以很平滑漫游,玩家已经几乎要觉察不到场景是一块块的,达到了无缝加载的目的。
但是仍然会发现一些新的现象,比如在世界某处你仍能发现场景卡一下子,虽然非常短暂,是我们的方案哪出问题了么?经过测试,分析,答案开始显现。由于现在我们的机器都进入多核时代,双核机器已经普及了,我们的原先的方案是多线程资源管理方案,在实现中我们为每种类型资源分配一个独立线程负责相关的加载、解析、操作,我们还要有主逻辑线程,渲染线程,这样我们可能将会有数个线程在后台同时工作。而我们的CPU一般双核居多,因此在CPU核心有限的情况下,数量众多的线程肯定有几个线程得不到CPU时间,必然会挂起等待CPU,如果很不幸这个挂起的线程是我们的主线程或渲染线程,那么就出现了屏幕卡住的一瞬间。
原因找到了,解决方法自然也就有了,当然你不能想着把某一个线程提升优先级来达到多占CPU时间的目的,如果你没确定这样所带来的所有的后果,就应该放弃这个危险的方案,否则事情会不受我们的控制。那么我们可以采取的方案是什么?线程池是个好的解决方案,通过线程池我们可以合理分配CPU资源,我们可以包证有一个可执行线程为我们的主线程服务。
这样我们原先的多线程加载方案就要进行相应的修改了,基于线程池的多线程加载方案。
总结
大规模场景资源管理会带来许多新的问题: 资源对象格式问题, 对象异步创建和使用问题,对象的生命周期管理问题,多线程加载方案设计问题等。然而解决这些问题后给我们的回报也是明显的。那就是流畅的游戏感受。
最后看下以上方案的实现效果视频:
WOW场景高速漫游(CPU:E6550 显卡8600GT
http://v.ku6.com/show/I13YVfEpmLhhJVT1.html