《游戏引擎架构》学习笔记——第五章(一)
第五章 游戏支持系统
每个游戏都需要一些底层支持系统,以管理一些例行却关键的任务。
5.1 子系统的启动和终止
游戏引擎是复杂软件,由多个相互合作的子系统结合而成。当引擎启动时,必须一次配置及初始化每个子系统。各子系统间的相互依赖关系,隐含地定义了每个子系统所需的启动次序。(各子系统的终止通常会采用与启动时的反向次序)
1.C++的静态初始化次序(是不可用的)
C++中,在调用程序进入点(main()或Windows下的WinMain())之前,全局及静态对象已被构建,而这些构造函数的调用次序是无法预知的;在结束返回之后,全局静态对象的析构函数的调用次序也是无法预知的。
要实现游戏引擎中的子系统,常见的设计模式是为每个子系统定义单例类(通常称为管理器)。
若我们能够指明全局或静态实例的建构、析构次序,那么就可以把单例定义为全局变量,而不必使用动态内存分配。但由于没法直接控制建构及析构次序,此法不通。
1.1 按需构建
函数内声明的静态变量并不会与main()之前建构,而是在第一次调用该函数时才建构的。因此,若把全局单例改为静态变量,我们就可控制全局单例的建构次序。
class RenderManager
{
public:
//取得唯一实例
static RenderManager& get()
{
//此函数中的静态变量将于函数被首次调用时构建
static RenderManager sSingleton;
return sSingleton;
}
RenderManager()
{
//对于需要依赖的管理器,先通过调用它们的get()启动它们
VideoManager::get();
TextureManager::get();
}
~RenderManager()
{
//终止管理器
}
}
或:
(含动态分配单例的变种)
static RenderManager& get()
{
static RenderManager* gpSingleton = NULL;
if (gpSingleton == NULL)
{
gpSingleton = new RenderManager;
}
ASSERT(gpSingleton);
return *gpSingleton;
}
缺点:
1.此方法不能控制析构次序
2.RenderManager确切的建构时间难以预计(不知道第一次调用 RenderManager::get() 的时间)。3.get()函数可能会有很高的开销。
2.行之有效的简单方法
明确地为各单例管理器类定义启动和终止函数,这些函数取代了建构和析构函数。在建构和析构函数中不做任何事情。那么在main()中(或某个管理整个引擎的单例中),按所需的明确次序调用各启动和终止函数。
“蛮力方法”:
class RenderManager{
public:
RenderManager(){}
~RenderManager(){}
void startUp(){
//自定义的启动函数
}
void shutDown(){
//自定义的终止函数
}
};
class PhysicsManager{/*同上面类似*/ };
class AnimationManager{/*同上面类似*/ };
class MemoryManager{/*同上面类似*/ };
RenderManager gRenderManager;
PhysicsManager gPhysicsManager;
AnimationManager gAnimationManager;
MemoryManager gMemoryManager;
int _tmain(int argc, _TCHAR* argv[])
{
//启动各个子系统
gMemoryManager.startUp();
...
gRenderManager.startUp();
gAnimationManager.startUp();
gPhysicsManager.startUp();
//运行游戏
//终止各个子系统
gPhysicsManager.shutDown();
gAnimationManager.shutDown();
gRenderManager.shutDown();
...
gMemoryManager.shutDown();
return 0;
}
其他方法
1.让各管理器把自己登记在一个全局的优先队列,之后再按恰当次序逐一启动所有管理器
2.通过每个管理器列举其依赖的管理器,定义一个管理器间的依赖图,然后按互相依赖关系计算最 优的启动次序
(该书作者推荐使用“蛮力方法”)
3.一些实际引擎的粒子
(略)