每个游戏都会包含一些底层支持系统, 例如资源管理器、日志系统、对象池、动画管理器、音乐/音效管理器等。这些管理器通常是一个单例类,如下面这段代码:
class ResourceManager
{
private:
static ResourceManager* m_pInstance;
public:
static ResourceManager* get()
{
if(m_pInstance == nullptr)
{
m_pInstance = new ResourceManager();
}
return m_pInstance;
}
};
我所在的项目之前就是使用这种方式实现的。
然而,这种方式有个极大的弊端,就是不能控制其构造和析构的时间。假如A管理器依赖B管理器,而在B管理器创建之前A已经被调用,或者B管理器已经被析构,而A还存在, 很有可能导致严重问题。
事实上这个实现方式确实导致过我们游戏崩溃。
于是上述设计被改成如下方案:
class ResourceManager
{
private:
static ResourceManager* m_pInstance;
public:
static ResourceManager* get()
{
return m_pInstance;
}
static void createInstance()
{
m_pInstance = new ResourceManager();
}
static void destoryInstance()
{
delete m_pInstance;
}
};
//其他管理器
...
//
class GOW
{
static void Init()
{
ResourceManager::createInstance();
//其他管理器同上
...
//
}
static void UnInit()
{
ResourceManager::destoryInstance();
//其他管理器同上
...
//
}
};
即明确的为各单例管理器定义构造和析构的函数,替代简单的get,这样就可以按所需的明确次序调用各启动和终止函数。
书中提到了更优雅的方法是:启动时将这些管理器按所需次序启动并放入一个栈中,这样终止时,可以逐一把管理器弹出栈并调用终止函数。根据这个思路,我想到如下实现:
class IManager
{
static void startUp() = 0;//启动管理器
static void shutDown() = 0;//终止管理器
};
class ResourceManager: public IMananger
{
private:
static ResourceManager* m_pInstance;
ResourceManager()
{
//不做任何事
}
~ResourceManager()
{
//不做任何事
}
public:
static ResourceManager* get()
{
if(m_pInstance == nullptr)
{
m_pInstance = new ResourceManager();
}
return m_pInstance;
}
static void startUp() override
{
//管理器的启动代码
}
static void shutDown() override
{
//管理器的终止代码
}
};
//其他管理器
...
//
class GOW
{
private:
static std::stack<IManager*> m_stkManager;
//这里使用栈的原因是,一般先启动的管理器更为基础,所以要后终止
static void InitManager(IManager* pMananger)
{
pManager->startUp();
m_stkManager.push(pMananger);
}
public:
static void Init()
{
InitManager(ResourceManager::get());
//其他管理器同上
...
//
}
static void UnInit()
{
while(!m_stkManager.empty())
{
auto* pManager = m_stkManager.top();
m_stkManager.pop();
pManager->shutDown();
delete pManager;
}
}
};
这种方式有个好处,在新增或者修改导致一些管理器的依赖顺序改变时,只需要修改Init函数中的启动顺序,则终止的顺序也会跟着改变。引擎开发者只需要关注管理器的启动顺序就好。