zhouqijie

一、游戏世界加载和串流

1.简单的关卡加载

最简单的的方法。只允许游戏每次加载一个世界组块。过关时玩家需要等待关卡载入。

内存管理很简单,堆栈分配器很适合这种仅加载一个关卡的设计。游戏开始运行时,首先加载所有世界组块都需要的LSR资源至栈底。然后每个游戏世界组块以及它相关的资源加载于LSR数据之上。关卡完成后,只需把堆栈指针回复至LSR数据之上。

缺点:

不能实现无缝世界。载入关卡时玩家只能看载入界面。



2.阻隔室

把游戏世界切割成两个不同大小的块,大的一块用来存储主要组块,小的一块用来存储一个阻隔室(air lock)

玩家进入阻隔室后,会有障碍物防止玩家看到或者返回之前的组块,然后卸载之前的完整组块并加载下一个完整的组块。(需要异步文件I/O)



3.游戏世界串流

很多现代的开放世界游戏拥有巨大且无缝的游戏世界,这类技术称为串流(streaming)

  1. 能在正常游戏时加载数据。
  2. 管理内存。使玩家在不断加载和卸载数据时不会导致内存碎片问题。

Jason:粗粒度的方式是预先分割同等大小的几个组块缓冲区,循环使用这些组块缓冲区来载入组块。注意组块的大小必须大致相同,且不能超载。
Jason:更细粒度的内存分割方式,不用串流较大的内存组块,而是每个游戏资产都切割为同等大小的数据块。然后使用一个块为单位、基于内存池的内存分系统,按需加载卸载资源数据。而无须担心内存碎片。

使用细粒度的块内存分配方案时候,需要判断哪个时间需要加载哪些资源。

Jason:可以使用一个关卡加载区域系统控制加载和卸载资源。玩家在某任意时刻会位于一个或者多个这些区域中,求出这些区域的组块列表的并集,来决定内存中应该有的世界组块集合。

Jason:关卡加载系统定期检查此主控列表,与内存中现有组块比较。如果组块在列表中不再有了,就可以卸载内存中的该组块。如果列表中出现新组块ia,则把它加载至内存。

Jason:应该精心设计关卡加载区域和世界组块,确保玩家不会看到组块的加载或者卸载过程。



4.对象生成和内存管理

游戏世界载入内存后,需要管理世界中动态对象的生成和销毁。

重要工作之一是新生成游戏对象时,进行高效的动态内存分配。

对象生成的离线内存分配:

Jason:部分游戏引擎完全禁止在游戏途中动态分配内存,在载入组块后立即生成所有动态游戏对象,然后就不再创建和销毁游戏对象了。称为“游戏对象守恒”。

Jason:这种设计把游戏对象的内存置于世界组块的数据中,能完全避免内存碎片,能避免意外的内存不足。缺点是限制了游戏性设计。

对象生成的动态内存分配:

Jason:动态内存分配最大的问题是内存碎片。由于游戏对象大小不一样,所以不能用池分配器。又由于生产销毁次序一般不相同,所以不能用堆栈分配器。需要其他的解决方案。

为每个对象类型建立一个池。这种方法的限制是会需要建立很多个池,并估算每个类型有多少实例。

容许游戏对象使用稍大的池,实现不同游戏对象共享内存池。这样能显著减少内存池的数量,但每个池都会浪费一些内存。

可能需要建立一组元素大小递增的池分配器,在分配内存的时候从最小开始搜寻合适的池。

遇到太大的对象没有合适的池,使用通用的堆分配器也无所谓,因为大块内存碎片问题不严重。

最直接的方法,即碎片整理及重定向。



5.游戏存档

游戏存档功能能保存游戏状态,下次进入游戏时,可以恢复至之前的状态。

游戏存档系统不是世界载入系统,不需要存储每个对象的所有状态细节。所以存档文件要比世界组块文件小很多,注重数据压缩和省略。

几种存档方式:

  1. 存档点:限制只能在某些指定地点存档。这种存档通常不需要存储每个游戏对象的状态。
  2. 随地存档:任何地点都可以存档。通常需要存储一些游戏性相关对象的状态。

(END)