每個遊戲都會包含一些底層支持系統, 例如資源管理器、日誌系統、對象池、動畫管理器、音樂/音效管理器等。這些管理器通常是一個單例類,如下面這段代碼:
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函數中的啓動順序,則終止的順序也會跟着改變。引擎開發者只需要關注管理器的啓動順序就好。