其实很早就想将代码贴出来,一直都在想服务器的讲解可能比较枯燥。
有时间的话,也可以先看看设计模式之类的,比如一些通用的Factory模式,Adapter模式,了解了解。
其他的也不多说。先贴代码,做做简单的说明。
上一章讲述了IModule和IModuleFactory的实现,这里就不多讲。现在有一个关键的地方,就是我们在实现相应逻辑功能的时候,不可能只写一个逻辑功能模块。这时候我们就需要使用相应的ModuleFactory来创建对象实例。
IModuleFactory重写一遍:
class IModuleFactory
{
public:
IModuleFactory(IModuleFactory * next)
{
m_pNext = next;
m_pModule = 0;
};
virtual ~IModuleFactory()
{
if (m_pModule != 0)
{
m_pModule->Shutdown();
delete m_pModule;
}
};
virtual const char * Name() = 0;
virtual IModule * Create() = 0;
IModuleFactory * Next() { return m_pNext; }
IModule * GetModule() { return m_pModule; }
bool Created() { return m_pModule != 0; }
protected:
IModuleFactory * m_pNext;
IModule * m_pModule;
};
通过链表的形式,只需要得到第一个ModuleFactory,我们就能得到所有的ModuleFactory,然后创建出所有Module的实例了。
下面使用宏来替换相应的ModuleFactory:
// 这里需要关注一下##和#的用法
#define CREATE_MODULE(a) \
class a##ModuleFactory: public IModuleFactory \
{ \
public: \
a##ModuleFactory(IModuleFactory * & p): IModuleFactory(p) { p = this; } \
virtual ~##a##ModuleFactory() {} \
virtual IModule * Create() { if (m_pModule == 0) {m_pModule = new a;} return m_pModule; } \
virtual const char * Name() { return #a; } \
}; \
a##ModuleFactory a##ModuleFactory(g_pModuleFactory);
在此说明一下,#把宏参数变为一个字符串,用##把两个宏参数贴合在一起。具体的用法各位可以上百度或者写个小程序测试一下。
为了好看和代码编写简单,我们将DLL的函数导出也用宏定义一下:#define BEGIN_MODULES \
extern "C" __declspec(dllexport) \
IModuleFactory * g_pModuleFactory = 0; \
extern "C" __declspec(dllexport) \
IModuleFactory * __cdecl GetModuleFactory() \
{ \
return g_pModuleFactory; \
}
首先我们需要一个class来管理游戏引擎的操作,我们将它命名为GameWorld:
class CGameWorld
{
public:
CGameWorld(const char *path);
virtual ~CGameWorld();
public:
CKernel * GetKernel() {return m_pKernel;}
CModuleSet * GetModuleSet() {return m_pModuleSet;}
public:
bool Begin();
bool Shutdown();
private:
CKernel * m_pKernel;
CModuleSet * m_pModuleSet;
private:
char m_path[MAX_PATH];
};
它里面有两个成员,Kernel就是我所定义的引擎指针,ModuleSet是负责管理逻辑模块的功能。
我们先看ModuleSet:
class CModuleSet
{
public:
CModuleSet();
CModuleSet(const CModuleSet &other);
virtual ~CModuleSet();
public:
//初始化
bool Initialize(CKernel * pKernel);
//查找模块
IModule * FindModule(const char * module);
//释放模块
bool Shutdown();
//
public:
//加载模块
bool Create(const char * module);
protected:
typedef map<string, IModule*> MapModule;
private:
MapModule m_mapModule;
typedef IModuleFactory * (*GetModuleFactory)(); //宏定义函数指针类型
};
看到后面有一个typedef了吗,GetModuleFactory是不是很熟悉,在哪见过呢?
IModuleFactory * __cdecl GetModuleFactory() \
{ \
return g_pModuleFactory; \
}
就是它了,我们能通过它,得到函数地址,然后就能够得到ModuleFactory的指针,再然后就能创建Module的实例。
一切就是这么简单,但是没办法,还是需要一步步的讲解,首先是ModuleSet的构造和析构函数:
CModuleSet::CModuleSet()
{
}
CModuleSet::CModuleSet(const CModuleSet &other)
{
m_mapModule = other.m_mapModule;
}
CModuleSet::~CModuleSet()
{
}
这个有点浪费篇幅了,我也觉得,下面就是过去指针创建实例的地方了:
//加载模块
bool CModuleSet::Create(const char * module)
{
HINSTANCE hDll = NULL; //DLL句柄
hDll = LoadLibrary(module);
if (hDll == NULL)
{
return false;
}
GetModuleFactory modulefactory; //函数指针
modulefactory = (GetModuleFactory)GetProcAddress(hDll, "GetModuleFactory");
IModuleFactory * pCreator = (*modulefactory)();
if (pCreator == NULL)
{
FreeLibrary(hDll);
}
//保存Module
do
{
IModule * p = pCreator->Create();
string name = pCreator->Name();
m_mapModule.insert(make_pair(name, p));
pCreator = pCreator->Next();
} while (pCreator != NULL);
return true;
}
关于应用程序调用DLL的地方,有不熟悉的,可以去找找相关资料。
主要是3个函数LoadLibrary、FreeLibrary、GetProcAddress。
Module的初始化和释放:
//初始化
bool CModuleSet::Initialize(CKernel * pKernel)
{
for (MapModule::iterator it = m_mapModule.begin();
it != m_mapModule.end();
++it)
{
if (!it->second->Initialize((IKernel*)pKernel))
{
return false;
}
}
return true;
}
//释放模块
bool CModuleSet::Shutdown()
{
for (MapModule::iterator it = m_mapModule.begin();
it != m_mapModule.end();
++it)
{
if (!it->second->Shutdown())
{
return false;
}
}
return true;
}
查找模块:
//查找模块
IModule * CModuleSet::FindModule(const char * module)
{
MapModule::iterator it = m_mapModule.find(module);
if (it != m_mapModule.end())
{
return it->second;
}
return NULL;
}
下面我们讲讲提供给逻辑层调用的引擎接口:
class CKernel : IKernel
{
public:
CKernel();
virtual ~CKernel();
public:
// 显示
virtual void Print(const char * info);
// 获取其他逻辑模块地址
virtual IModule *GetModule(const char * name);
};
这里面我只写了2个接口,其中一个Print是为了抛砖引玉,能实现它,自然也就能实现其他的接口。
另外一个是获得其他逻辑模块的地址,这样的话我们就能够实现引擎和逻辑、逻辑和逻辑之间的相会调用了。
下面的是实现代码:
extern CGameWorld *g_pGameWorld;
CKernel::CKernel()
{
}
CKernel::~CKernel()
{
}
//
void CKernel::Print(const char * info)
{
printf("%s", info);
}
//
IModule *CKernel::GetModule(const char * name)
{
return g_pGameWorld->GetModuleSet()->FindModule(name);
}
到此,GameWorld的两个成员就都实现完了,我们再来看看GameWorld的实现。
先是构造和析构:
CGameWorld::CGameWorld(const char *path)
{
m_pKernel = NULL;
m_pModuleSet = NULL;
strcpy(m_path, path);
}
CGameWorld::~CGameWorld()
{
delete m_pKernel;
delete m_pModuleSet;
}
Begin和Shutdown的实现:
bool CGameWorld::Begin()
{
m_pKernel = new CKernel();
m_pModuleSet = new CModuleSet();
// 加载dll,这里只加载了一个,抛砖引玉只用
// 主要讲解并不在这里,有兴趣的可以加载多个dll
char dll[MAX_PATH] = {0};
if (!m_path || strlen(m_path) == 0)
{
return false;
}
size_t index = strlen(m_path) - 1;
while (index >= 0)
{
if (m_path[index] == '\\')
{
break;
}
index--;
}
strncpy(dll, m_path, index + 1);
strcat(dll, "Logic.dll");
m_pModuleSet->Create(dll);
// 断点跟进就能看到如何相互调用,包括引擎和逻辑间调用,逻辑和逻辑间调用
m_pModuleSet->Initialize(m_pKernel);
return true;
}
bool CGameWorld::Shutdown()
{
m_pModuleSet->Shutdown();
return true;
}
我们再来看看main函数:
CGameWorld * g_pGameWorld = 0;
int main(int argc, char* argv[])
{
g_pGameWorld = new CGameWorld(argv[0]);
g_pGameWorld->Begin();
// 这里可以使用scanf或其他通过读字符串的操作,通过输入命令来break,随意发挥
while (true)
{
Sleep(1000);
}
g_pGameWorld->Shutdown();
delete g_pGameWorld;
return 0;
}
引擎的代码就到这里了,逻辑DLL的代码可以通过下面的链接下载之后看一下。只是实现了Print和GetModule的调用。
在vs2005下编译并且调试通过。
说完这一章,其实应该明白了逻辑功能个引擎之间的调用,后面的只要讲解都会是关于引擎的实现,有可能讲解的篇幅太长,一个功能分为几个篇章讲解。代码并不太会频繁提供源码下载,主要是一些想法的东西和文章中将代码贴出。不过在一个完整的功能讲完之后都会有编译通过的的源码提供。
本章源码下载地址: